| /*--------------------------------------------------------- |
| * Copyright (C) Microsoft Corporation. All rights reserved. |
| * Licensed under the MIT License. See LICENSE in the project root for license information. |
| *--------------------------------------------------------*/ |
| |
| 'use strict'; |
| |
| import cp = require('child_process'); |
| import path = require('path'); |
| import vscode = require('vscode'); |
| import { toolExecutionEnvironment } from './goEnv'; |
| import { getTextEditForAddImport } from './goImport'; |
| import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; |
| import { isModSupported } from './goModules'; |
| import { getImportablePackages, PackageInfo } from './goPackages'; |
| import { |
| byteOffsetAt, |
| getBinPath, |
| getCurrentGoPath, |
| getGoConfig, |
| getParametersAndReturnType, |
| goBuiltinTypes, |
| goKeywords, |
| guessPackageNameFromFile, |
| isPositionInComment, |
| isPositionInString, |
| parseFilePrelude, |
| runGodoc |
| } from './util'; |
| import { getCurrentGoWorkspaceFromGOPATH } from './utils/pathUtils'; |
| |
| function vscodeKindFromGoCodeClass(kind: string, type: string): vscode.CompletionItemKind { |
| switch (kind) { |
| case 'const': |
| return vscode.CompletionItemKind.Constant; |
| case 'package': |
| return vscode.CompletionItemKind.Module; |
| case 'type': |
| switch (type) { |
| case 'struct': |
| return vscode.CompletionItemKind.Class; |
| case 'interface': |
| return vscode.CompletionItemKind.Interface; |
| } |
| return vscode.CompletionItemKind.Struct; |
| case 'func': |
| return vscode.CompletionItemKind.Function; |
| case 'var': |
| return vscode.CompletionItemKind.Variable; |
| case 'import': |
| return vscode.CompletionItemKind.Module; |
| } |
| return vscode.CompletionItemKind.Property; // TODO@EG additional mappings needed? |
| } |
| |
| interface GoCodeSuggestion { |
| class: string; |
| package?: string; |
| name: string; |
| type: string; |
| receiver?: string; |
| } |
| |
| class ExtendedCompletionItem extends vscode.CompletionItem { |
| public package?: string; |
| public receiver?: string; |
| public fileName: string; |
| } |
| |
| const lineCommentFirstWordRegex = /^\s*\/\/\s+[\S]*$/; |
| const exportedMemberRegex = /(const|func|type|var)(\s+\(.*\))?\s+([A-Z]\w*)/; |
| const gocodeNoSupportForgbMsgKey = 'dontshowNoSupportForgb'; |
| |
| export class GoCompletionItemProvider implements vscode.CompletionItemProvider, vscode.Disposable { |
| private pkgsList = new Map<string, PackageInfo>(); |
| private killMsgShown: boolean = false; |
| private setGocodeOptions: boolean = true; |
| private isGoMod: boolean = false; |
| private globalState: vscode.Memento; |
| private previousFile: string; |
| private previousFileDir: string; |
| private gocodeFlags: string[]; |
| private excludeDocs: boolean = false; |
| |
| constructor(globalState?: vscode.Memento) { |
| this.globalState = globalState; |
| } |
| |
| public provideCompletionItems( |
| document: vscode.TextDocument, |
| position: vscode.Position, |
| token: vscode.CancellationToken |
| ): Thenable<vscode.CompletionList> { |
| return this.provideCompletionItemsInternal(document, position, token, getGoConfig(document.uri)).then( |
| (result) => { |
| if (!result) { |
| return new vscode.CompletionList([], false); |
| } |
| if (Array.isArray(result)) { |
| return new vscode.CompletionList(result, false); |
| } |
| return result; |
| } |
| ); |
| } |
| |
| public resolveCompletionItem( |
| item: vscode.CompletionItem, |
| token: vscode.CancellationToken |
| ): vscode.ProviderResult<vscode.CompletionItem> { |
| if ( |
| !(item instanceof ExtendedCompletionItem) || |
| item.kind === vscode.CompletionItemKind.Module || |
| this.excludeDocs |
| ) { |
| return; |
| } |
| |
| if (typeof item.package === 'undefined') { |
| promptForUpdatingTool('gocode'); |
| return; |
| } |
| |
| return runGodoc( |
| path.dirname(item.fileName), |
| item.package || path.dirname(item.fileName), |
| item.receiver, |
| item.label, |
| token |
| ) |
| .then((doc) => { |
| item.documentation = new vscode.MarkdownString(doc); |
| return item; |
| }) |
| .catch((err) => { |
| console.log(err); |
| return item; |
| }); |
| } |
| |
| public async provideCompletionItemsInternal( |
| document: vscode.TextDocument, |
| position: vscode.Position, |
| token: vscode.CancellationToken, |
| config: vscode.WorkspaceConfiguration |
| ): Promise<vscode.CompletionItem[] | vscode.CompletionList> { |
| // Completions for the package statement based on the file name |
| const pkgStatementCompletions = await getPackageStatementCompletions(document); |
| if (pkgStatementCompletions && pkgStatementCompletions.length) { |
| return pkgStatementCompletions; |
| } |
| |
| this.excludeDocs = false; |
| this.gocodeFlags = ['-f=json']; |
| if (Array.isArray(config['gocodeFlags'])) { |
| this.gocodeFlags.push(...config['gocodeFlags']); |
| } |
| |
| return this.ensureGoCodeConfigured(document.uri, config).then(() => { |
| return new Promise<vscode.CompletionItem[] | vscode.CompletionList>((resolve, reject) => { |
| const filename = document.fileName; |
| const lineText = document.lineAt(position.line).text; |
| const lineTillCurrentPosition = lineText.substr(0, position.character); |
| const autocompleteUnimportedPackages = |
| config['autocompleteUnimportedPackages'] === true && !lineText.match(/^(\s)*(import|package)(\s)+/); |
| |
| // triggering completions in comments on exported members |
| const commentCompletion = getCommentCompletion(document, position); |
| if (commentCompletion) { |
| return resolve([commentCompletion]); |
| } |
| // prevent completion when typing in a line comment that doesnt start from the beginning of the line |
| if (isPositionInComment(document, position)) { |
| return resolve([]); |
| } |
| |
| const inString = isPositionInString(document, position); |
| if (!inString && lineTillCurrentPosition.endsWith('"')) { |
| return resolve([]); |
| } |
| |
| const currentWord = getCurrentWord(document, position); |
| if (currentWord.match(/^\d+$/)) { |
| return resolve([]); |
| } |
| |
| let offset = byteOffsetAt(document, position); |
| let inputText = document.getText(); |
| const includeUnimportedPkgs = autocompleteUnimportedPackages && !inString && currentWord.length > 0; |
| |
| return this.runGoCode( |
| document, |
| filename, |
| inputText, |
| offset, |
| inString, |
| position, |
| lineText, |
| currentWord, |
| includeUnimportedPkgs, |
| config |
| ).then((suggestions) => { |
| // gocode does not suggest keywords, so we have to do it |
| suggestions.push(...getKeywordCompletions(currentWord)); |
| |
| // If no suggestions and cursor is at a dot, then check if preceeding word is a package name |
| // If yes, then import the package in the inputText and run gocode again to get suggestions |
| if ((!suggestions || suggestions.length === 0) && lineTillCurrentPosition.endsWith('.')) { |
| const pkgPath = this.getPackagePathFromLine(lineTillCurrentPosition); |
| if (pkgPath.length === 1) { |
| // Now that we have the package path, import it right after the "package" statement |
| const { imports, pkg } = parseFilePrelude( |
| vscode.window.activeTextEditor.document.getText() |
| ); |
| const posToAddImport = document.offsetAt(new vscode.Position(pkg.start + 1, 0)); |
| const textToAdd = `import "${pkgPath[0]}"\n`; |
| inputText = |
| inputText.substr(0, posToAddImport) + textToAdd + inputText.substr(posToAddImport); |
| offset += textToAdd.length; |
| |
| // Now that we have the package imported in the inputText, run gocode again |
| return this.runGoCode( |
| document, |
| filename, |
| inputText, |
| offset, |
| inString, |
| position, |
| lineText, |
| currentWord, |
| false, |
| config |
| ).then((newsuggestions) => { |
| // Since the new suggestions are due to the package that we imported, |
| // add additionalTextEdits to do the same in the actual document in the editor |
| // We use additionalTextEdits instead of command so that 'useCodeSnippetsOnFunctionSuggest' |
| // feature continues to work |
| newsuggestions.forEach((item) => { |
| item.additionalTextEdits = getTextEditForAddImport(pkgPath[0]); |
| }); |
| resolve(newsuggestions); |
| }, reject); |
| } |
| if (pkgPath.length > 1) { |
| pkgPath.forEach((pkg) => { |
| const item = new vscode.CompletionItem( |
| `${lineTillCurrentPosition.replace('.', '').trim()} (${pkg})`, |
| vscode.CompletionItemKind.Module |
| ); |
| item.additionalTextEdits = getTextEditForAddImport(pkg); |
| item.insertText = ''; |
| item.detail = pkg; |
| item.command = { |
| title: 'Trigger Suggest', |
| command: 'editor.action.triggerSuggest' |
| }; |
| suggestions.push(item); |
| }); |
| resolve(new vscode.CompletionList(suggestions, true)); |
| } |
| } |
| resolve(suggestions); |
| }, reject); |
| }); |
| }); |
| } |
| |
| public dispose() { |
| const gocodeName = this.isGoMod ? 'gocode-gomod' : 'gocode'; |
| const gocode = getBinPath(gocodeName); |
| if (path.isAbsolute(gocode)) { |
| cp.spawn(gocode, ['close'], { env: toolExecutionEnvironment() }); |
| } |
| } |
| |
| private runGoCode( |
| document: vscode.TextDocument, |
| filename: string, |
| inputText: string, |
| offset: number, |
| inString: boolean, |
| position: vscode.Position, |
| lineText: string, |
| currentWord: string, |
| includeUnimportedPkgs: boolean, |
| config: vscode.WorkspaceConfiguration |
| ): Thenable<vscode.CompletionItem[]> { |
| return new Promise<vscode.CompletionItem[]>((resolve, reject) => { |
| const gocodeName = this.isGoMod ? 'gocode-gomod' : 'gocode'; |
| const gocode = getBinPath(gocodeName); |
| if (!path.isAbsolute(gocode)) { |
| promptForMissingTool(gocodeName); |
| return reject(); |
| } |
| |
| const env = toolExecutionEnvironment(); |
| let stdout = ''; |
| let stderr = ''; |
| |
| // stamblerre/gocode does not support -unimported-packages flags. |
| if (this.isGoMod) { |
| const unimportedPkgIndex = this.gocodeFlags.indexOf('-unimported-packages'); |
| if (unimportedPkgIndex >= 0) { |
| this.gocodeFlags.splice(unimportedPkgIndex, 1); |
| } |
| } |
| |
| // -exclude-docs is something we use internally and is not related to gocode |
| const excludeDocsIndex = this.gocodeFlags.indexOf('-exclude-docs'); |
| if (excludeDocsIndex >= 0) { |
| this.gocodeFlags.splice(excludeDocsIndex, 1); |
| this.excludeDocs = true; |
| } |
| |
| // Spawn `gocode` process |
| const p = cp.spawn(gocode, [...this.gocodeFlags, 'autocomplete', filename, '' + offset], { env }); |
| p.stdout.on('data', (data) => (stdout += data)); |
| p.stderr.on('data', (data) => (stderr += data)); |
| p.on('error', (err) => { |
| if (err && (<any>err).code === 'ENOENT') { |
| promptForMissingTool(gocodeName); |
| return reject(); |
| } |
| return reject(err); |
| }); |
| p.on('close', (code) => { |
| try { |
| if (code !== 0) { |
| if (stderr.indexOf(`rpc: can't find service Server.AutoComplete`) > -1 && !this.killMsgShown) { |
| vscode.window.showErrorMessage( |
| 'Auto-completion feature failed as an older gocode process is still running. Please kill the running process for gocode and try again.' |
| ); |
| this.killMsgShown = true; |
| } |
| if (stderr.startsWith('flag provided but not defined:')) { |
| promptForUpdatingTool(gocodeName); |
| } |
| return reject(); |
| } |
| const results = <[number, GoCodeSuggestion[]]>JSON.parse(stdout.toString()); |
| let suggestions: vscode.CompletionItem[] = []; |
| const packageSuggestions: string[] = []; |
| |
| const wordAtPosition = document.getWordRangeAtPosition(position); |
| let areCompletionsForPackageSymbols = false; |
| if (results && results[1]) { |
| for (const suggest of results[1]) { |
| if (inString && suggest.class !== 'import') { |
| continue; |
| } |
| const item = new ExtendedCompletionItem(suggest.name); |
| item.kind = vscodeKindFromGoCodeClass(suggest.class, suggest.type); |
| item.package = suggest.package; |
| item.receiver = suggest.receiver; |
| item.fileName = document.fileName; |
| item.detail = suggest.type; |
| if (!areCompletionsForPackageSymbols && item.package && item.package !== 'builtin') { |
| areCompletionsForPackageSymbols = true; |
| } |
| if (suggest.class === 'package') { |
| const possiblePackageImportPaths = this.getPackageImportPath(item.label); |
| if (possiblePackageImportPaths.length === 1) { |
| item.detail = possiblePackageImportPaths[0]; |
| } |
| packageSuggestions.push(suggest.name); |
| } |
| if (inString && suggest.class === 'import') { |
| item.textEdit = new vscode.TextEdit( |
| new vscode.Range( |
| position.line, |
| lineText.substring(0, position.character).lastIndexOf('"') + 1, |
| position.line, |
| position.character |
| ), |
| suggest.name |
| ); |
| } |
| if ( |
| (config['useCodeSnippetsOnFunctionSuggest'] || |
| config['useCodeSnippetsOnFunctionSuggestWithoutType']) && |
| ((suggest.class === 'func' && lineText.substr(position.character, 2) !== '()') || // Avoids met() -> method()() |
| (suggest.class === 'var' && |
| suggest.type.startsWith('func(') && |
| lineText.substr(position.character, 1) !== ')' && // Avoids snippets when typing params in a func call |
| lineText.substr(position.character, 1) !== ',')) // Avoids snippets when typing params in a func call |
| ) { |
| const { params, returnType } = getParametersAndReturnType(suggest.type.substring(4)); |
| const paramSnippets = []; |
| for (let i = 0; i < params.length; i++) { |
| let param = params[i].trim(); |
| if (param) { |
| param = param.replace('${', '\\${').replace('}', '\\}'); |
| if (config['useCodeSnippetsOnFunctionSuggestWithoutType']) { |
| if (param.includes(' ')) { |
| // Separate the variable name from the type |
| param = param.substr(0, param.indexOf(' ')); |
| } |
| } |
| paramSnippets.push('${' + (i + 1) + ':' + param + '}'); |
| } |
| } |
| item.insertText = new vscode.SnippetString( |
| suggest.name + '(' + paramSnippets.join(', ') + ')' |
| ); |
| } |
| if ( |
| config['useCodeSnippetsOnFunctionSuggest'] && |
| suggest.class === 'type' && |
| suggest.type.startsWith('func(') |
| ) { |
| const { params, returnType } = getParametersAndReturnType(suggest.type.substring(4)); |
| const paramSnippets = []; |
| for (let i = 0; i < params.length; i++) { |
| let param = params[i].trim(); |
| if (param) { |
| param = param.replace('${', '\\${').replace('}', '\\}'); |
| if (!param.includes(' ')) { |
| // If we don't have an argument name, we need to create one |
| param = 'arg' + (i + 1) + ' ' + param; |
| } |
| const arg = param.substr(0, param.indexOf(' ')); |
| paramSnippets.push( |
| '${' + |
| (i + 1) + |
| ':' + |
| arg + |
| '}' + |
| param.substr(param.indexOf(' '), param.length) |
| ); |
| } |
| } |
| item.insertText = new vscode.SnippetString( |
| suggest.name + |
| '(func(' + |
| paramSnippets.join(', ') + |
| ') {\n $' + |
| (params.length + 1) + |
| '\n})' + |
| returnType |
| ); |
| } |
| |
| if ( |
| wordAtPosition && |
| wordAtPosition.start.character === 0 && |
| suggest.class === 'type' && |
| !goBuiltinTypes.has(suggest.name) |
| ) { |
| const auxItem = new vscode.CompletionItem( |
| suggest.name + ' method', |
| vscode.CompletionItemKind.Snippet |
| ); |
| auxItem.label = 'func (*' + suggest.name + ')'; |
| auxItem.filterText = suggest.name; |
| auxItem.detail = 'Method snippet'; |
| auxItem.sortText = 'b'; |
| const prefix = 'func (' + suggest.name[0].toLowerCase() + ' *' + suggest.name + ')'; |
| const snippet = prefix + ' ${1:methodName}(${2}) ${3} {\n\t$0\n}'; |
| auxItem.insertText = new vscode.SnippetString(snippet); |
| suggestions.push(auxItem); |
| } |
| |
| // Add same sortText to all suggestions from gocode so that they appear before the unimported packages |
| item.sortText = 'a'; |
| suggestions.push(item); |
| } |
| } |
| |
| // Add importable packages matching currentword to suggestions |
| if (includeUnimportedPkgs && !this.isGoMod && !areCompletionsForPackageSymbols) { |
| suggestions = suggestions.concat( |
| getPackageCompletions(document, currentWord, this.pkgsList, packageSuggestions) |
| ); |
| } |
| |
| resolve(suggestions); |
| } catch (e) { |
| reject(e); |
| } |
| }); |
| if (p.pid) { |
| p.stdin.end(inputText); |
| } |
| }); |
| } |
| // TODO: Shouldn't lib-path also be set? |
| private ensureGoCodeConfigured(fileuri: vscode.Uri, goConfig: vscode.WorkspaceConfiguration): Thenable<void> { |
| const currentFile = fileuri.fsPath; |
| let checkModSupport = Promise.resolve(this.isGoMod); |
| if (this.previousFile !== currentFile && this.previousFileDir !== path.dirname(currentFile)) { |
| this.previousFile = currentFile; |
| this.previousFileDir = path.dirname(currentFile); |
| checkModSupport = isModSupported(fileuri).then((result) => (this.isGoMod = result)); |
| } |
| const setPkgsList = getImportablePackages(currentFile, true).then((pkgMap) => { |
| this.pkgsList = pkgMap; |
| }); |
| |
| if (!this.setGocodeOptions) { |
| return Promise.all([checkModSupport, setPkgsList]).then(() => { |
| return; |
| }); |
| } |
| |
| const setGocodeProps = new Promise<void>((resolve, reject) => { |
| const gocode = getBinPath('gocode'); |
| const env = toolExecutionEnvironment(); |
| |
| cp.execFile(gocode, ['set'], { env }, (err, stdout, stderr) => { |
| if (err && stdout.startsWith('gocode: unknown subcommand:')) { |
| if ( |
| goConfig['gocodePackageLookupMode'] === 'gb' && |
| this.globalState && |
| !this.globalState.get(gocodeNoSupportForgbMsgKey) |
| ) { |
| vscode.window |
| .showInformationMessage( |
| 'The go.gocodePackageLookupMode setting for gb will not be honored as github.com/mdempskey/gocode doesnt support it yet.', |
| `Don't show again` |
| ) |
| .then((selected) => { |
| if (selected === `Don't show again`) { |
| this.globalState.update(gocodeNoSupportForgbMsgKey, true); |
| } |
| }); |
| } |
| this.setGocodeOptions = false; |
| return resolve(); |
| } |
| |
| const existingOptions = stdout.split(/\r\n|\n/); |
| const optionsToSet: string[][] = []; |
| const setOption = () => { |
| const [name, value] = optionsToSet.pop(); |
| cp.execFile(gocode, ['set', name, value], { env }, () => { |
| if (optionsToSet.length) { |
| setOption(); |
| } else { |
| resolve(); |
| } |
| }); |
| }; |
| |
| if (existingOptions.indexOf('propose-builtins true') === -1) { |
| optionsToSet.push(['propose-builtins', 'true']); |
| } |
| if (existingOptions.indexOf(`autobuild ${goConfig['gocodeAutoBuild']}`) === -1) { |
| optionsToSet.push(['autobuild', goConfig['gocodeAutoBuild']]); |
| } |
| if (existingOptions.indexOf(`package-lookup-mode ${goConfig['gocodePackageLookupMode']}`) === -1) { |
| optionsToSet.push(['package-lookup-mode', goConfig['gocodePackageLookupMode']]); |
| } |
| if (!optionsToSet.length) { |
| return resolve(); |
| } |
| |
| setOption(); |
| }); |
| }); |
| |
| return Promise.all([setPkgsList, setGocodeProps, checkModSupport]).then(() => { |
| return; |
| }); |
| } |
| |
| // Given a line ending with dot, return the import paths of packages that match with the word preceeding the dot |
| private getPackagePathFromLine(line: string): string[] { |
| const pattern = /(\w+)\.$/g; |
| const wordmatches = pattern.exec(line); |
| if (!wordmatches) { |
| return []; |
| } |
| |
| const [_, pkgNameFromWord] = wordmatches; |
| // Word is isolated. Now check pkgsList for a match |
| return this.getPackageImportPath(pkgNameFromWord); |
| } |
| |
| /** |
| * Returns import path for given package. Since there can be multiple matches, |
| * this returns an array of matches |
| * @param input Package name |
| */ |
| private getPackageImportPath(input: string): string[] { |
| const matchingPackages: any[] = []; |
| this.pkgsList.forEach((info: PackageInfo, pkgPath: string) => { |
| if (input === info.name) { |
| matchingPackages.push(pkgPath); |
| } |
| }); |
| return matchingPackages; |
| } |
| } |
| |
| /** |
| * Provides completion item for the exported member in the next line if current line is a comment |
| * @param document The current document |
| * @param position The cursor position |
| */ |
| function getCommentCompletion(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem { |
| const lineText = document.lineAt(position.line).text; |
| const lineTillCurrentPosition = lineText.substr(0, position.character); |
| // triggering completions in comments on exported members |
| if (lineCommentFirstWordRegex.test(lineTillCurrentPosition) && position.line + 1 < document.lineCount) { |
| const nextLine = document.lineAt(position.line + 1).text.trim(); |
| const memberType = nextLine.match(exportedMemberRegex); |
| let suggestionItem: vscode.CompletionItem; |
| if (memberType && memberType.length === 4) { |
| suggestionItem = new vscode.CompletionItem(memberType[3], vscodeKindFromGoCodeClass(memberType[1], '')); |
| } |
| return suggestionItem; |
| } |
| } |
| |
| function getCurrentWord(document: vscode.TextDocument, position: vscode.Position): string { |
| // get current word |
| const wordAtPosition = document.getWordRangeAtPosition(position); |
| let currentWord = ''; |
| if (wordAtPosition && wordAtPosition.start.character < position.character) { |
| const word = document.getText(wordAtPosition); |
| currentWord = word.substr(0, position.character - wordAtPosition.start.character); |
| } |
| |
| return currentWord; |
| } |
| |
| function getKeywordCompletions(currentWord: string): vscode.CompletionItem[] { |
| if (!currentWord.length) { |
| return []; |
| } |
| const completionItems: vscode.CompletionItem[] = []; |
| goKeywords.forEach((keyword) => { |
| if (keyword.startsWith(currentWord)) { |
| completionItems.push(new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword)); |
| } |
| }); |
| return completionItems; |
| } |
| |
| /** |
| * Return importable packages that match given word as Completion Items |
| * @param document Current document |
| * @param currentWord The word at the cursor |
| * @param allPkgMap Map of all available packages and their import paths |
| * @param importedPackages List of imported packages. Used to prune imported packages out of available packages |
| */ |
| function getPackageCompletions( |
| document: vscode.TextDocument, |
| currentWord: string, |
| allPkgMap: Map<string, PackageInfo>, |
| importedPackages: string[] = [] |
| ): vscode.CompletionItem[] { |
| const cwd = path.dirname(document.fileName); |
| const goWorkSpace = getCurrentGoWorkspaceFromGOPATH(getCurrentGoPath(), cwd); |
| const workSpaceFolder = vscode.workspace.getWorkspaceFolder(document.uri); |
| const currentPkgRootPath = (workSpaceFolder ? workSpaceFolder.uri.path : cwd).slice(goWorkSpace.length + 1); |
| |
| const completionItems: any[] = []; |
| |
| allPkgMap.forEach((info: PackageInfo, pkgPath: string) => { |
| const pkgName = info.name; |
| if (pkgName.startsWith(currentWord) && importedPackages.indexOf(pkgName) === -1) { |
| const item = new vscode.CompletionItem(pkgName, vscode.CompletionItemKind.Keyword); |
| item.detail = pkgPath; |
| item.documentation = 'Imports the package'; |
| item.insertText = pkgName; |
| item.command = { |
| title: 'Import Package', |
| command: 'go.import.add', |
| arguments: [{ importPath: pkgPath, from: 'completion' }] |
| }; |
| item.kind = vscode.CompletionItemKind.Module; |
| |
| // Unimported packages should appear after the suggestions from gocode |
| const isStandardPackage = !item.detail.includes('.'); |
| item.sortText = isStandardPackage ? 'za' : pkgPath.startsWith(currentPkgRootPath) ? 'zb' : 'zc'; |
| completionItems.push(item); |
| } |
| }); |
| return completionItems; |
| } |
| |
| async function getPackageStatementCompletions(document: vscode.TextDocument): Promise<vscode.CompletionItem[]> { |
| // 'Smart Snippet' for package clause |
| const inputText = document.getText(); |
| if (inputText.match(/package\s+(\w+)/)) { |
| return []; |
| } |
| |
| const pkgNames = await guessPackageNameFromFile(document.fileName); |
| const suggestions = pkgNames.map((pkgName) => { |
| const packageItem = new vscode.CompletionItem('package ' + pkgName); |
| packageItem.kind = vscode.CompletionItemKind.Snippet; |
| packageItem.insertText = 'package ' + pkgName + '\r\n\r\n'; |
| return packageItem; |
| }); |
| return suggestions; |
| } |