From 60d93b09d00b58705c8b513c57434c933e80411c Mon Sep 17 00:00:00 2001 From: Geequlim Date: Mon, 2 Jan 2017 23:31:32 +0800 Subject: [PATCH] implement documentation preview in new window --- src/config.ts | 21 ++- src/gdscript/definitionprovider.ts | 6 +- src/gdscript/docprovider.ts | 237 +++++++++++++++++++++++++++++ src/gdscript/hoverprovider.ts | 58 +++++-- src/tool_manager.ts | 6 +- 5 files changed, 303 insertions(+), 25 deletions(-) create mode 100644 src/gdscript/docprovider.ts diff --git a/src/config.ts b/src/config.ts index 277410c..28fdf02 100644 --- a/src/config.ts +++ b/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(); \ No newline at end of file +export default new Config(); diff --git a/src/gdscript/definitionprovider.ts b/src/gdscript/definitionprovider.ts index 4b1e88b..df75a03 100644 --- a/src/gdscript/definitionprovider.ts +++ b/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; \ No newline at end of file +export default GDScriptDefinitionProivder; diff --git a/src/gdscript/docprovider.ts b/src/gdscript/docprovider.ts new file mode 100644 index 0000000..eff8fa4 --- /dev/null +++ b/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 = `${title}`; + if(span) + link = `${link}`; + 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 { + 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 = ` +
  • +

    ${ret_type} ${mDoc.name} (${args}) ${mDoc.qualifiers}

    +

    ${mDoc.description}

    +
  • + `; + return doc; + } + + genPropDoc(pDoc:any): string { + let doc = ` +
  • +

    ${genLink(pDoc.type,pDoc.type)} ${pDoc.name}

    +

    ${pDoc.description}

    +
  • + `; + return doc; + } + + genConstDoc(cDoc:any): string { + let doc = ` +
  • +

    ${cDoc.name} = ${cDoc.value}

    +

    ${cDoc.description}

    +
  • + `; + 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 = ` +

    Documentation of ${genLink(classname, classname)}.${membername}

    + + `; + return doc; + } + + genClassDoc(rawDoc): string { + if(!rawDoc) + return null; + const classname = rawDoc.name; + let inherits = getProp(rawDoc, "inherits", (inherits:string)=>{ + return "

    Inherits: " + genLink(inherits, inherits, true) +"

    "; + }); + + let category = getProp(rawDoc, "category", (c:string)=>{ + return "

    Category: " + c +"

    "; + }); + + 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 = "

    Inherited by

    " + ""; + + let briefDescript = getProp(rawDoc, "brief_description", (dec:string)=>{ + return "

    Brief Description

    " + ""; + }); + let descript = getProp(rawDoc, "description", (dec:string)=>{ + return "

    Description

    " + ""; + }); + + let methods = ""; + for(let m of rawDoc.methods) { + methods += this.genMethodDoc(m); + } + if(methods.length >0 ) + methods = `

    Methods

    `; + + let signals = ""; + for(let s of rawDoc.signals) { + signals += this.genMethodDoc(s); + } + if(signals.length >0 ) + signals = `

    Signals

    `; + + 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 = `

    Properties

    ` + + let constants = ""; + for(let c of rawDoc.constants) { + constants += this.genConstDoc(c); + } + if(constants.length >0 ) + constants = `

    Constants

    ` + + let doc = ` +

    Native Class ${classname}

    +

    ${category}

    +

    ${inherits}

    +

    ${subclasses}

    +

    ${briefDescript}

    +

    ${descript}

    +

    ${methods}

    +

    ${signals}

    +

    ${constants}

    +

    ${props}

    + `; + return doc; + } +} + +export default GDScriptDocumentContentProvider; diff --git a/src/gdscript/hoverprovider.ts b/src/gdscript/hoverprovider.ts index 9414b8d..ca3a171 100644 --- a/src/gdscript/hoverprovider.ts +++ b/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; \ No newline at end of file +export default GDScriptHoverProvider; diff --git a/src/tool_manager.ts b/src/tool_manager.ts index 01b35ed..0f228fa 100644 --- a/src/tool_manager.ts +++ b/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; \ No newline at end of file +export default ToolManager;