Przeglądaj źródła

Add actions to run project and scenes
Fix errors with validating
Add node completion

Geequlim 8 lat temu
rodzic
commit
815a80dfed
5 zmienionych plików z 229 dodań i 8 usunięć
  1. 32 1
      configrations/snippets.json
  2. 17 0
      package.json
  3. 97 2
      src/config.ts
  4. 2 2
      src/gdscript/diagnostic.ts
  5. 81 3
      src/tool_manager.ts

+ 32 - 1
configrations/snippets.json

@@ -114,7 +114,7 @@
   },
   },
 
 
   "function define": {
   "function define": {
-    "prefix": "while",
+    "prefix": "func",
 		"body": [
 		"body": [
       "func ${1:method}(${2:args}):",
       "func ${1:method}(${2:args}):",
       "\t${3:pass}"
       "\t${3:pass}"
@@ -161,5 +161,36 @@
 		"body": [
 		"body": [
       "${1:element} in ${$2:array}"
       "${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)"
+    ]
   }
   }
 }
 }

+ 17 - 0
package.json

@@ -20,6 +20,18 @@
       {
       {
         "command": "godot.updateWorkspaceSymbols",
         "command": "godot.updateWorkspaceSymbols",
         "title": "GodotTools: Update Workspace Symbols"
         "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": {
     "configuration": {
@@ -35,6 +47,11 @@
           "type": "number",
           "type": "number",
           "default": 100,
           "default": 100,
           "description": "Controls the maximum number of problems produced by the server."
           "description": "Controls the maximum number of problems produced by the server."
+        },
+        "GodotTools.editorPath": {
+          "type": "string",
+          "default": "",
+          "description": "The absolute path of your godot editor"
         }
         }
       }
       }
     },
     },

+ 97 - 2
src/config.ts

@@ -2,15 +2,28 @@ import GDScriptSymbolParser from './gdscript/symbolparser';
 import * as fs from 'fs';
 import * as fs from 'fs';
 import {CompletionItem, CompletionItemKind, TextEdit, Range, workspace} from 'vscode';
 import {CompletionItem, CompletionItemKind, TextEdit, Range, workspace} from 'vscode';
 
 
+interface NodeInfo {
+  name: string,
+  type: string,
+  parent: string,
+  instance: string
+};
+
 class Config {
 class Config {
   private symbols;
   private symbols;
   private classes;
   private classes;
   public bintinSybmolInfoList: CompletionItem[];
   public bintinSybmolInfoList: CompletionItem[];
   public parser: GDScriptSymbolParser;
   public parser: GDScriptSymbolParser;
+  // scriptpath : scenepath
+  public scriptSceneMap: Object;
+  // scenepath : NodeInfo[]
+  private nodeInfoMap: Object;
 
 
   constructor() {
   constructor() {
     this.symbols = {};
     this.symbols = {};
     this.bintinSybmolInfoList = [];
     this.bintinSybmolInfoList = [];
+    this.nodeInfoMap = {};
+    this.scriptSceneMap = {};
     this.parser = new GDScriptSymbolParser();
     this.parser = new GDScriptSymbolParser();
   }
   }
 
 
@@ -37,12 +50,12 @@ class Config {
   }
   }
 
 
   normalizePath(path) {
   normalizePath(path) {
-    let newpath = path;
+    let newpath = path.replace(/\\/g, "/");
     if( path.indexOf(":") != -1){
     if( path.indexOf(":") != -1){
       let parts = path.split(":");
       let parts = path.split(":");
       newpath = parts[0].toUpperCase()
       newpath = parts[0].toUpperCase()
       for(let i=1; i<parts.length; i++)
       for(let i=1; i<parts.length; i++)
-        newpath += parts[i].replace(/\\/g, "/");
+        newpath += parts[i];
     }
     }
     return newpath;
     return newpath;
   }
   }
@@ -138,9 +151,91 @@ class Config {
         items = [...items, ...addScriptItems(script.signals, CompletionItemKind.Interface, "Signal")];
         items = [...items, ...addScriptItems(script.signals, CompletionItemKind.Interface, "Signal")];
         items = [...items, ...addScriptItems(script.constants, CompletionItemKind.Enum, "Constant")];
         items = [...items, ...addScriptItems(script.constants, CompletionItemKind.Enum, "Constant")];
       }
       }
+
+      const addSceneNodes = ()=>{
+        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;
       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) {
   getClass(name: string) {
     return this.classes[name];
     return this.classes[name];
   }
   }

+ 2 - 2
src/gdscript/diagnostic.ts

@@ -59,7 +59,7 @@ class GDScriptDiagnosticSeverity {
     const text = doc.getText();
     const text = doc.getText();
     
     
     const check = (name:string, range: vscode.Range) => {
     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'));
       var matchs = text.match(new RegExp(pattern, 'g'));
       if(matchs.length <= 1)
       if(matchs.length <= 1)
         diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning));
         diagnostics.push(new vscode.Diagnostic(range, `${name} is never used.`, DiagnosticSeverity.Warning));
@@ -81,7 +81,7 @@ class GDScriptDiagnosticSeverity {
       if(semicolonIndex != -1) {
       if(semicolonIndex != -1) {
         diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex+1), "Statement ends with a semicolon.", DiagnosticSeverity.Warning));
         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)
         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));
           diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, 0, i, line.length), "':' expected at end of the line.", DiagnosticSeverity.Error));
       }
       }

+ 81 - 3
src/tool_manager.ts

@@ -6,6 +6,8 @@ import GDScriptCompletionItemProvider from './gdscript/completion';
 var glob = require("glob")
 var glob = require("glob")
 import config from './config';
 import config from './config';
 import * as path from 'path';
 import * as path from 'path';
+import * as fs from 'fs';
+const cmd = require('node-cmd');
 
 
 class ToolManager {
 class ToolManager {
 
 
@@ -36,7 +38,10 @@ class ToolManager {
     vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
     vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
     // Commands
     // Commands
     this._disposable = vscode.Disposable.from(
     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<any> {
   loadAllSymbols(): Promise<any> {
     const self = this;
     const self = this;
     return new Promise((resolve, reject) => {
     return new Promise((resolve, reject) => {
-      glob( this.workspaceDir +"/**/*.gd", (err, files)=>{
+      glob( self.workspaceDir +"/**/*.gd", (err, files)=>{
         if(!err) {
         if(!err) {
           const symbols = {};
           const symbols = {};
           for(let i=0; i< files.length; i++)
           for(let i=0; i< files.length; i++)
             symbols[files[i]] = config.loadSymbolsFromFile(files[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);
           resolve(symbols);
         }
         }
         else
         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=>{
     this.loadAllSymbols().then(symbols=>{
         vscode.window.showInformationMessage("Update GDScript symbols done");
         vscode.window.showInformationMessage("Update GDScript symbols done");
         config.setAllSymbols(symbols);
         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() {
   loadClasses() {
     let done :boolean = false;
     let done :boolean = false;
     if(this.workspaceDir)
     if(this.workspaceDir)