| /*--------------------------------------------------------- |
| * Copyright (C) Microsoft Corporation. All rights reserved. |
| * Licensed under the MIT License. See License.txt in the project root for license information. |
| *--------------------------------------------------------*/ |
| |
| 'use strict'; |
| |
| import cp = require('child_process'); |
| import path = require('path'); |
| import vscode = require('vscode'); |
| import { getTextEditForAddImport } from './goImport'; |
| import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools'; |
| import { isModSupported } from './goModules'; |
| import { getImportablePackages, PackageInfo } from './goPackages'; |
| import { getCurrentGoWorkspaceFromGOPATH } from './goPath'; |
| import { byteOffsetAt, getBinPath, getCurrentGoPath, getGoConfig, getParametersAndReturnType, getToolsEnvVars, goBuiltinTypes, goKeywords, guessPackageNameFromFile, isPositionInComment, isPositionInString, parseFilePrelude, runGodoc } from './util'; |
| |
| 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 { |
| package?: string; |
| receiver?: string; |
| 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: getToolsEnvVars() }); |
| } |
| } |
| |
| 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 = getToolsEnvVars(); |
| let stdout = ''; |
| let stderr = ''; |
| |
| // stamblerre/gocode does not support -unimported-packages flags. |
| if (this.isGoMod) { |
| const idx = this.gocodeFlags.indexOf('-unimported-packages'); |
| if (idx >= 0) { |
| this.gocodeFlags.splice(idx, 1); |
| } |
| } |
| |
| // -exclude-docs is something we use internally and is not related to gocode |
| const idx = this.gocodeFlags.indexOf('-exclude-docs'); |
| if (idx >= 0) { |
| this.gocodeFlags.splice(idx, 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 = getToolsEnvVars(); |
| |
| 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 }, (err, stdout, stderr) => { |
| 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; |
| } |
| |
| export async function getCompletionsWithoutGoCode(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.CompletionItem[]> { |
| // Completions for the package statement based on the file name |
| const pkgStatementCompletions = await getPackageStatementCompletions(document); |
| if (pkgStatementCompletions && pkgStatementCompletions.length) { |
| return pkgStatementCompletions; |
| } |
| |
| const lineText = document.lineAt(position.line).text; |
| const config = getGoConfig(document.uri); |
| const autocompleteUnimportedPackages = config['autocompleteUnimportedPackages'] === true && !lineText.match(/^(\s)*(import|package)(\s)+/); |
| |
| const commentCompletion = getCommentCompletion(document, position); |
| if (commentCompletion) { |
| return [commentCompletion]; |
| } |
| |
| if (isPositionInComment(document, position)) { |
| return []; |
| } |
| |
| const currentWord = getCurrentWord(document, position); |
| if (!currentWord.length) { |
| return []; |
| } |
| |
| // gocode does not suggest keywords, so we have to do it |
| const completionItems: any[] = getKeywordCompletions(currentWord); |
| if (!autocompleteUnimportedPackages) { |
| return completionItems; |
| } |
| |
| const isMod = await isModSupported(document.uri); |
| if (isMod) { |
| return completionItems; |
| } |
| |
| const pkgMap = await getImportablePackages(document.fileName, true); |
| const packageCompletions = getPackageCompletions(document, currentWord, pkgMap); |
| return packageCompletions.concat(completionItems); |
| |
| } |