implement documentation preview in new window

This commit is contained in:
Geequlim
2017-01-02 23:31:32 +08:00
parent 971a6e8961
commit 60d93b09d0
5 changed files with 303 additions and 25 deletions

View File

@@ -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();

View File

@@ -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
src/gdscript/docprovider.ts Normal file
View File

@@ -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;

View File

@@ -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;

View File

@@ -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;