From f890468f98ffbfb35a0b1b124c6521f2bbe557af Mon Sep 17 00:00:00 2001 From: Geequlim Date: Tue, 10 Jan 2017 23:43:23 +0800 Subject: [PATCH] Add signature helper for builtin functions Add signature infomations for workspace functions in hover message --- src/config.ts | 6 +- src/gdscript/diagnostic.ts | 2 +- src/gdscript/hoverprovider.ts | 5 +- src/gdscript/signature_helper.ts | 94 ++++++++++++++++++++++++++++++++ src/gdscript/symbolparser.ts | 20 +++++-- src/gdscript/utils.ts | 10 ++++ src/tool_manager.ts | 4 +- 7 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 src/gdscript/signature_helper.ts diff --git a/src/config.ts b/src/config.ts index 28fdf02..89a18d4 100644 --- a/src/config.ts +++ b/src/config.ts @@ -20,6 +20,8 @@ class Config { public nodeInfoMap: Object; // symbolname: {completionItem: CompletionItem, rowDoc: docdata} public builtinSymbolInfoMap: Object; + // path.function: signature + public workspaceMethodSignatureMap: Object; constructor() { this.symbols = {}; @@ -90,7 +92,7 @@ class Config { builtinSymbolInfoMap[classdoc.name] = {completionItem: item, rowDoc: classdoc}; // methods const methods = classdoc.methods - const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name+"()")=>{ + const parsMethod = (m, kind: CompletionItemKind, insertAction=(name)=>name)=>{ const mi = new CompletionItem(m.name, kind); mi.insertText = insertAction(m.name) mi.filterText = m.name @@ -154,7 +156,7 @@ class Config { return _items; } items = [...items, ...addScriptItems(script.classes, CompletionItemKind.Class, "Class")]; - items = [...items, ...addScriptItems(script.functions, CompletionItemKind.Method, "Method", (t)=>`${t}()`)]; + items = [...items, ...addScriptItems(script.functions, CompletionItemKind.Method, "Method", (f)=>f+`${script.signatures[f]}`)]; items = [...items, ...addScriptItems(script.variables, CompletionItemKind.Variable, "Variable")]; items = [...items, ...addScriptItems(script.signals, CompletionItemKind.Interface, "Signal")]; items = [...items, ...addScriptItems(script.constants, CompletionItemKind.Enum, "Constant")]; diff --git a/src/gdscript/diagnostic.ts b/src/gdscript/diagnostic.ts index 75bc262..d32c62f 100644 --- a/src/gdscript/diagnostic.ts +++ b/src/gdscript/diagnostic.ts @@ -82,7 +82,7 @@ class GDScriptDiagnosticSeverity { if(semicolonIndex != -1) { diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, semicolonIndex, i, semicolonIndex+1), "Statement ends with a semicolon.", DiagnosticSeverity.Warning)); } - if(line.match(/\s*(if|elif|else|for|while|func|class)\s*/g) && line.indexOf(":") == -1) { + if(line.match(/\s*(if|elif|else|for|while|func|class)\s*$/g) && line.indexOf(":") == -1) { if(line.indexOf("#") == -1) diagnostics.push(new vscode.Diagnostic(new vscode.Range(i, 0, i, line.length), "':' expected at end of the line.", DiagnosticSeverity.Error)); } diff --git a/src/gdscript/hoverprovider.ts b/src/gdscript/hoverprovider.ts index ca3a171..88cd5ad 100644 --- a/src/gdscript/hoverprovider.ts +++ b/src/gdscript/hoverprovider.ts @@ -44,7 +44,10 @@ class GDScriptHoverProvider implements HoverProvider { let dfile = path; if (workspace && workspace.asRelativePath(dfile)) dfile = workspace.asRelativePath(dfile); - _items.push({language:'gdscript', value:`${type} ${name}`}); + let extra = ""; + if(type == "func"|| type == "signal" && script.signatures[name]) + extra = script.signatures[name]; + _items.push({language:'gdscript', value:`${type} ${name}${extra}`}); _items.push(`Defined in *[${dfile}](${Uri.file(path).toString()})*`) break; } diff --git a/src/gdscript/signature_helper.ts b/src/gdscript/signature_helper.ts new file mode 100644 index 0000000..45860ce --- /dev/null +++ b/src/gdscript/signature_helper.ts @@ -0,0 +1,94 @@ +import { + SignatureHelpProvider, + TextDocument, + Position, + CancellationToken, + SignatureInformation, + SignatureHelp, + CompletionItemKind, + ParameterInformation +} from 'vscode'; +import config from '../config'; +import { countSubStr } from './utils'; +class GDScriptSignatureHelpProvider implements SignatureHelpProvider { + constructor() {} + + /** + * Provide help for the signature at the given position and document. + * + * @param document The document in which the command was invoked. + * @param position The position at which the command was invoked. + * @param token A cancellation token. + * @return Signature help or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. + */ + provideSignatureHelp(document : TextDocument, position : Position, token : CancellationToken) : SignatureHelp | Thenable < SignatureHelp > { + const range = document.getWordRangeAtPosition(position); + let funcname = ""; + let curparam = 0; + const checkPosition = () => { + const line = document.lineAt(position); + const startPos = line.firstNonWhitespaceCharacterIndex; + const endPos = position.character; + const queryStr = line.text.substring(startPos, endPos); + + var reg = /([A-z_]+[A-z0-9_]*)\(/g; + let match = reg.exec(queryStr); + while (match != null) { + funcname = match[1]; + match = reg.exec(queryStr); + } + if(funcname != "") { + const funcrangestr = line.text.substring(line.text.indexOf(queryStr)+queryStr.indexOf(funcname)+funcname.length, endPos); + curparam = countSubStr(funcrangestr, ","); + } + + }; + + checkPosition(); + + let help: SignatureHelp = { + signatures: [], + activeSignature: 0, + activeParameter: curparam + }; + + if (funcname.length > 0) { + // Builtin functions + for (let key of Object.keys(config.builtinSymbolInfoMap)) { + if (key.endsWith(`\.${funcname}`)) { + if (config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Method || config.builtinSymbolInfoMap[key].completionItem.kind == CompletionItemKind.Function) { + const rawDoc = config.builtinSymbolInfoMap[key].rowDoc; + const item = config.builtinSymbolInfoMap[key].completionItem; + let signatureInfor: SignatureInformation = new SignatureInformation(item.documentation.split('\n')[0], rawDoc.description); + for(let arg of rawDoc.arguments){ + let param: ParameterInformation = new ParameterInformation(`${arg.type} ${arg.name}${arg.default_value.length>0?'='+arg.default_value:''}`, ""); + signatureInfor.parameters.push(param); + } + help.signatures.push(signatureInfor); + } + } + } + // workspace functions + // for (let path of Object.keys(config.getAllSymbols())) { + // const script = config.getSymbols(path); + // for(let f of Object.keys(script.signatures)) { + // if(f == funcname) { + // let signatureInfor: SignatureInformation = new SignatureInformation(`func ${f}${script.signatures[f]}`, `Method defined in ${path}`); + // let param: ParameterInformation = new ParameterInformation(script.signatures[f], ""); + // signatureInfor.parameters.push(param); + // help.signatures.push(signatureInfor); + // } + // } + // } + + } + if(help.signatures.length>0) + return help; + + return null +} + +} + +export default GDScriptSignatureHelpProvider; \ No newline at end of file diff --git a/src/gdscript/symbolparser.ts b/src/gdscript/symbolparser.ts index 06c36be..bb944fe 100644 --- a/src/gdscript/symbolparser.ts +++ b/src/gdscript/symbolparser.ts @@ -8,7 +8,8 @@ interface GDScript { signals: {}, classes: {}, base: string, - native: string + native: string, + signatures: {} } class GDScriptSymbolParser { @@ -23,7 +24,8 @@ class GDScriptSymbolParser { signals: {}, classes: {}, base: "Object", - native: "Object" + native: "Object", + signatures: {} } const text = content; const lines = text.split(/\r?\n/); @@ -66,8 +68,18 @@ class GDScriptSymbolParser { let funcsnames = getMatches(text, /func\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/g, 1); const funcs = findLineRanges(funcsnames, "func\\s+$X$\\s*\\("); - for (let key of Object.keys(funcs)) - script.functions[key] = determRange(key, funcs); + for (let key of Object.keys(funcs)) { + let r: Range = determRange(key, funcs); + script.functions[key] = r; + const line = lines[r.start.line]; + if(line.indexOf("(")!= -1 && line.indexOf(")")!=-1) { + const signature = line.substring(line.indexOf("("), line.indexOf(")")+1); + if(signature && signature.length >0) { + script.signatures[key] = signature; + // console.log(key, signature); + } + } + } let signalnames = getMatches(text, /signal\s+([_A-Za-z]+[_A-Za-z0-9]*)\s*\(/g, 1); const signals = findLineRanges(signalnames, "signal\\s+$X$\\s*\\("); diff --git a/src/gdscript/utils.ts b/src/gdscript/utils.ts index 7053614..74968b8 100644 --- a/src/gdscript/utils.ts +++ b/src/gdscript/utils.ts @@ -28,4 +28,14 @@ export function getStrContent(rawstr: string):string { ss = ss.replace(/"|'|@"|"""/g,"") } return ss; +} + +export function countSubStr(str:string, sub:string): number { + let count = 0; + let pos = str.indexOf(sub); + while (pos !== -1) { + count++; + pos = str.indexOf(sub, pos + sub.length); + } + return count; } \ No newline at end of file diff --git a/src/tool_manager.ts b/src/tool_manager.ts index 0f228fa..8d108e3 100644 --- a/src/tool_manager.ts +++ b/src/tool_manager.ts @@ -6,7 +6,7 @@ import GDScriptCompletionItemProvider from './gdscript/completion'; import GDScriptDefinitionProivder from './gdscript/definitionprovider'; import GDScriptHoverProvider from './gdscript/hoverprovider'; import GDScriptDocumentContentProvider from './gdscript/docprovider'; - +import GDScriptSignatureHelpProvider from './gdscript/signature_helper'; var glob = require("glob") import config from './config'; import * as path from 'path'; @@ -45,6 +45,8 @@ class ToolManager { vscode.languages.registerHoverProvider('gdscript', new GDScriptHoverProvider()); // code completion provider vscode.languages.registerCompletionItemProvider('gdscript', new GDScriptCompletionItemProvider(), '.', '"', "'"); + // signature help provider + vscode.languages.registerSignatureHelpProvider('gdscript', new GDScriptSignatureHelpProvider(), '(', ','); // Commands this._disposable = vscode.Disposable.from( vscode.commands.registerCommand('godot.updateWorkspaceSymbols', this.loadWorkspaceSymbols.bind(this)),