| /*--------------------------------------------------------------------------------------------- |
| * Copyright (c) Microsoft Corporation. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------------------------------------------*/ |
| |
| 'use strict'; |
| |
| import { |
| CancellationToken, |
| ParameterInformation, |
| Position, |
| SignatureHelp, |
| SignatureHelpProvider, |
| SignatureInformation, |
| TextDocument, |
| WorkspaceConfiguration |
| } from 'vscode'; |
| import { definitionLocation } from './goDeclaration'; |
| import { getGoConfig, getParametersAndReturnType, isPositionInComment, isPositionInString } from './util'; |
| |
| export class GoSignatureHelpProvider implements SignatureHelpProvider { |
| constructor(private goConfig?: WorkspaceConfiguration) {} |
| |
| public async provideSignatureHelp( |
| document: TextDocument, |
| position: Position, |
| token: CancellationToken |
| ): Promise<SignatureHelp> { |
| let goConfig = this.goConfig || getGoConfig(document.uri); |
| |
| const theCall = this.walkBackwardsToBeginningOfCall(document, position); |
| if (theCall == null) { |
| return Promise.resolve(null); |
| } |
| const callerPos = this.previousTokenPosition(document, theCall.openParen); |
| // Temporary fix to fall back to godoc if guru is the set docsTool |
| if (goConfig['docsTool'] === 'guru') { |
| goConfig = Object.assign({}, goConfig, { docsTool: 'godoc' }); |
| } |
| try { |
| const res = await definitionLocation(document, callerPos, goConfig, true, token); |
| if (!res) { |
| // The definition was not found |
| return null; |
| } |
| if (res.line === callerPos.line) { |
| // This must be a function definition |
| return null; |
| } |
| let declarationText: string = (res.declarationlines || []).join(' ').trim(); |
| if (!declarationText) { |
| return null; |
| } |
| const result = new SignatureHelp(); |
| let sig: string; |
| let si: SignatureInformation; |
| if (res.toolUsed === 'godef') { |
| // declaration is of the form "Add func(a int, b int) int" |
| const nameEnd = declarationText.indexOf(' '); |
| const sigStart = nameEnd + 5; // ' func' |
| const funcName = declarationText.substring(0, nameEnd); |
| sig = declarationText.substring(sigStart); |
| si = new SignatureInformation(funcName + sig, res.doc); |
| } else if (res.toolUsed === 'gogetdoc') { |
| // declaration is of the form "func Add(a int, b int) int" |
| declarationText = declarationText.substring(5); |
| const funcNameStart = declarationText.indexOf(res.name + '('); // Find 'functionname(' to remove anything before it |
| if (funcNameStart > 0) { |
| declarationText = declarationText.substring(funcNameStart); |
| } |
| si = new SignatureInformation(declarationText, res.doc); |
| sig = declarationText.substring(res.name.length); |
| } |
| si.parameters = getParametersAndReturnType(sig).params.map( |
| (paramText) => new ParameterInformation(paramText) |
| ); |
| result.signatures = [si]; |
| result.activeSignature = 0; |
| result.activeParameter = Math.min(theCall.commas.length, si.parameters.length - 1); |
| return result; |
| } catch (e) { |
| return null; |
| } |
| } |
| |
| private previousTokenPosition(document: TextDocument, position: Position): Position { |
| while (position.character > 0) { |
| const word = document.getWordRangeAtPosition(position); |
| if (word) { |
| return word.start; |
| } |
| position = position.translate(0, -1); |
| } |
| return null; |
| } |
| |
| /** |
| * Goes through the function params' lines and gets the number of commas and the start position of the call. |
| */ |
| private walkBackwardsToBeginningOfCall( |
| document: TextDocument, |
| position: Position |
| ): { openParen: Position; commas: Position[] } | null { |
| let parenBalance = 0; |
| let maxLookupLines = 30; |
| const commas = []; |
| |
| for (let lineNr = position.line; lineNr >= 0 && maxLookupLines >= 0; lineNr--, maxLookupLines--) { |
| const line = document.lineAt(lineNr); |
| |
| // Stop processing if we're inside a comment |
| if (isPositionInComment(document, position)) { |
| return null; |
| } |
| |
| // if its current line, get the text until the position given, otherwise get the full line. |
| const [currentLine, characterPosition] = |
| lineNr === position.line |
| ? [line.text.substring(0, position.character), position.character] |
| : [line.text, line.text.length - 1]; |
| |
| for (let char = characterPosition; char >= 0; char--) { |
| switch (currentLine[char]) { |
| case '(': |
| parenBalance--; |
| if (parenBalance < 0) { |
| return { |
| openParen: new Position(lineNr, char), |
| commas |
| }; |
| } |
| break; |
| case ')': |
| parenBalance++; |
| break; |
| case ',': |
| const commaPos = new Position(lineNr, char); |
| if (parenBalance === 0 && !isPositionInString(document, commaPos)) { |
| commas.push(commaPos); |
| } |
| break; |
| } |
| } |
| } |
| return null; |
| } |
| } |