diff --git a/src/config.ts b/src/config.ts index 2d8c396..277410c 100644 --- a/src/config.ts +++ b/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 diff --git a/src/gdscript/definitionprovider.ts b/src/gdscript/definitionprovider.ts index f5521fe..7feb091 100644 --- a/src/gdscript/definitionprovider.ts +++ b/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()) diff --git a/src/gdscript/hoverprovider.ts b/src/gdscript/hoverprovider.ts new file mode 100644 index 0000000..9414b8d --- /dev/null +++ b/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; \ No newline at end of file diff --git a/src/gdscript/utils.ts b/src/gdscript/utils.ts new file mode 100644 index 0000000..7053614 --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/src/tool_manager.ts b/src/tool_manager.ts index cd7c9f8..eebc1e5 100644 --- a/src/tool_manager.ts +++ b/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