From 815a80dfed5b9404071ec5335e85bd4d3118229e Mon Sep 17 00:00:00 2001 From: Geequlim Date: Tue, 27 Dec 2016 21:31:00 +0800 Subject: [PATCH] Add actions to run project and scenes Fix errors with validating Add node completion --- configrations/snippets.json | 33 ++++++++++++- package.json | 17 +++++++ src/config.ts | 99 ++++++++++++++++++++++++++++++++++++- src/gdscript/diagnostic.ts | 4 +- src/tool_manager.ts | 84 +++++++++++++++++++++++++++++-- 5 files changed, 229 insertions(+), 8 deletions(-) diff --git a/configrations/snippets.json b/configrations/snippets.json index 7c313ab..b17bbd0 100644 --- a/configrations/snippets.json +++ b/configrations/snippets.json @@ -114,7 +114,7 @@ }, "function define": { - "prefix": "while", + "prefix": "func", "body": [ "func ${1:method}(${2:args}):", "\t${3:pass}" @@ -161,5 +161,36 @@ "body": [ "${1:element} in ${$2:array}" ] + }, + + "GDScript template": { + "prefix": "gdscript", + "body": [ + "extends ${1:BaseClass}", + "", + "# class member variables go here, for example:", + "# var a = 2", + "# var b = \"textvar\"", + "", + "func _ready():", + "\t# Called every time the node is added to the scene.", + "\t# Initialization here", + "\tpass", + "" + ] + }, + + "Enable process function": { + "prefix": "process", + "body": [ + "set_process(true)" + ] + }, + + "Enable process input function": { + "prefix": "processin", + "body": [ + "set_process_input(true)" + ] } } \ No newline at end of file diff --git a/package.json b/package.json index 1a842fc..83c41cd 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,18 @@ { "command": "godot.updateWorkspaceSymbols", "title": "GodotTools: Update Workspace Symbols" + }, + { + "command": "godot.runWorkspace", + "title": "GodotTools: Run workspace as godot project" + }, + { + "command": "godot.openWithEditor", + "title": "GodotTools: Open workspace with godot editor" + }, + { + "command": "godot.runCurrentScene", + "title": "GodotTools: Run current scene" } ], "configuration": { @@ -35,6 +47,11 @@ "type": "number", "default": 100, "description": "Controls the maximum number of problems produced by the server." + }, + "GodotTools.editorPath": { + "type": "string", + "default": "", + "description": "The absolute path of your godot editor" } } }, diff --git a/src/config.ts b/src/config.ts index 81ac727..17973d0 100644 --- a/src/config.ts +++ b/src/config.ts @@ -2,15 +2,28 @@ import GDScriptSymbolParser from './gdscript/symbolparser'; import * as fs from 'fs'; import {CompletionItem, CompletionItemKind, TextEdit, Range, workspace} from 'vscode'; +interface NodeInfo { + name: string, + type: string, + parent: string, + instance: string +}; + class Config { private symbols; private classes; public bintinSybmolInfoList: CompletionItem[]; public parser: GDScriptSymbolParser; + // scriptpath : scenepath + public scriptSceneMap: Object; + // scenepath : NodeInfo[] + private nodeInfoMap: Object; constructor() { this.symbols = {}; this.bintinSybmolInfoList = []; + this.nodeInfoMap = {}; + this.scriptSceneMap = {}; this.parser = new GDScriptSymbolParser(); } @@ -37,12 +50,12 @@ class Config { } normalizePath(path) { - let newpath = path; + let newpath = path.replace(/\\/g, "/"); if( path.indexOf(":") != -1){ let parts = path.split(":"); newpath = parts[0].toUpperCase() for(let i=1; i{ + const _items: CompletionItem[] = []; + for (let scnenepath of Object.keys(this.nodeInfoMap)) { + const nodes: NodeInfo[] = this.nodeInfoMap[scnenepath]; + nodes.map((n=>{ + const item = new CompletionItem(n.name, CompletionItemKind.Reference); + item.detail = n.type; + item.documentation = `${n.parent}/${n.name} in ${scnenepath}`; + _items.push(item); + + const fullitem = new CompletionItem(`${n.parent}/${n.name}`, CompletionItemKind.Reference); + fullitem.detail = n.type; + fullitem.filterText = n.name; + fullitem.sortText = n.name; + fullitem.documentation = `${n.parent}/${n.name} in ${scnenepath}`; + _items.push(fullitem); + })); + } + return _items; + }; + items = [...items, ...addSceneNodes()]; + return items; } + loadScene(scenePath: string) { + console.log(scenePath); + if(fs.existsSync(scenePath) && fs.statSync(scenePath).isFile()) { + try { + const content: string = fs.readFileSync(scenePath, 'utf-8'); + if(content) { + // extern resources + const exteres = {}; + let reg = /ext_resource path="res:\/\/(.*)" type="(.*)" id=(\d+)/g; + let match = reg.exec(content); + while (match != null) { + const path = match[1]; + const type = match[2]; + const id = match[3]; + exteres[id] = {path, type}; + if (type == "Script") { + let workspacescenepath = scenePath; + if(workspace) + workspacescenepath = workspace.asRelativePath(scenePath); + this.scriptSceneMap[path] = workspacescenepath; + } + match = reg.exec(content); + } + // nodes + const nodes: NodeInfo[] = []; + reg = /node\s+name="(.*)"\s+type="(.*)"\s+parent="(.*)"/g; + match = reg.exec(content); + while (match != null) { + nodes.push({ + name : match[1], + type : match[2], + parent : match[3], + instance: "" + }); + match = reg.exec(content); + } + // packed scenes + reg = /node name="(.*)" parent="(.*)" instance=ExtResource\(\s*(\d+)\s*\)/g; + match = reg.exec(content); + while (match != null) { + const id = match[3]; + nodes.push({ + name : match[1], + type : exteres[id].type, + parent : match[2], + instance: exteres[id].path + }); + match = reg.exec(content); + } + if(workspace) + scenePath = workspace.asRelativePath(scenePath); + this.nodeInfoMap[scenePath] = nodes; + } + } catch (error) { + console.error(error); + } + } + } + getClass(name: string) { return this.classes[name]; } diff --git a/src/gdscript/diagnostic.ts b/src/gdscript/diagnostic.ts index 7200e92..41fa9ee 100644 --- a/src/gdscript/diagnostic.ts +++ b/src/gdscript/diagnostic.ts @@ -59,7 +59,7 @@ class GDScriptDiagnosticSeverity { const text = doc.getText(); const check = (name:string, range: vscode.Range) => { - const pattern = `[\\s\\+\\-\\*/%\\^\\(\\[\\{]${name}[^0-9A-Za-z_]\\s*`; + const pattern = `[\\s\\+\\-\\*/%\\^\\(\\[\\{\.]${name}[^0-9A-Za-z_]\\s*`; var matchs = text.match(new RegExp(pattern, 'g')); if(matchs.length <= 1) diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning)); @@ -81,7 +81,7 @@ class GDScriptDiagnosticSeverity { if(semicolonIndex != -1) { diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex+1), "Statement ends with a semicolon.", DiagnosticSeverity.Warning)); } - if(line.match(/\s+if|elif|else|for|while|func|class\s+/g) && line.indexOf(":") == -1) { + if(line.match(/\s*(if|elif|else|for|while|func|class)\s/g) && line.indexOf(":") == -1) { if(line.indexOf("#") == -1) diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, 0, i, line.length), "':' expected at end of the line.", DiagnosticSeverity.Error)); } diff --git a/src/tool_manager.ts b/src/tool_manager.ts index 8c9b25e..eeff5ee 100644 --- a/src/tool_manager.ts +++ b/src/tool_manager.ts @@ -6,6 +6,8 @@ import GDScriptCompletionItemProvider from './gdscript/completion'; var glob = require("glob") import config from './config'; import * as path from 'path'; +import * as fs from 'fs'; +const cmd = require('node-cmd'); class ToolManager { @@ -36,7 +38,10 @@ class ToolManager { vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'"); // Commands this._disposable = vscode.Disposable.from( - vscode.commands.registerCommand('godot.updateWorkspaceSymbols', this.loadWorkspaceSymbols.bind(this)) + vscode.commands.registerCommand('godot.updateWorkspaceSymbols', this.loadWorkspaceSymbols.bind(this)), + vscode.commands.registerCommand('godot.runWorkspace', ()=>{this.openWorkspaceWithEditor()}), + vscode.commands.registerCommand('godot.openWithEditor', ()=>{this.openWorkspaceWithEditor("-e")}), + vscode.commands.registerCommand('godot.runCurrentScene', this.runCurrentScenr.bind(this)) ); } @@ -59,11 +64,33 @@ class ToolManager { loadAllSymbols(): Promise { const self = this; return new Promise((resolve, reject) => { - glob( this.workspaceDir +"/**/*.gd", (err, files)=>{ + glob( self.workspaceDir +"/**/*.gd", (err, files)=>{ if(!err) { const symbols = {}; for(let i=0; i< files.length; i++) symbols[files[i]] = config.loadSymbolsFromFile(files[i]); + // load autoloads from engin.cfg + const engincfg = path.join(self.workspaceDir, "engine.cfg"); + if(fs.existsSync(engincfg) && fs.statSync(engincfg).isFile()) { + try { + const script = { constants: {}, functions: {}, variables: {}, signals: {}, classes: {}, base: "Object", native: "Object"}; + let content: string = fs.readFileSync(engincfg, 'utf-8'); + if(content && content.indexOf("[autoload]") != -1) { + content = content.substring(content.indexOf("[autoload]")+"[autoload]".length, content.length); + content = content.substring(0, content.indexOf("[")); + const lines = content.split(/\r?\n/); + lines.map(l=>{ + if(l.indexOf("=") != 0) { + const name = l.substring(0, l.indexOf("=")); + script.constants[name] = new vscode.Range(0, 0, 0,0); + } + }); + } + symbols["autoload"] = script; + } catch (error) { + console.error(error); + } + } resolve(symbols); } else @@ -72,7 +99,18 @@ class ToolManager { }); } - loadWorkspaceSymbols() { + private loadAllNodesInWorkspace() { + glob( this.workspaceDir +"/**/*.tscn", (err, files)=>{ + if(!err) { + const symbols = {}; + for(let i=0; i< files.length; i++) + config.loadScene(files[i]); + } + }); + } + + private loadWorkspaceSymbols() { + this.loadAllNodesInWorkspace(); this.loadAllSymbols().then(symbols=>{ vscode.window.showInformationMessage("Update GDScript symbols done"); config.setAllSymbols(symbols); @@ -81,6 +119,46 @@ class ToolManager { }); } + private openWorkspaceWithEditor(params="") { + let workspaceValid = false + if(this.workspaceDir) { + let cfg = path.join(this.workspaceDir, "engine.cfg"); + if( fs.existsSync(cfg) && fs.statSync(cfg).isFile()) + workspaceValid = true; + } + if(workspaceValid) + this.runEditor(`-path ${this.workspaceDir} ${params}`); + else + vscode.window.showErrorMessage("Current workspace is not a godot project"); + } + + private runEditor(params="") { + const editorPath = vscode.workspace.getConfiguration("GodotTools").get("editorPath", "") + if(!fs.existsSync(editorPath) || !fs.statSync(editorPath).isFile()) { + vscode.window.showErrorMessage("Invalid editor path to run the project"); + } + else { + cmd.run(`${editorPath} ${params}`); + } + } + + private runCurrentScenr() { + let scenePath = null + if(vscode.window.activeTextEditor) + scenePath = vscode.workspace.asRelativePath(vscode.window.activeTextEditor.document.uri); + console.log("======================", scenePath); + console.log(Object.keys(config.scriptSceneMap).toString()); + if(scenePath.endsWith(".gd")) + scenePath = config.scriptSceneMap[config.normalizePath(scenePath)]; + console.log("======================", scenePath); + if(scenePath && (scenePath.endsWith(".tscn") || scenePath.endsWith(".scn"))) { + scenePath = ` res://${scenePath} `; + this.openWorkspaceWithEditor(scenePath); + } + else + vscode.window.showErrorMessage("Current document is not a scene file"); + } + loadClasses() { let done :boolean = false; if(this.workspaceDir)