diff --git a/CHANGELOG.md b/CHANGELOG.md index a166e1d..71f8442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ Auto complete for variables in-file and all routines that have been discovered f Fix for incorrect syntax highlighting of the control statement `end` +Enhanced (and fixed) go-to definitions for functions, procedures, and methods (procedure and function) + ## 1.5.2 - 2019-08-25 Corrected the way to add syntax for IDL + ENVI tasks, ENVI style sheets, and ENVI modeler files. diff --git a/README.md b/README.md index 7180f33..a1fda0a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ For developers, see [CONTRIBUTING.md](./CONTRIBUTING.md) for notes on getting yo * Function/procedure/variable completion for user defined routines in workspaces and opened files -* Go-to definition for functions and procedures (not methods) +* Go-to definition for functions, procedures, and methods from user defined routines * Search for procedure/function definitions through symbols diff --git a/server/src/providers/idl-document-symbol-extractor.ts b/server/src/providers/idl-document-symbol-extractor.ts index 461cdfa..41a7b2c 100644 --- a/server/src/providers/idl-document-symbol-extractor.ts +++ b/server/src/providers/idl-document-symbol-extractor.ts @@ -261,9 +261,10 @@ export class IDLDocumentSymbolExtractor { } - getSelectedWord(line: string, position: Position): string { + getSelectedWord(line: string, position: Position): [string, boolean] { // placeholder for the name let symbolName = ""; + let functionFlag = false; // get the character position - move to the left so that we are in a word // otherwise we are outside a word as we are on the next character @@ -274,13 +275,13 @@ export class IDLDocumentSymbolExtractor { // check if zero, then just try and return the first character if (useChar === 0) { - return line.substr(0, 1).trim(); + return [line.substr(0, 1).trim(), functionFlag]; } } // split by words to extract our symbol that we may have clicked on // TODO: add logic for objects and methods here, func/pro are good for now - const wordRegEx = /[a-z_][a-z0-9:_$]*/gim; + const wordRegEx = /[a-z_][\.a-z0-9:_$\-\>]*/gim; let m: RegExpExecArray; while ((m = wordRegEx.exec(line)) !== null) { // This is necessary to avoid infinite loops with zero-width matches @@ -297,6 +298,7 @@ export class IDLDocumentSymbolExtractor { idx + m[i].length > useChar ) { symbolName = m[i]; + functionFlag = line.substr(idx + m[i].length, 1) === '(' break; } } @@ -306,7 +308,7 @@ export class IDLDocumentSymbolExtractor { } } - return symbolName; + return [symbolName, functionFlag]; } diff --git a/server/src/providers/idl-document-symbol-manager.ts b/server/src/providers/idl-document-symbol-manager.ts index d4c41f9..1f35df4 100644 --- a/server/src/providers/idl-document-symbol-manager.ts +++ b/server/src/providers/idl-document-symbol-manager.ts @@ -69,8 +69,9 @@ export class IDLDocumentSymbolManager { extractor: IDLDocumentSymbolExtractor; // Track constants by file and routines by all files we have opened - constantLookup: { [key: string]: CompletionItem[] } = {}; - routineLookup: { [key: string]: CompletionItem } = {}; + constantCompletionLookup: { [key: string]: CompletionItem[] } = {}; + routineCompletionLookup: { [key: string]: CompletionItem } = {}; + routineSymbolLookup: { [key: string]: DocumentSymbol } = {}; constructor(connection: Connection, documents: TextDocuments) { this.connection = connection; @@ -114,16 +115,16 @@ export class IDLDocumentSymbolManager { let items: CompletionItem[] = []; // get constants for our file - if (uri in this.constantLookup) { - items = this.constantLookup[uri]; + if (uri in this.constantCompletionLookup) { + items = this.constantCompletionLookup[uri]; } // merge with all of our symbols if we have user routines - if (Object.keys(this.routineLookup).length > 0) { + if (Object.keys(this.routineCompletionLookup).length > 0) { if (items.length === 0) { - items = Object.values(this.routineLookup); + items = Object.values(this.routineCompletionLookup); } else { - items = items.concat(Object.values(this.routineLookup)); + items = items.concat(Object.values(this.routineCompletionLookup)); } } @@ -163,7 +164,7 @@ export class IDLDocumentSymbolManager { // return items; } - getSelectedSymbolName(params: TextDocumentPositionParams): string { + getSelectedSymbolName(params: TextDocumentPositionParams): [string, boolean] { // read the strings from our text document const line = this._getStrings(params.textDocument.uri).split("\n")[ params.position.line @@ -176,7 +177,20 @@ export class IDLDocumentSymbolManager { // search for symbols by line searchByLine(params: TextDocumentPositionParams, limit = true): Definition { // get the highlighted symbol name - const symbolName = this.getSelectedSymbolName(params); + const res = this.getSelectedSymbolName(params); + let symbolName = res[0].toLowerCase(); + const functionFlag = res[1]; + + // check if we need to clean up the name + switch (true) { + case symbolName.includes('.'): + symbolName = '::' + symbolName.split('.')[1] + break; + case symbolName.includes('->'): + symbolName = '::' + symbolName.split('->')[1] + break; + default:// do nothing + } // create a placeholder to return let placeholder: Definition = null; @@ -189,8 +203,24 @@ export class IDLDocumentSymbolManager { if (symbols.length > 0) { // are we limiting results and being strict, or loosey goosey? if (limit) { - if (symbols[0].name.toLowerCase() === symbolName.toLowerCase()) { - placeholder = symbols[0].location; + switch (true) { + // function method + case symbolName.includes('::') && symbols[0].name.toLowerCase().endsWith(symbolName + '()') && functionFlag: + placeholder = symbols[0].location; + break; + // procedure method + case symbolName.includes('::') && symbols[0].name.toLowerCase().endsWith(symbolName): + placeholder = symbols[0].location; + break; + // function + case symbols[0].name.toLowerCase() === symbolName + '()' && functionFlag: + placeholder = symbols[0].location; + break; + // procedure + case symbols[0].name.toLowerCase() === symbolName: + placeholder = symbols[0].location; + break; + default: // do nothing } } else { placeholder = symbols[0].location; @@ -203,7 +233,7 @@ export class IDLDocumentSymbolManager { // when we remove a document, clean up the symbol lookup information private _removeSymbols(uri: string, symbols: DocumentSymbol[]) { // clear constant lookup - delete this.constantLookup[uri] + delete this.constantCompletionLookup[uri] // process each symbol symbols.forEach(symbol => { @@ -211,11 +241,14 @@ export class IDLDocumentSymbolManager { const key = symbol.name.toLowerCase(); // clean up routine lookup for functions and procedures - if (key in this.routineLookup) { - delete this.routineLookup[key] + if (key in this.routineCompletionLookup) { + delete this.routineCompletionLookup[key] + } + if (key + '(' in this.routineCompletionLookup) { + delete this.routineCompletionLookup[key + '('] } - if (key + '(' in this.routineLookup) { - delete this.routineLookup[key + '('] + if (key in this.routineSymbolLookup) { + delete this.routineSymbolLookup[key] } // make sure we have symbols to clean up @@ -344,26 +377,30 @@ export class IDLDocumentSymbolManager { // process all of the symbols that we found foundSymbols.forEach(symbol => { - // make our symbol lookup information - const info: ISymbolLookup = { - uri: uri, - symbol: symbol - }; - - // get the key - const key = symbol.name.toLowerCase(); - - // save in our lookup table - if (this.symbols[key]) { - this.symbols[key].push(info); - } else { - this.symbols[key] = [info]; - this.symbolKeys.push(key); - this.symbolKeysSearch.push(fuzzysort.prepare(key)); - } - // build the completion item for our symbol if it is not a constant if (symbol.kind !== SymbolKind.Variable) { + // make our symbol lookup information + const info: ISymbolLookup = { + uri: uri, + symbol: symbol + }; + + // get the key + const key = symbol.name.toLowerCase(); + + // save in our lookup table + if (this.symbols[key]) { + this.symbols[key].push(info); + } else { + this.symbols[key] = [info]; + this.symbolKeys.push(key); + this.symbolKeysSearch.push(fuzzysort.prepare(key)); + } + + // save our routine symbol lookup + this.routineSymbolLookup[key] = symbol; + + // save compeltion information const completionItem: CompletionItem = { label: symbol.name, kind: resolveCompletionItemKind(symbol.kind) @@ -390,14 +427,14 @@ export class IDLDocumentSymbolManager { completionItem.insertText = replaceName; } - // save in our routine lookip - this.routineLookup[symbol.name.toLowerCase()] = completionItem; + // save in our routine lookup + this.routineCompletionLookup[key] = completionItem; } }); // save any constants that we may have found, but only if we have one by that name const constantNames = []; - this.constantLookup[uri] = foundSymbols.filter(symbol => { + this.constantCompletionLookup[uri] = foundSymbols.filter(symbol => { let flag = false; if (symbol.kind === SymbolKind.Variable) { const saveName = symbol.name.toLowerCase(); diff --git a/server/src/server.ts b/server/src/server.ts index ffe319d..433fd83 100755 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -214,7 +214,7 @@ connection.onCompletion( // get the word that we are trying to complete // do this here just so we dont have to split larger files more than once // because we need the strings, split, and regex to find our work - const query = symbolProvider.getSelectedSymbolName(_textDocumentPosition) + const query = symbolProvider.getSelectedSymbolName(_textDocumentPosition)[0] // get docs matches // const start1: any = new Date(); @@ -253,7 +253,7 @@ connection.onWorkspaceSymbol( // handle when we want the definition of a symbol connection.onDefinition( (params: TextDocumentPositionParams): Definition => { - const res = symbolProvider.searchByLine(params); + const res = symbolProvider.searchByLine(params, true); return res; } ); @@ -272,7 +272,7 @@ connection.onDocumentSymbol( ): Promise => { return (await symbolProvider.get.documentSymbols( params.textDocument.uri - )).filter(symbol => { return symbol.kind !== SymbolKind.Constant }).map(symbol => { + )).filter(symbol => { return symbol.kind !== SymbolKind.Variable }).map(symbol => { return { name: symbol.displayName, detail: symbol.detail,