فهرست منبع

implement documentation preview in new window

Geequlim 8 سال پیش
والد
کامیت
60d93b09d0
5فایلهای تغییر یافته به همراه303 افزوده شده و 25 حذف شده
  1. 15 6
      src/config.ts
  2. 5 1
      src/gdscript/definitionprovider.ts
  3. 237 0
      src/gdscript/docprovider.ts
  4. 42 16
      src/gdscript/hoverprovider.ts
  5. 4 2
      src/tool_manager.ts

+ 15 - 6
src/config.ts

@@ -18,7 +18,7 @@ class Config {
   public scriptSceneMap: Object;
   // scenepath : NodeInfo[]
   public nodeInfoMap: Object;
-  // symbolname: CompletionItem
+  // symbolname: {completionItem: CompletionItem, rowDoc: docdata}
   public builtinSymbolInfoMap: Object;
 
   constructor() {
@@ -87,7 +87,7 @@ class Config {
         item.detail = 'Native Class';
         item.documentation = classdoc.brief_description + " \n\n" +classdoc.description;
         bintinSybmolInfoList.push(item);
-        builtinSymbolInfoMap[classdoc.name] = item;
+        builtinSymbolInfoMap[classdoc.name] = {completionItem: item, rowDoc: classdoc};
         // methods
         const methods = classdoc.methods
         const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name+"()")=>{
@@ -105,7 +105,7 @@ class Config {
           mdoc += m.description;
           mi.documentation = mdoc;
           bintinSybmolInfoList.push(mi);
-          builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = mi;
+          builtinSymbolInfoMap[`${classdoc.name}.${m.name}`] = {completionItem: mi, rowDoc: m};
         };
         methods.map(m=>parsMethod(m, CompletionItemKind.Method));
         // signals
@@ -118,7 +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;
+          builtinSymbolInfoMap[`${classdoc.name}.${c.name}`] = {completionItem: ci, rowDoc: c};
         });
         // properties
         const properties = classdoc.properties;
@@ -127,7 +127,7 @@ class Config {
           pi.detail = `${p.type} of ${classdoc.name}`;
           pi.documentation = p.description;
           bintinSybmolInfoList.push(pi);
-          builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = pi;
+          builtinSymbolInfoMap[`${classdoc.name}.${p.name}`] = {completionItem: pi, rowDoc: p};
         };
         properties.map(p=>parseProp(p));
         // theme_properties
@@ -247,6 +247,15 @@ class Config {
     return this.classes[name];
   }
 
+  getBuiltinClassNameList() {
+    let namelist = null;
+    if(this.classes)
+      namelist = Object.keys(this.classes);
+    if(!namelist)
+      namelist = [];
+    return namelist;
+  }
+
 };
 
-export default new Config();
+export default new Config();

+ 5 - 1
src/gdscript/definitionprovider.ts

@@ -55,6 +55,10 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
                     locations = [...locations, ...scriptitems];
                 }
                 // check from builtin
+                if(config.getClass(content) != null) {
+                    const uri = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${content}`)));
+                    locations.push(new Location(Uri.parse(uri), new Range(0,0,0,0)));
+                }
                 return locations;
             }
         };
@@ -74,4 +78,4 @@ class GDScriptDefinitionProivder implements DefinitionProvider {
     }
 }
 
-export default GDScriptDefinitionProivder;
+export default GDScriptDefinitionProivder;

+ 237 - 0
src/gdscript/docprovider.ts

@@ -0,0 +1,237 @@
+import {TextDocumentContentProvider, DocumentLinkProvider, Uri, CancellationToken } from 'vscode';
+import config from '../config';
+
+function genLink(title:string, uri:string, span=true):string {
+    const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
+    let link = `<a href="${u}">${title}</a>`;
+    if(span)
+        link = `<span>${link}</span>`;
+    return link;
+};
+
+function getProp(rawDoc:any, propname: string, action=(s :string)=>s): string {
+    let prop = rawDoc[propname];
+    if(prop && prop.length > 0)
+        prop =  action(prop);
+    return prop;
+}
+
+class GDScriptDocumentContentProvider implements TextDocumentContentProvider{
+    constructor() {
+    }
+
+    /**
+     * Provide textual content for a given uri.
+     *
+     * The editor will use the returned string-content to create a readonly
+     * [document](TextDocument). Resources allocated should be released when
+     * the corresponding document has been [closed](#workspace.onDidCloseTextDocument).
+     *
+     * @param uri An uri which scheme matches the scheme this provider was [registered](#workspace.registerTextDocumentContentProvider) for.
+     * @param token A cancellation token.
+     * @return A string or a thenable that resolves to such.
+     */
+    provideTextDocumentContent(uri: Uri, token: CancellationToken): string | Thenable<string> {
+        const request = uri.authority;
+        let classname = request;
+        let membername = null;
+       
+        if(request.indexOf(".") != -1) {
+            classname = request.substring(0, request.indexOf("."));
+            if(!request.endsWith("."))
+                membername = request.substring(request.indexOf(".")+1, request.length);
+        }
+        if(classname.length >= 1) {
+            for(let key of config.getBuiltinClassNameList()) {
+                if(key.toLowerCase() == classname) {
+                    classname = key;
+                    break;
+                }
+            }
+        }
+
+        console.log(classname, membername);
+        if(classname && classname.length > 0) {
+            if(membername && membername.length >0 )
+                return this.genMemberDoc(classname, membername);
+            else
+                return this.genClassDoc(config.getClass(classname));
+        }
+        return null;
+    }
+
+    genMethodDoc(mDoc:any):string {
+        let ret_type = getProp(mDoc, "return_type", (type:string):string =>{
+            return `${genLink(type,type)} `;
+        });
+        let args = "";
+        for(let arg of mDoc.arguments){
+            if(mDoc.arguments.indexOf(arg)!=0)
+                args += ", ";
+            args += `${genLink(arg.type, arg.type)} ${arg.name}`
+            if(arg.default_value && arg.default_value.length > 0)
+                args += `=${arg.default_value}`;
+        }
+        let doc = `
+            <li>
+                <h4>${ret_type} ${mDoc.name} (${args}) <i>${mDoc.qualifiers}</i></h4>
+                <p>${mDoc.description}</p>
+            </li>
+        `;
+        return doc;
+    }
+
+    genPropDoc(pDoc:any): string {
+        let doc = `
+            <li>
+                <h4>${genLink(pDoc.type,pDoc.type)} ${pDoc.name}</h4>
+                <p>${pDoc.description}</p>
+            </li>
+        `;
+        return doc;
+    }
+
+    genConstDoc(cDoc:any): string {
+        let doc = `
+            <li>
+                <h4>${cDoc.name} = ${cDoc.value}</h4>
+                <p>${cDoc.description}</p>
+            </li>
+        `;
+        return doc;
+    }
+
+    genMemberDoc(classname, membername): string {
+        let realDoc = null;
+        const classdoc = config.getClass(classname);
+        if(!classdoc)
+            return null;
+        for(let m of classdoc.methods) {
+            if(m.name.toLowerCase() == membername) {
+                realDoc = this.genMethodDoc(m);
+                break;
+            }
+        }
+        if(!realDoc) {
+            for(let s of classdoc.signals) {
+                if(s.name.toLowerCase() == membername) {
+                    realDoc = this.genMethodDoc(s);
+                    break;
+                }
+            }
+        }
+
+        if(!realDoc) {
+            for(let c of classdoc.constants) {
+                if(c.name.toLowerCase() == membername) {
+                    realDoc = this.genConstDoc(c);
+                    break;
+                }
+            }
+        }
+
+        if(!realDoc) {
+            for(let p of classdoc.properties) {
+                if(p.name.toLowerCase() == membername) {
+                    realDoc = this.genPropDoc(p);
+                    break;
+                }
+            }
+        }
+
+        if(!realDoc) {
+            for(let p of classdoc.theme_properties) {
+                if(p.name.toLowerCase() == membername) {
+                    realDoc = this.genPropDoc(p);
+                    break;
+                }
+            }
+        }
+        
+        if(!realDoc)
+            return null;
+        
+        let doc = `
+            <h2>Documentation of ${genLink(classname, classname)}.${membername}</h2>
+            <ul>${realDoc}</ul>
+        `;
+        return doc;
+    }
+
+    genClassDoc(rawDoc): string {
+        if(!rawDoc)
+            return null;
+        const classname = rawDoc.name;
+        let inherits = getProp(rawDoc, "inherits", (inherits:string)=>{
+            return "<h4>Inherits: " + genLink(inherits, inherits, true) +"</h4>";
+        });
+
+        let category = getProp(rawDoc, "category", (c:string)=>{
+            return "<h4>Category: " + c +"</h4>";
+        });
+
+        let subclasses = "";
+        for(let key of config.getBuiltinClassNameList()) {
+            let c = config.getClass(key);
+            if(c && c.inherits == classname) {
+                subclasses += genLink(key, key, true) + " "
+            }
+        };
+        if(subclasses && subclasses.length > 0)
+            subclasses = "<h3>Inherited by</h3> " + "<ul><li>" + subclasses + "</li></ul>";
+        
+        let briefDescript = getProp(rawDoc, "brief_description", (dec:string)=>{
+            return "<h3>Brief Description</h3>" + "<ul><li>" + dec + "</li></ul>";
+        });
+        let descript = getProp(rawDoc, "description", (dec:string)=>{
+            return "<h3>Description</h3>" + "<ul><li>" + dec + "</li></ul>";
+        });
+
+        let methods = "";
+        for(let m of rawDoc.methods) {
+            methods += this.genMethodDoc(m);
+        }
+        if(methods.length >0 )
+            methods = `<h3>Methods</h3><ul>${methods}</ul/>`;
+        
+        let signals = "";
+        for(let s of rawDoc.signals) {
+            signals += this.genMethodDoc(s);
+        }
+        if(signals.length >0 )
+            signals = `<h3>Signals</h3><ul>${signals}</ul/>`;
+
+        let props = "";
+        for(let p of rawDoc.properties) {
+            props += this.genPropDoc(p)
+        }
+        for(let p of rawDoc.theme_properties) {
+            props += this.genPropDoc(p)
+        }
+        if(props.length >0 )
+            props = `<h3>Properties</h3><ul>${props}</ul>`
+
+        let constants = "";
+        for(let c of rawDoc.constants) {
+            constants += this.genConstDoc(c);
+        }
+        if(constants.length >0 )
+            constants = `<h3>Constants</h3><ul>${constants}</ul>`
+        
+        let doc = `
+            <h1>Native Class ${classname}</h1>
+            <p>${category}</p>
+            <p>${inherits}</p>
+            <p>${subclasses}</p>
+            <p>${briefDescript}</p>
+            <p>${descript}</p>
+            <p>${methods}</p>
+            <p>${signals}</p>
+            <p>${constants}</p>
+            <p>${props}</p>
+        `;
+        return doc;
+    }
+}
+
+export default GDScriptDocumentContentProvider;

+ 42 - 16
src/gdscript/hoverprovider.ts

@@ -18,6 +18,12 @@ import {
 import config from '../config';
 import * as path from 'path';
 
+
+function genLink(title:string, uri:string):string {
+    const u = encodeURI('command:vscode.previewHtml?' + JSON.stringify(Uri.parse(`godotdoc://${uri}`)));
+    return `[${title}](${u})`;
+};
+
 class GDScriptHoverProvider implements HoverProvider {
     constructor() {}
 
@@ -70,7 +76,7 @@ class GDScriptHoverProvider implements HoverProvider {
                         instance = ` which is an instance of *[${node.instance}](${Uri.file(instancepath).toString()})*`;
                     }
                     tips = [...tips, 
-                        {language: 'gdscript', value: `${node.type} ${fullpath}`},
+                        `${genLink(node.type, node.type)} ${fullpath}`,
                         `${node.type} defined in *[${scnenepath}](${Uri.file(filepath).toString()})*${instance}`
                     ];
                     break;
@@ -79,38 +85,58 @@ class GDScriptHoverProvider implements HoverProvider {
         }
 
         // check from builtin
-        const item2MarkdStrings = (name: string,item: CompletionItem):MarkedString[] => {
+        const item2MarkdStrings = (name: string,item: CompletionItem, rowDoc: any):MarkedString[] => {
             let value = "";
             let doc = item.documentation;
+            // get class name
+            let classname = name;
+            let matchs = name.match(/[@A-z][A-z0-9]*\./);
+            if(matchs) {
+                classname = matchs[0];
+                if(classname.endsWith("."))
+                    classname = classname.substring(0, classname.length -1);
+            }
+
+            const genMethodMarkDown = ():string =>{
+                let content = `${genLink(rowDoc.return_type, rowDoc.return_type)} `;
+                let matchs = name.match(/[@A-z][A-z0-9]*\./);
+                content += `${genLink(classname, classname)}.`;
+                let args = "";
+                for(let arg of rowDoc.arguments){
+                    if(rowDoc.arguments.indexOf(arg)!=0)
+                        args += ", ";
+                    args += `${genLink(arg.type, arg.type)} ${arg.name}`
+                    if(arg.default_value && arg.default_value.length > 0)
+                        args += `=${arg.default_value}`;
+                }
+                content += `${genLink(rowDoc.name, classname+'.'+rowDoc.name)}(${args}) ${rowDoc.qualifiers}`;
+                return content;
+            };
+            
             switch (item.kind) {
                 case CompletionItemKind.Class:
-                    value += name;
-                    break;
+                    return [`Native Class ${genLink(classname, classname)}`, doc];
                 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;
+                    return [genMethodMarkDown(), doc];
                 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;
+                    return ['signal ' + genMethodMarkDown(), doc];
                 case CompletionItemKind.Variable:
                 case CompletionItemKind.Property:
-                    value += "var " + name;
-                    break;
+                    return [`${rowDoc.type} ${genLink(classname, classname)}.${genLink(rowDoc.name, classname+"."+rowDoc.name)}`, doc];
                 case CompletionItemKind.Enum:
-                    value += "const " + name;
-                    break;
+                    return [`const ${genLink(classname, classname)}.${genLink(rowDoc.name, classname+"."+rowDoc.name)} = ${rowDoc.value}`, doc];
                 default:
                     break;
             }
-            return [{language: 'gdscript', value}, doc];
+            return [name, 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))];
+                const item: {completionItem: CompletionItem, rowDoc: any} = config.builtinSymbolInfoMap[name];
+                tips = [...tips, ...(item2MarkdStrings(name, item.completionItem, item.rowDoc))];
             }
         }
 
@@ -121,4 +147,4 @@ class GDScriptHoverProvider implements HoverProvider {
     }
 }
 
-export default GDScriptHoverProvider;
+export default GDScriptHoverProvider;

+ 4 - 2
src/tool_manager.ts

@@ -5,6 +5,7 @@ import GDScriptWorkspaceSymbolProvider from './gdscript/workspace_symbol_provide
 import GDScriptCompletionItemProvider from './gdscript/completion';
 import GDScriptDefinitionProivder from './gdscript/definitionprovider';
 import GDScriptHoverProvider from './gdscript/hoverprovider';
+import GDScriptDocumentContentProvider from './gdscript/docprovider';
 
 var glob = require("glob")
 import config from './config';
@@ -23,7 +24,8 @@ class ToolManager {
   constructor(context: vscode.ExtensionContext) {
     this._context = context;
     this.workspaceDir = vscode.workspace.rootPath;
-    if(this.workspaceDir) {
+    if(vscode.workspace && this.workspaceDir) {
+      vscode.workspace.registerTextDocumentContentProvider('godotdoc', new GDScriptDocumentContentProvider());
       this.workspaceDir = this.workspaceDir.replace(/\\/g, "/");
       this.loadWorkspaceSymbols();
     }
@@ -178,4 +180,4 @@ class ToolManager {
   }
 };
 
-export default ToolManager;
+export default ToolManager;