Browse Source

Implement mouse hover provider

Geequlim 8 years ago
parent
commit
ffbf1ed783
5 changed files with 173 additions and 21 deletions
  1. 9 1
      src/config.ts
  2. 5 20
      src/gdscript/definitionprovider.ts
  3. 124 0
      src/gdscript/hoverprovider.ts
  4. 31 0
      src/gdscript/utils.ts
  5. 4 0
      src/tool_manager.ts

+ 9 - 1
src/config.ts

@@ -17,11 +17,14 @@ class Config {
   // scriptpath : scenepath
   public scriptSceneMap: Object;
   // scenepath : NodeInfo[]
-  private nodeInfoMap: Object;
+  public nodeInfoMap: Object;
+  // symbolname: CompletionItem
+  public builtinSymbolInfoMap: Object;
 
   constructor() {
     this.symbols = {};
     this.bintinSybmolInfoList = [];
+    this.builtinSymbolInfoMap = {};
     this.nodeInfoMap = {};
     this.scriptSceneMap = {};
     this.parser = new GDScriptSymbolParser();
@@ -78,11 +81,13 @@ class Config {
       for (let key of Object.keys(this.classes)) {
         const classdoc = this.classes[key];
         const bintinSybmolInfoList = this.bintinSybmolInfoList;
+        const builtinSymbolInfoMap = this.builtinSymbolInfoMap;
         // class
         const item: CompletionItem = new CompletionItem(classdoc.name, CompletionItemKind.Class);
         item.detail = 'Native Class';
         item.documentation = classdoc.brief_description + " \n\n" +classdoc.description;
         bintinSybmolInfoList.push(item);
+        builtinSymbolInfoMap[classdoc.name] = item;
         // methods
         const methods = classdoc.methods
         const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name+"()")=>{
@@ -100,6 +105,7 @@ class Config {
           mdoc += m.description;
           mi.documentation = mdoc;
           bintinSybmolInfoList.push(mi);
+          builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = mi;
         };
         methods.map(m=>parsMethod(m, CompletionItemKind.Method));
         // signals
@@ -112,6 +118,7 @@ class Config {
           ci.detail = c.value;
           ci.documentation = `${classdoc.name}.${c.name} = ${c.value}`;
           bintinSybmolInfoList.push(ci);
+          builtinSymbolInfoMap[`${classdoc.name}.${c.name}`] = ci;
         });
         // properties
         const properties = classdoc.properties;
@@ -120,6 +127,7 @@ class Config {
           pi.detail = `${p.type} of ${classdoc.name}`;
           pi.documentation = p.description;
           bintinSybmolInfoList.push(pi);
+          builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = pi;
         };
         properties.map(p=>parseProp(p));
         // theme_properties

+ 5 - 20
src/gdscript/definitionprovider.ts

@@ -12,6 +12,7 @@ import {
 import * as path from 'path';
 import * as fs from 'fs';
 import config from '../config';
+import {isStr, getSelectedContent, getStrContent} from './utils';
 
 class GDScriptDefinitionProivder implements DefinitionProvider {
     constructor() {
@@ -19,22 +20,6 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
     }
 
     provideDefinition(document: TextDocument, position: Position, token: CancellationToken): Definition | Thenable < Definition > {
-        const isStr = (content:string) => (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
-        const getSelectedContent = ():string =>{
-            const line = document.lineAt(position);
-            const wordRange = document.getWordRangeAtPosition(position) ;
-            const machs = line.text.match(/[A-z_]+[A-z_0-9]*|".*"|'.*'|@".*"/g)
-            let res = line.text.substring(wordRange.start.character, wordRange.end.character);
-            machs.map(m=>{
-                if(m) {
-                    if(isStr(m)){
-                        res = m;
-                        return;
-                    }
-                }
-            });
-            return res;
-        };
         const getDefinitions = (content: string):Location[]| Location => {
             if(content.startsWith("res://")) {
                 content = content.replace("res://", "");
@@ -57,6 +42,7 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
                         for (let name of Object.keys(items)) {
                             if(name == content) {
                                 _items.push(new Location(Uri.file(path), items[name]));
+                                break;
                             }
                         }
                         return _items;
@@ -72,13 +58,12 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
                 return locations;
             }
         };
-
-
-        let selStr = getSelectedContent();
+        
+        let selStr = getSelectedContent(document, position);
         if(selStr) {
             // For strings
             if(isStr(selStr)) {
-                selStr = selStr.replace(/"|'|@"/g,"");
+                selStr =  getStrContent(selStr);
                 let fpath = path.join(path.dirname(document.uri.fsPath), selStr)
                 console.log(fpath);
                 if(fs.existsSync(fpath) && fs.statSync(fpath).isFile())

+ 124 - 0
src/gdscript/hoverprovider.ts

@@ -0,0 +1,124 @@
+import {
+    HoverProvider,
+    TextDocument,
+    Position,
+    CancellationToken,
+    Hover,
+    MarkedString,
+    workspace,
+    Uri,
+    CompletionItem,
+    CompletionItemKind
+} from 'vscode';
+import {
+    isStr,
+    getSelectedContent,
+    getStrContent
+} from './utils';
+import config from '../config';
+import * as path from 'path';
+
+class GDScriptHoverProvider implements HoverProvider {
+    constructor() {}
+
+    provideHover(document: TextDocument, position: Position, token: CancellationToken): Hover | Thenable < Hover > {
+        let hoverText = getSelectedContent(document, position);
+        if (isStr(hoverText))
+            hoverText = getStrContent(hoverText);
+        const workspaceSymbols = config.getAllSymbols();
+        let tips: MarkedString[] = [];
+        // check from workspace
+        for (let path of Object.keys(workspaceSymbols)) {
+            const script = workspaceSymbols[path];
+            let scriptips: MarkedString[] = [];
+            const getHoverText = (items, type, path): MarkedString[] => {
+                const _items: MarkedString[] = [];
+                for (let name of Object.keys(items)) {
+                    if (name == hoverText) {
+                        let dfile = path;
+                        if (workspace && workspace.asRelativePath(dfile))
+                            dfile = workspace.asRelativePath(dfile);
+                        _items.push({language:'gdscript', value:`${type} ${name}`});
+                        _items.push(`Defined in *[${dfile}](${Uri.file(path).toString()})*`)
+                        break;
+                    }
+                }
+                return _items;
+            }
+            scriptips = [...scriptips, ...getHoverText(script.variables, 'var', path)];
+            scriptips = [...scriptips, ...getHoverText(script.constants, 'const', path)];
+            scriptips = [...scriptips, ...getHoverText(script.functions, 'func', path)];
+            scriptips = [...scriptips, ...getHoverText(script.signals, 'signal', path)];
+            scriptips = [...scriptips, ...getHoverText(script.classes, 'class', path)];
+            tips = [...tips, ...scriptips];
+        }
+        // check from scnes
+        for (let scnenepath of Object.keys(config.nodeInfoMap)) {
+            const nodes: any[] = config.nodeInfoMap[scnenepath];
+            for (let index = 0; index < nodes.length; index++) {
+                const node:any = nodes[index];
+                const fullpath = node.parent + "/" + node.name;
+                if(fullpath == hoverText || fullpath.endsWith(hoverText)) {
+                    let filepath = scnenepath;
+                    if(workspace && workspace.rootPath)
+                        filepath = path.join(workspace.rootPath, filepath);
+                    let instance = "";
+                    if(node.instance && node.instance.length > 1) {
+                        let instancepath = node.instance;
+                        if(workspace && workspace.rootPath)
+                            instancepath = path.join(workspace.rootPath, instancepath);
+                        instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
+                    }
+                    tips = [...tips, 
+                        {language: 'gdscript', value: `${node.type} ${fullpath}`},
+                        `${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`
+                    ];
+                    break;
+                }
+            }
+        }
+
+        // check from builtin
+        const item2MarkdStrings = (name: string,item: CompletionItem):MarkedString[] => {
+            let value = "";
+            let doc = item.documentation;
+            switch (item.kind) {
+                case CompletionItemKind.Class:
+                    value += name;
+                    break;
+                case CompletionItemKind.Method:
+                    value += item.documentation.substring(0, item.documentation.indexOf("\n"));
+                    doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
+                    break;
+                case CompletionItemKind.Interface:
+                    value += "signal " + item.documentation.substring(0, item.documentation.indexOf("\n"));
+                    doc = item.documentation.substring(item.documentation.indexOf("\n")+1, item.documentation.length);
+                    break;
+                case CompletionItemKind.Variable:
+                case CompletionItemKind.Property:
+                    value += "var " + name;
+                    break;
+                case CompletionItemKind.Enum:
+                    value += "const " + name;
+                    break;
+                default:
+                    break;
+            }
+            return [{language: 'gdscript', value}, doc];
+        };
+        for (let name of Object.keys(config.builtinSymbolInfoMap)) {
+            const pattern = `[A-z@_]+[A-z0-9_]*\\.${hoverText}\\b`;
+            if(name == hoverText || name.match(new RegExp(pattern))) {
+                const item: CompletionItem = config.builtinSymbolInfoMap[name];
+                tips = [...tips, ...(item2MarkdStrings(name, item))];
+            }
+        }
+
+        if (tips.length > 0)
+            return new Hover(tips);
+        else
+            return null;
+    }
+}
+
+export default GDScriptHoverProvider;

+ 31 - 0
src/gdscript/utils.ts

@@ -0,0 +1,31 @@
+import {TextDocument, Position} from 'vscode';
+
+export function isStr(content:string) {
+    return (content.startsWith("'") || content.startsWith('"') || content.startsWith('@"') ) && (content.endsWith("'") || content.endsWith('"'));
+}
+
+export function getSelectedContent(document: TextDocument, position: Position):string {
+    const line = document.lineAt(position);
+    const wordRange = document.getWordRangeAtPosition(position) ;
+    const machs = line.text.match(/[A-z_]+[A-z_0-9]*|".*?"|'.*?'|@".*?"/g)
+    let res = line.text.substring(wordRange.start.character, wordRange.end.character);
+    machs.map(m=>{
+        if(m) {
+            const startPos = line.text.indexOf(m);
+            const endPos = startPos + m.length;
+            if(isStr(m) && startPos != -1 && wordRange.start.character >= startPos && wordRange.end.character <= endPos){
+                res = m;
+                return;
+            }
+        }
+    });
+    return res;
+};
+
+export function getStrContent(rawstr: string):string {
+    let ss = rawstr;
+    if(isStr(ss)) {
+        ss = ss.replace(/"|'|@"|"""/g,"")
+    }
+    return ss;
+}

+ 4 - 0
src/tool_manager.ts

@@ -4,6 +4,8 @@ import GDScriptSymbolProvider from './gdscript/symbolprovider';
 import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provider';
 import GDScriptCompletionItemProvider from './gdscript/completion';
 import GDScriptDefinitionProivder from './gdscript/definitionprovider';
+import GDScriptHoverProvider from './gdscript/hoverprovider';
+
 var glob = require("glob")
 import config from './config';
 import * as path from 'path';
@@ -37,6 +39,8 @@ class ToolManager {
     vscode.languages.registerWorkspaceSymbolProvider(this.workspacesymbolprovider);
     // definition provider
     vscode.languages.registerDefinitionProvider('gdscript', new GDScriptDefinitionProivder());
+    // hover provider
+    vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider());
     // code completion provider
     vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'");
     // Commands