mirror of
https://github.com/godotengine/godot-vscode-plugin.git
synced 2026-01-04 10:09:58 +03:00
implement documentation preview in new window
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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
237
src/gdscript/docprovider.ts
Normal 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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user