Browse Source

Implements workspace symbol provider

Geequlim 8 năm trước cách đây
mục cha
commit
6ba675ad21

+ 4 - 7
package.json

@@ -15,12 +15,6 @@
   ],
   "main": "./out/src/extension",
   "contributes": {
-    "commands": [
-      {
-        "command": "extension.sayHello",
-        "title": "Hello World"
-      }
-    ],
     "configuration": {
       "type": "object",
       "title": "Godot tools configuration",
@@ -32,7 +26,7 @@
         },
         "GDScriptServer.maxNumberOfProblems": {
           "type": "number",
-          "default": 10,
+          "default": 100,
           "description": "Controls the maximum number of problems produced by the server."
         }
       }
@@ -50,5 +44,8 @@
     "mocha": "^2.3.3",
     "@types/node": "^6.0.40",
     "@types/mocha": "^2.2.32"
+  },
+  "dependencies": {
+    "glob": "^7.1.1"
   }
 }

+ 35 - 0
src/config.ts

@@ -0,0 +1,35 @@
+import GDScriptSymbolParser from './gdscript/symbolparser';
+
+class Config {
+  private symbols;
+  public parser: GDScriptSymbolParser;
+
+  constructor() {
+    this.symbols = {};
+    this.parser = new GDScriptSymbolParser();
+  }
+
+  loadSymbolsFromFile(path) {
+    const script = this.parser.parseFile(path);
+    this.setSymbols(path, script);
+    return script;
+  }
+
+  setSymbols(path, s) {
+    this.symbols[path] = s;
+  }
+
+  getSymbols(path) {
+    return this.symbols[path];
+  }
+
+  setAllSymbols(s) {
+    this.symbols = s;
+  }
+  
+  getAllSymbols() {
+    return this.symbols;
+  }
+};
+
+export default new Config();

+ 1 - 1
src/extension.ts

@@ -7,7 +7,7 @@ let tool: ToolManager = null;
 
 export function activate(context: ExtensionContext) {
 	tool = new ToolManager(context);
-	context.subscriptions.push(tool);
+	// context.subscriptions.push(tool);
 	context.subscriptions.push(new WindowWatch());
 	console.log("[GodotTools]: Extension Activated");
 }

+ 2 - 2
src/gdscript/parser.ts → src/gdscript/diagnostic.ts

@@ -28,7 +28,7 @@ interface ParseRequest {
 
 
 
-class GDParser {
+class GDScriptDiagnosticSeverity {
   private _subscription: DiagnosticCollection;
   
   constructor() {
@@ -86,4 +86,4 @@ class GDParser {
   
 }
 
-export default GDParser;
+export default GDScriptDiagnosticSeverity;

+ 97 - 0
src/gdscript/symbolparser.ts

@@ -0,0 +1,97 @@
+import {Range} from 'vscode';
+import * as fs from 'fs';
+
+interface GDScript {
+  constants: {},
+  functions: {},
+  variables: {},
+  signals: {},
+  classes: {},
+  base: string,
+  native: string
+}
+
+class GDScriptSymbolParser {
+  constructor() {
+  }
+
+  parseContent(content: string): GDScript {
+    const script: GDScript = {
+        constants: {},
+        functions: {},
+        variables: {},
+        signals: {},
+        classes: {},
+        base: "Object",
+        native: "Object"
+    }
+    const text  = content;
+    const lines = text.split(/\r?\n/);
+
+    const getMatches = (string, regex, index=1) => {
+      var matches = [];
+      var match;
+      while (match = regex.exec(string)) {
+        matches.push(match[index]);
+      }
+      return matches;
+    };
+    
+    const findLineRanges = (symbols, reg)=>{
+      const sm = {};
+      symbols.map((name:string)=>{
+        let line = 0;
+        let curline = 0;
+        lines.map(l=>{
+          const nreg = reg.replace("$X$", name);
+          if(l.match(nreg) != null) {
+            line = curline;
+            return;
+          }
+          curline += 1;
+        });
+        sm[name] = line;
+      });
+      return sm;
+    }
+    
+    let funcsnames = getMatches(text, /func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/gi, 1);
+    const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\(.*\\)");
+    for (let key of Object.keys(funcs))
+      script.functions[key] = new Range(funcs[key], 0, funcs[key],lines[funcs[key]].length);
+    
+    let signalnames = getMatches(text, /signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/gi, 1);
+    const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\(.*\\)");
+    for (let key of Object.keys(signals))
+      script.signals[key] = new Range(signals[key], 0, signals[key],lines[signals[key]].length);
+    
+    let varnames = getMatches(text, /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/gi, 1);
+    const vars = findLineRanges(varnames, "var\\s+$X$\\s*");
+    for (let key of Object.keys(vars))
+      script.variables[key] = new Range(vars[key], 0, vars[key],lines[vars[key]].length);
+    
+    let constnames = getMatches(text, /const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/gi, 1);
+    const consts = findLineRanges(constnames, "const\\s+$X$\\s*");
+    for (let key of Object.keys(consts))
+      script.constants[key] = new Range(consts[key], 0, consts[key],lines[consts[key]].length);
+    
+    let classnames = getMatches(text, /class\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*extends\s+/gi, 1);
+    const classes = findLineRanges(classnames, "class\\s+$X$\\s*extends\\s+");
+    for (let key of Object.keys(classes))
+      script.classes[key] = new Range(classes[key], 0, classes[key],lines[classes[key]].length);
+
+    return script;
+  }
+
+  parseFile(path:string): GDScript {
+    const self = this;
+    if(fs.existsSync(path) && fs.statSync(path).isFile()){
+      const content = fs.readFileSync(path, 'utf-8');
+      return this.parseContent(content);
+    }
+    return null;
+  }
+
+}
+
+export default GDScriptSymbolParser;

+ 23 - 43
src/gdscript/symbolprovider.ts

@@ -7,61 +7,41 @@ import {
   Range
 } from 'vscode';
 
+import GDScriptSymbolParser from '../gdscript/symbolparser';
+import config from '../config';
+
 class GDScriptSymbolProvider implements DocumentSymbolProvider {
-  constructor() {}
+  private parser: GDScriptSymbolParser = null;
+
+  constructor() {
+    this.parser = new GDScriptSymbolParser();
+  }
 
   provideDocumentSymbols(document: TextDocument, token: CancellationToken): SymbolInformation[] | Thenable<SymbolInformation[]> {
 
     const symbols: SymbolInformation[] = [];
-    const text  =document.getText();
-    const lines = text.split(/\r?\n/);
+    const script = this.parser.parseContent(document.getText());
+    config.setSymbols(document.fileName, script);
 
-    const getMatches = (string, regex, index=1) => {
-      var matches = [];
-      var match;
-      while (match = regex.exec(string)) {
-        matches.push(match[index]);
-      }
-      return matches;
-    };
-    
-    const findLineRanges = (symbols, reg)=>{
-      const sm = {};
-      symbols.map((name:string)=>{
-        let line = 0;
-        let curline = 0;
-        lines.map(l=>{
-          const nreg = reg.replace("$X$", name);
-          if(l.match(nreg) != null) {
-            line = curline;
-            return;
-          }
-          curline += 1;
-        });
-        sm[name] = line;
-      });
-      return sm;
-    }
-    
-    let funcsnames = getMatches(text, /func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/gi, 1);
-    const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\(.*\\)");
+    const funcs = script.functions;
     for (let key of Object.keys(funcs))
-      symbols.push(new SymbolInformation(key, SymbolKind.Function, new Range(funcs[key], 0, funcs[key],lines[funcs[key]].length)));
+      symbols.push(new SymbolInformation(key, SymbolKind.Function, funcs[key]));
     
-    let signalnames = getMatches(text, /signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(.*\)/gi, 1);
-    const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\(.*\\)");
+    const signals = script.signals;
     for (let key of Object.keys(signals))
-      symbols.push(new SymbolInformation(key, SymbolKind.Interface, new Range(signals[key], 0, signals[key],lines[signals[key]].length)));
-    
-    let varnames = getMatches(text, /var\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/gi, 1);
-    const vars = findLineRanges(varnames, "var\\s+$X$\\s*");
+      symbols.push(new SymbolInformation(key, SymbolKind.Interface, signals[key]));
+
+    const vars = script.variables;
     for (let key of Object.keys(vars))
-      symbols.push(new SymbolInformation(key, SymbolKind.Variable, new Range(vars[key], 0, vars[key],lines[vars[key]].length)));
+      symbols.push(new SymbolInformation(key, SymbolKind.Variable, vars[key]));
     
-    let constnames = getMatches(text, /const\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*/gi, 1);
-    const consts = findLineRanges(constnames, "const\\s+$X$\\s*");
+    const consts = script.constants;
     for (let key of Object.keys(consts))
-      symbols.push(new SymbolInformation(key, SymbolKind.Constant, new Range(consts[key], 0, consts[key],lines[consts[key]].length)));
+      symbols.push(new SymbolInformation(key, SymbolKind.Constant, consts[key]));
+
+    const classes = script.classes;
+    for (let key of Object.keys(classes))
+      symbols.push(new SymbolInformation(key, SymbolKind.Class, classes[key]));
     
     return symbols;
   }

+ 38 - 0
src/gdscript/workspace_symbol_provider.ts

@@ -0,0 +1,38 @@
+import * as vscode from 'vscode';
+import config from '../config';
+
+class GDScriptWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider {
+    public provideWorkspaceSymbols(query: string, token: vscode.CancellationToken): vscode.SymbolInformation[] {
+      const scripts = config.getAllSymbols();
+      const symbols: vscode.SymbolInformation[] = [];
+      console.log(query);
+      for (let path of Object.keys(scripts)) {
+        const queryMembers = (query, members, kind: vscode.SymbolKind, path:string)=> {
+          for (let name of Object.keys(members)) {
+            const range: vscode.Range = members[name];
+            if(name.toLowerCase().indexOf(query.toLowerCase()) != -1) {
+              const symbol: vscode.SymbolInformation = {
+                name,
+                kind,
+                containerName: "",
+                location: {
+                  uri: vscode.Uri.file(path),
+                  range
+                }
+              };
+              symbols.push(symbol);
+            }
+          }
+        }
+        const scrip = scripts[path];
+        queryMembers(query, scrip.functions, vscode.SymbolKind.Function, path);
+        queryMembers(query, scrip.signals, vscode.SymbolKind.Interface, path);
+        queryMembers(query, scrip.variables, vscode.SymbolKind.Variable, path);
+        queryMembers(query, scrip.constants, vscode.SymbolKind.Constant, path);
+        queryMembers(query, scrip.classes, vscode.SymbolKind.Class, path);
+      }
+      return symbols;
+    }
+}
+
+export default GDScriptWorkspaceSymbolProvider;

+ 34 - 0
src/tool_manager.ts

@@ -2,19 +2,28 @@ import * as vscode from 'vscode';
 import DocDataManager from './docdata';
 import godotRequest from './request';
 import GDScriptSymbolProvider from './gdscript/symbolprovider';
+import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provider';
+var glob = require("glob")
+import config from './config';
 
 class ToolManager {
 
   private workspaceDir: string = "";
   private docs: DocDataManager = null;
   private symbolprovider: GDScriptSymbolProvider = null;
+  private workspacesymbolprovider: GDScriptWorkspaceSymbolProvider = null;
 
   constructor(context: vscode.ExtensionContext) {
     this.workspaceDir = vscode.workspace.rootPath;
     this.validate();
+    this.loadWorkspaceSymbols();
+    
     this.docs = new DocDataManager(context.extensionPath);
     this.symbolprovider = new GDScriptSymbolProvider();
     vscode.languages.registerDocumentSymbolProvider('gdscript', this.symbolprovider);
+    this.workspacesymbolprovider = new GDScriptWorkspaceSymbolProvider();
+    vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
+    
   }
 
   validate() {
@@ -33,6 +42,31 @@ class ToolManager {
     });
   }
 
+  loadAllSymbols(): Promise<any> {
+    const self = this;
+    return new Promise((resolve, reject) => {
+      glob( this.workspaceDir +"/**/*.gd", (err, files)=>{
+        if(!err) {
+          const symbols = {};
+          for(let i=0; i< files.length; i++)
+            symbols[files[i]] = config.loadSymbolsFromFile(files[i]);
+          resolve(symbols);
+        }
+        else
+          reject(err);
+      });
+    });
+  }
+
+  loadWorkspaceSymbols() {
+    this.loadAllSymbols().then(symbols=>{
+        // vscode.window.showInformationMessage("GDScript symbols done");
+        config.setAllSymbols(symbols);
+    }).catch(e=>{
+        // vscode.window.showWarningMessage("GDScript symbols parse failed");
+    });
+  }
+
   dispose() {
 
   }

+ 12 - 10
src/window_watcher.ts

@@ -1,8 +1,7 @@
 import {Disposable, window} from 'vscode';
-import parse from "./gdscript/parser";
-import GDParser from './gdscript/parser';
+import GDScriptDiagnosticSeverity from './gdscript/diagnostic';
 import GDScriptCompleter from './gdscript/completion';
-
+import config from './config';
 
 interface DocumentFlag {
   path: string,
@@ -12,7 +11,7 @@ interface DocumentFlag {
 class WindowWatcher {
   
   private _disposable: Disposable;
-  private _parser: GDParser;
+  private _diagnosticSeverity: GDScriptDiagnosticSeverity;
   private _lastText: DocumentFlag;
   private _completer: GDScriptCompleter;
 
@@ -23,9 +22,9 @@ class WindowWatcher {
     window.onDidChangeTextEditorOptions(this.onDidChangeTextEditorOptions.bind(this), this, subscriptions);
     window.onDidChangeTextEditorViewColumn(this.onDidChangeTextEditorViewColumn.bind(this), this, subscriptions);
     
-    this._parser = new GDParser();
+    this._diagnosticSeverity = new GDScriptDiagnosticSeverity();
     this._completer = new GDScriptCompleter();
-    this._disposable = Disposable.from(...subscriptions, this._parser, this._completer);
+    this._disposable = Disposable.from(...subscriptions, this._diagnosticSeverity, this._completer);
     this._lastText = {path: "-1", version: -1};
   }
 
@@ -39,11 +38,12 @@ class WindowWatcher {
    * to `undefined`.
    */
   private onDidChangeActiveTextEditor(event: any) {
-    console.log("[GodotTools]:onDidChangeActiveTextEditor", event);
-    if(window.activeTextEditor != undefined) {
+    // console.log("[GodotTools]:onDidChangeActiveTextEditor", event);
+    if(window.activeTextEditor != undefined) { 
       const doc = window.activeTextEditor.document;
-      this._parser.parseDocument(doc);
+      this._diagnosticSeverity.parseDocument(doc);
       this._lastText = {path: doc.fileName, version: doc.version};
+      config.loadSymbolsFromFile(doc.fileName);
     }
   }
 
@@ -54,9 +54,11 @@ class WindowWatcher {
     console.log("[GodotTools]:onDidChangeTextEditorSelection");
     const doc = window.activeTextEditor.document;
     const curText: DocumentFlag= {path: doc.fileName, version: doc.version};
+    // Check content changed
     if(this._lastText.path != curText.path || this._lastText.version != curText.version) {
-      this._parser.parseDocument(doc);
+      this._diagnosticSeverity.parseDocument(doc);
       this._lastText = curText;
+      config.loadSymbolsFromFile(doc.fileName);
     }
   }