This commit is contained in:
Ramya Rao 2019-03-28 15:17:01 -07:00 коммит произвёл GitHub
Родитель 9f99c306e0
Коммит 8ea6e4708d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 360 добавлений и 122 удалений

Просмотреть файл

@ -76,15 +76,34 @@ The Go extension is ready to use on the get go. If you want to customize the fea
### Go Language Server (Experimental)
The Go extension uses a host of Go tools to provide the various language features. An alternative is to use a single language server that provides the same feature.
The Go extension uses a host of [Go tools](https://github.com/Microsoft/vscode-go/wiki/Go-tools-that-the-Go-extension-depends-on) to provide the various language features. An alternative is to use a single language server that provides the same features.
Previously, we added support to use the [language server from Sourcegraph](https://github.com/sourcegraph/go-langserver). Since there is no
active development for it anymore and because it doesn't support Go modules, we are now switching to use the [language server from Google](https://github.com/golang/go/wiki/gopls).
- If you are using the language server from Sourcegraph, you can continue to use it as long as you are not using Go modules.
- Since the language server from Google provides much better support for Go modules, you will be prompted about it when the extension detects that you are working on a project that uses Go modules.
- If you have never used language server before, and now opt to use it, you will be prompted to install and use the language server from Google.
#### Settings to control the use of the Go language server
Below are the settings you can use to control the use of the language server. You need to reload the VS Code window for any changes in these settings to take effect.
- Set `go.useLanguageServer` to `true` to enable the use of language server
- Use the setting `go.languageServerExperimentalFeatures` to control which features do you want to be powered by the language server.
- Set `"go.languageServerFlags": ["-trace"]` to collect traces in the output panel.
- Set `"go.languageServerFlags": ["-trace", "-logfile", "path to a text file that exists"]` to collect traces in a log file.
#### Setting to change the language server being used
If you want to try out other language servers, for example, [bingo](https://github.com/saibing/bingo), then install it and add the below setting
```json
"go.alternateTools": {
"gopls": "bingo"
}
```
This will tell the Go extension to use `bingo` in place of `gopls`.
Set `go.useLanguageServer` to `true` to use the Go language server from [Sourcegraph](https://github.com/sourcegraph/go-langserver) for features like Hover, Definition, Find All References, Signature Help, Go to Symbol in File and Workspace.
* Since only a single language server is spun up for given VS Code instance, having multi-root setup where the folders have different GOPATH is not supported.
* If set to true, you will be prompted to install the Go language server. Once installed, you will have to reload VS Code window. The language server will then be run by the Go extension in the background to provide services needed for the above mentioned features.
* Every time you change the value of the setting `go.useLanguageServer`, you need to reload the VS Code window for it to take effect.
* To collect traces, set `"go.languageServerFlags": ["-trace"]`
* To collect errors from language server in a logfile, set `"go.languageServerFlags": ["-trace", "-logfile", "path to a text file that exists"]`
* Use the new setting `go.languageServerExperimentalFeatures` to opt-in to try new features like Code Completion and Formatting from the language server that might not be feature complete yet.
### Linter

Просмотреть файл

@ -905,7 +905,7 @@
"go.useLanguageServer": {
"type": "boolean",
"default": false,
"description": "Experimental: Use Go language server from Sourcegraph for Hover, Definition, Find All References, Signature Help, File Outline and Workspace Symbol features instead of tools like guru, godef, go-outline and go-symbol"
"description": "Experimental: Use Go language server from Google or Sourcegraph for Hover, Definition, Signature Help, Auto Completion and Formatting features instead of tools like godef, gocode and goreturns"
},
"go.languageServerFlags": {
"type": "array",
@ -964,11 +964,16 @@
"type": "boolean",
"default": false,
"description": "If true, the language server will be used for the Go to Workspace Symbols feature."
},
"diagnostics": {
"type": "boolean",
"default": false,
"description": "If true, the language server will provide build, vet errors and the extension will ignore the `buildOnSave`, `vetOnSave` settings."
}
},
"default": {
"format": false,
"autoComplete": false,
"format": true,
"autoComplete": true,
"rename": true,
"goToDefinition": true,
"hover": true,
@ -977,7 +982,8 @@
"goToImplementation": true,
"documentSymbols": true,
"workspaceSymbols": true,
"findReferences": true
"findReferences": true,
"diagnostics": false
},
"description": "Use this setting to enable/disable experimental features from the language server."
},

Просмотреть файл

@ -16,6 +16,8 @@ import { goVet } from './goVet';
import { goBuild } from './goBuild';
import { isModSupported } from './goModules';
import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
import { getLanguageServerToolPath } from './goInstallTools';
import { getToolFromToolPath } from './goPath';
const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
statusBarItem.command = 'go.test.showOutput';
@ -58,6 +60,21 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati
const runningToolsPromises = [];
const cwd = path.dirname(fileUri.fsPath);
const goRuntimePath = getBinPath('go');
const languageServerTool = getToolFromToolPath(getLanguageServerToolPath());
const languageServerOptions: any = goConfig.get('languageServerExperimentalFeatures');
let languageServerFlags: string[] = goConfig.get('languageServerFlags');
if (!Array.isArray(languageServerFlags)) {
languageServerFlags = [];
}
// If diagnostics are enabled via a language server, then we disable running build or vet to avoid duplicate errors & warnings.
let disableBuild = languageServerOptions['diagnostics'] === true && (languageServerTool === 'gopls' || languageServerTool === 'bingo');
const disableVet = languageServerOptions['diagnostics'] === true && languageServerTool === 'gopls';
// Some bingo users have disabled diagnostics using the -diagnostics-style=none flag, so respect that choice
if (disableBuild && languageServerTool === 'bingo' && languageServerFlags.indexOf('-diagnostics-style=none') > -1) {
disableBuild = false;
}
if (!goRuntimePath) {
vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately');
@ -90,7 +107,7 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati
return testPromise;
};
if (!!goConfig['buildOnSave'] && goConfig['buildOnSave'] !== 'off') {
if (!disableBuild && !!goConfig['buildOnSave'] && goConfig['buildOnSave'] !== 'off') {
runningToolsPromises.push(isModSupported(fileUri)
.then(isMod => goBuild(fileUri, isMod, goConfig, goConfig['buildOnSave'] === 'workspace'))
.then(errors => ({ diagnosticCollection: buildDiagnosticCollection, errors })));
@ -113,12 +130,12 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati
if (!!goConfig['lintOnSave'] && goConfig['lintOnSave'] !== 'off') {
runningToolsPromises.push(goLint(fileUri, goConfig, goConfig['lintOnSave'])
.then(errors => ({diagnosticCollection: lintDiagnosticCollection, errors: errors})));
.then(errors => ({ diagnosticCollection: lintDiagnosticCollection, errors: errors })));
}
if (!!goConfig['vetOnSave'] && goConfig['vetOnSave'] !== 'off') {
if (!disableVet && !!goConfig['vetOnSave'] && goConfig['vetOnSave'] !== 'off') {
runningToolsPromises.push(goVet(fileUri, goConfig, goConfig['vetOnSave'] === 'workspace')
.then(errors => ({diagnosticCollection: vetDiagnosticCollection, errors: errors})));
.then(errors => ({ diagnosticCollection: vetDiagnosticCollection, errors: errors })));
}
if (!!goConfig['coverOnSave']) {

Просмотреть файл

@ -12,6 +12,7 @@ import cp = require('child_process');
import { showGoStatus, hideGoStatus, outputChannel } from './goStatus';
import { getBinPath, getToolsGopath, getGoVersion, SemVersion, isVendorSupported, getCurrentGoPath, resolvePath } from './util';
import { goLiveErrorsEnabled } from './goLiveErrors';
import { getToolFromToolPath } from './goPath';
const updatesDeclinedTools: string[] = [];
const installsDeclinedTools: string[] = [];
@ -39,6 +40,7 @@ const allToolsWithImportPaths: { [key: string]: string } = {
'golangci-lint': 'github.com/golangci/golangci-lint/cmd/golangci-lint',
'revive': 'github.com/mgechev/revive',
'go-langserver': 'github.com/sourcegraph/go-langserver',
'gopls': 'golang.org/x/tools/cmd/gopls',
'dlv': 'github.com/go-delve/delve/cmd/dlv',
'fillstruct': 'github.com/davidrjenni/reftools/cmd/fillstruct',
'godoctor': 'github.com/godoctor/godoctor',
@ -121,7 +123,8 @@ function getTools(goVersion: SemVersion): string[] {
}
if (goConfig['useLanguageServer']) {
tools.push('go-langserver');
const languageServer = getToolFromToolPath(getLanguageServerToolPath());
tools.push(languageServer === 'go-langserver' ? 'go-langserver' : 'gopls');
}
if (goLiveErrorsEnabled()) {
@ -164,7 +167,8 @@ export function installAllTools(updateExistingToolsOnly: boolean = false) {
'golangci-lint': '\t(Linter)',
'revive': '\t\t(Linter)',
'staticcheck': '\t(Linter)',
'go-langserver': '(Language Server)',
'go-langserver': '(Language Server from Sourcegraph)',
'gopls': '\t\t(Language Server from Google)',
'dlv': '\t\t\t(Debugging)',
'fillstruct': '\t\t(Fill structs with defaults)',
'godoctor': '\t\t(Extract to functions and variables)'
@ -262,7 +266,7 @@ export function promptForUpdatingTool(tool: string) {
*
* @param string[] array of tool names to be installed
*/
export function installTools(missing: string[], goVersion: SemVersion) {
export function installTools(missing: string[], goVersion: SemVersion): Promise<void> {
const goRuntimePath = getBinPath('go');
if (!goRuntimePath) {
vscode.window.showInformationMessage('Cannot find "go" binary. Update PATH or GOROOT appropriately');
@ -319,7 +323,7 @@ export function installTools(missing: string[], goVersion: SemVersion) {
outputChannel.appendLine(''); // Blank line for spacing.
missing.reduce((res: Promise<string[]>, tool: string) => {
return missing.reduce((res: Promise<string[]>, tool: string) => {
return res.then(sofar => new Promise<string[]>((resolve, reject) => {
const callback = (err: Error, stdout: string, stderr: string) => {
if (err) {
@ -388,7 +392,7 @@ export function installTools(missing: string[], goVersion: SemVersion) {
outputChannel.appendLine(''); // Blank line for spacing
const failures = res.filter(x => x != null);
if (failures.length === 0) {
if (missing.indexOf('go-langserver') > -1) {
if (missing.indexOf('go-langserver') > -1 || missing.indexOf('gopls') > -1) {
outputChannel.appendLine('Reload VS Code window to use the Go language server');
}
outputChannel.appendLine('All tools successfully installed. You\'re ready to Go :).');
@ -436,7 +440,13 @@ export function updateGoPathGoRootFromConfig(): Promise<void> {
});
}
let alreadyOfferedToInstallTools = false;
export function offerToInstallTools() {
if (alreadyOfferedToInstallTools) {
return;
}
alreadyOfferedToInstallTools = true;
isVendorSupported();
getGoVersion().then(goVersion => {
@ -449,6 +459,30 @@ export function offerToInstallTools() {
});
}
});
const usingSourceGraph = getToolFromToolPath(getLanguageServerToolPath()) === 'go-langserver';
if (usingSourceGraph) {
const promptMsg = 'The language server from Sourcegraph is no longer under active development and it does not support Go modules as well. Please install and use the language server from Google or disable the use of language servers altogether.';
const disableLabel = 'Disable language server';
const installLabel = 'Install';
vscode.window.showInformationMessage(promptMsg, installLabel, disableLabel)
.then(selected => {
if (selected === installLabel) {
installTools(['gopls'], goVersion)
.then(() => {
vscode.window.showInformationMessage('Reload VS Code window to enable the use of Go language server');
});
} else if (selected === disableLabel) {
const goConfig = vscode.workspace.getConfiguration('go');
const inspectLanguageServerSetting = goConfig.inspect('useLanguageServer');
if (inspectLanguageServerSetting.globalValue === true) {
goConfig.update('useLanguageServer', false, vscode.ConfigurationTarget.Global);
} else if (inspectLanguageServerSetting.workspaceFolderValue === true) {
goConfig.update('useLanguageServer', false, vscode.ConfigurationTarget.WorkspaceFolder);
}
}
});
}
});
@ -490,24 +524,53 @@ function getMissingTools(goVersion: SemVersion): Promise<string[]> {
});
}
// If langserver needs to be used, but is not installed, this will prompt user to install and Reload
// If langserver needs to be used, and is installed, this will return true
// Returns false in all other cases
export function checkLanguageServer(): boolean {
/**
* Gets the absolute path to the language server to be used.
* If the required tool is not available, then user is prompted to install it.
* This supports the language servers from both Google and Sourcegraph with the
* former getting a precedence over the latter
*/
export function getLanguageServerToolPath(): string | undefined {
const latestGoConfig = vscode.workspace.getConfiguration('go');
if (!latestGoConfig['useLanguageServer']) return false;
if (!latestGoConfig['useLanguageServer']) return;
if (!allFoldersHaveSameGopath()) {
vscode.window.showInformationMessage('The Go language server is not supported in a multi root set up with different GOPATHs.');
return false;
return;
}
const langServerAvailable = getBinPath('go-langserver') !== 'go-langserver';
if (!langServerAvailable) {
promptForMissingTool('go-langserver');
vscode.window.showInformationMessage('Reload VS Code window after installing the Go language server');
// Get the path to gopls or any alternative that the user might have set for gopls
const goplsBinaryPath = getBinPath('gopls');
if (path.isAbsolute(goplsBinaryPath)) {
return goplsBinaryPath;
}
return langServerAvailable;
// Get the path to go-langserver or any alternative that the user might have set for go-langserver
const golangserverBinaryPath = getBinPath('go-langserver');
if (path.isAbsolute(golangserverBinaryPath)) {
return golangserverBinaryPath;
}
// Notify the user about the unavailability of the language server
let languageServerOfChoice = 'gopls';
if (latestGoConfig['alternateTools']) {
const goplsAlternate = latestGoConfig['alternateTools']['gopls'];
const golangserverAlternate = latestGoConfig['alternateTools']['go-langserver'];
if (typeof goplsAlternate === 'string') {
languageServerOfChoice = getToolFromToolPath(goplsAlternate);
} else if (typeof golangserverAlternate === 'string') {
languageServerOfChoice = getToolFromToolPath(golangserverAlternate);
}
if (languageServerOfChoice !== 'gopls' && languageServerOfChoice !== 'go-langserver') {
vscode.window.showErrorMessage(`Cannot find the language server ${languageServerOfChoice}. Please install it and reload this VS Code window`);
return;
}
}
promptForMissingTool(languageServerOfChoice);
vscode.window.showInformationMessage('Reload VS Code window after installing the Go language server');
}
function allFoldersHaveSameGopath(): boolean {

Просмотреть файл

@ -6,7 +6,7 @@
'use strict';
import vscode = require('vscode');
import { GoCompletionItemProvider } from './goSuggest';
import { GoCompletionItemProvider, getCompletionsWithoutGoCode } from './goSuggest';
import { GoHoverProvider } from './goExtraInfo';
import { GoDefinitionProvider } from './goDeclaration';
import { GoReferenceProvider } from './goReferences';
@ -28,7 +28,7 @@ import { testAtCursor, testCurrentPackage, testCurrentFile, testPrevious, testWo
import { showTestOutput, cancelRunningTests } from './testUtils';
import * as goGenerateTests from './goGenerateTests';
import { addImport, addImportToWorkspace } from './goImport';
import { installAllTools, checkLanguageServer } from './goInstallTools';
import { installAllTools, getLanguageServerToolPath } from './goInstallTools';
import {
isGoPathSet, getBinPath, sendTelemetryEvent, getExtensionCommands, getGoVersion, getCurrentGoPath,
getToolsGopath, handleDiagnosticErrors, disposeTelemetryReporter, getToolsEnvVars, cleanupTempDir
@ -36,9 +36,9 @@ import {
import {
LanguageClient, RevealOutputChannelOn, FormattingOptions, ProvideDocumentFormattingEditsSignature,
ProvideCompletionItemsSignature, ProvideRenameEditsSignature, ProvideDefinitionSignature, ProvideHoverSignature,
ProvideReferencesSignature, ProvideSignatureHelpSignature, ProvideDocumentSymbolsSignature, ProvideWorkspaceSymbolsSignature
ProvideReferencesSignature, ProvideSignatureHelpSignature, ProvideDocumentSymbolsSignature, ProvideWorkspaceSymbolsSignature, HandleDiagnosticsSignature
} from 'vscode-languageclient';
import { clearCacheForTools, fixDriveCasingInWindows } from './goPath';
import { clearCacheForTools, fixDriveCasingInWindows, getToolFromToolPath } from './goPath';
import { addTags, removeTags } from './goModifytags';
import { runFillStruct } from './goFillStruct';
import { parseLiveFile } from './goLiveErrors';
@ -107,14 +107,16 @@ export function activate(ctx: vscode.ExtensionContext): void {
ctx.globalState.update('toolsGoInfo', toolsGoInfo);
offerToInstallTools();
if (checkLanguageServer()) {
const languageServerToolPath = getLanguageServerToolPath();
if (languageServerToolPath) {
const languageServerTool = getToolFromToolPath(languageServerToolPath);
const languageServerExperimentalFeatures: any = vscode.workspace.getConfiguration('go').get('languageServerExperimentalFeatures') || {};
const langServerFlags: string[] = vscode.workspace.getConfiguration('go')['languageServerFlags'] || [];
const c = new LanguageClient(
'go-langserver',
languageServerTool,
{
command: getBinPath('go-langserver'),
command: languageServerToolPath,
args: ['-mode=stdio', ...langServerFlags],
options: {
env: getToolsEnvVars()
@ -139,9 +141,21 @@ export function activate(ctx: vscode.ExtensionContext): void {
}
return [];
},
provideCompletionItem: (document: vscode.TextDocument, position: vscode.Position, context: vscode.CompletionContext, token: vscode.CancellationToken, next: ProvideCompletionItemsSignature) => {
provideCompletionItem: async (document: vscode.TextDocument, position: vscode.Position, context: vscode.CompletionContext, token: vscode.CancellationToken, next: ProvideCompletionItemsSignature) => {
if (languageServerExperimentalFeatures['autoComplete'] === true) {
return next(document, position, context, token);
const promiseFromLanguageServer = Promise.resolve(next(document, position, context, token));
const promiseWithoutGoCode = getCompletionsWithoutGoCode(document, position);
const [resultFromLanguageServer, resultWithoutGoCode] = await Promise.all([promiseFromLanguageServer, promiseWithoutGoCode]);
if (!resultWithoutGoCode || !resultWithoutGoCode.length) {
return resultFromLanguageServer;
}
const completionItemsFromLanguageServer = Array.isArray(resultFromLanguageServer) ? resultFromLanguageServer : resultFromLanguageServer.items;
resultWithoutGoCode.forEach(x => {
if (x.kind !== vscode.CompletionItemKind.Module || !completionItemsFromLanguageServer.some(y => y.label === x.label)) {
completionItemsFromLanguageServer.push(x);
}
});
return resultFromLanguageServer;
}
return [];
},
@ -199,6 +213,12 @@ export function activate(ctx: vscode.ExtensionContext): void {
}
return null;
},
handleDiagnostics: (uri: vscode.Uri, diagnostics: vscode.Diagnostic[], next: HandleDiagnosticsSignature) => {
if (languageServerExperimentalFeatures['diagnostics'] === true) {
return next(uri, diagnostics);
}
return null;
}
}
}
);
@ -433,10 +453,22 @@ export function activate(ctx: vscode.ExtensionContext): void {
sendTelemetryEventForConfig(updatedGoConfig);
updateGoPathGoRootFromConfig();
let reloadMessage: string;
if (e.affectsConfiguration('go.useLanguageServer')) {
if (updatedGoConfig['useLanguageServer']) {
if (getLanguageServerToolPath()) {
reloadMessage = 'Reload VS Code window to enable the use of language server';
}
} else {
reloadMessage = 'Reload VS Code window to disable the use of language server';
}
} else if (e.affectsConfiguration('go.languageServerFlags') || e.affectsConfiguration('go.languageServerExperimentalFeatures')) {
reloadMessage = 'Reload VS Code window for the changes in language server settings to take effect';
}
// If there was a change in "useLanguageServer" setting, then ask the user to reload VS Code.
if (didLangServerConfigChange(e)
&& (!updatedGoConfig['useLanguageServer'] || checkLanguageServer())) {
vscode.window.showInformationMessage('Reload VS Code window for the change in usage of language server to take effect', 'Reload').then(selected => {
if (reloadMessage) {
vscode.window.showInformationMessage(reloadMessage, 'Reload').then(selected => {
if (selected === 'Reload') {
vscode.commands.executeCommand('workbench.action.reloadWindow');
}
@ -661,10 +693,6 @@ function sendTelemetryEventForConfig(goConfig: vscode.WorkspaceConfiguration) {
});
}
function didLangServerConfigChange(e: vscode.ConfigurationChangeEvent): boolean {
return e.affectsConfiguration('go.useLanguageServer') || e.affectsConfiguration('go.languageServerFlags') || e.affectsConfiguration('go.languageServerExperimentalFeatures');
}
function checkToolExists(tool: string) {
if (tool === getBinPath(tool)) {
promptForMissingTool(tool);

Просмотреть файл

@ -59,6 +59,10 @@ export function getModFolderPath(fileuri: vscode.Uri): Promise<string> {
goConfig.update('formatTool', 'goimports', vscode.ConfigurationTarget.WorkspaceFolder);
vscode.window.showInformationMessage('`goreturns` doesnt support auto-importing missing imports when using Go modules yet. So updating the "formatTool" setting to `goimports` for this workspace.');
}
if (goConfig['useLanguageServer'] === false) {
const promptMsg = 'To get better performance during code completion, please update to use the language server from Google';
promptToUpdateToolForModules('gopls', promptMsg);
}
}
packageModCache.set(pkgPath, result);
return result;
@ -97,7 +101,19 @@ export function promptToUpdateToolForModules(tool: string, promptMsg: string) {
.then(selected => {
switch (selected) {
case 'Update':
installTools([tool], goVersion);
installTools([tool], goVersion)
.then(() => {
if (tool === 'gopls') {
const goConfig = vscode.workspace.getConfiguration('go');
if (goConfig.get('useLanguageServer') === false) {
goConfig.update('useLanguageServer', true, vscode.ConfigurationTarget.Global);
}
if (goConfig.inspect('useLanguageServer').workspaceFolderValue === false) {
goConfig.update('useLanguageServer', true, vscode.ConfigurationTarget.WorkspaceFolder);
}
vscode.window.showInformationMessage('Reload VS Code window to enable the use of Go language server');
}
});
promptedToolsForModules[tool] = true;
updateGlobalState('promptedToolsForModules', promptedToolsForModules);
break;

Просмотреть файл

@ -191,3 +191,18 @@ export function getCurrentGoWorkspaceFromGOPATH(gopath: string, currentFileDirPa
export function fixDriveCasingInWindows(pathToFix: string): string {
return (process.platform === 'win32' && pathToFix) ? pathToFix.substr(0, 1).toUpperCase() + pathToFix.substr(1) : pathToFix;
}
/**
* Returns the tool name from the given path to the tool
* @param toolPath
*/
export function getToolFromToolPath(toolPath: string): string | undefined {
if (!toolPath) {
return;
}
let tool = path.basename(toolPath);
if (process.platform === 'win32' && tool.endsWith('.exe')) {
tool = tool.substr(0, tool.length - 4);
}
return tool;
}

Просмотреть файл

@ -86,8 +86,8 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
public resolveCompletionItem(item: vscode.CompletionItem, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CompletionItem> {
if (!(item instanceof ExtendedCompletionItem)
|| item.kind === vscode.CompletionItemKind.Module
|| this.excludeDocs) {
|| item.kind === vscode.CompletionItemKind.Module
|| this.excludeDocs) {
return;
}
@ -120,26 +120,13 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
const autocompleteUnimportedPackages = config['autocompleteUnimportedPackages'] === true && !lineText.match(/^(\s)*(import|package)(\s)+/);
// 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 resolve(suggestionItem ? [suggestionItem] : []);
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
const commentIndex = lineText.indexOf('//');
if (commentIndex >= 0 && position.character > commentIndex) {
const commentPosition = new vscode.Position(position.line, commentIndex);
const isCommentInString = isPositionInString(document, commentPosition);
if (!isCommentInString) {
return resolve([]);
}
if (isPositionInComment(document, position)) {
return resolve([]);
}
const inString = isPositionInString(document, position);
@ -147,14 +134,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
return resolve([]);
}
// 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);
}
const currentWord = getCurrentWord(document, position);
if (currentWord.match(/^\d+$/)) {
return resolve([]);
}
@ -165,13 +145,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
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
if (currentWord.length > 0) {
goKeywords.forEach(keyword => {
if (keyword.startsWith(currentWord)) {
suggestions.push(new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword));
}
});
}
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
@ -282,7 +256,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
}
const results = <[number, GoCodeSuggestion[]]>JSON.parse(stdout.toString());
let suggestions: vscode.CompletionItem[] = [];
const suggestionSet = new Set<string>();
const packageSuggestions: string[] = [];
const wordAtPosition = document.getWordRangeAtPosition(position);
@ -300,6 +274,7 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
if (possiblePackageImportPaths.length === 1) {
item.detail = possiblePackageImportPaths[0];
}
packageSuggestions.push(suggest.name);
}
if (inString && suggest.class === 'import') {
item.textEdit = new vscode.TextEdit(
@ -371,13 +346,12 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
// Add same sortText to all suggestions from gocode so that they appear before the unimported packages
item.sortText = 'a';
suggestions.push(item);
suggestionSet.add(item.label);
}
}
// Add importable packages matching currentword to suggestions
if (includeUnimportedPkgs) {
suggestions = suggestions.concat(this.getMatchingPackages(document, currentWord, suggestionSet));
if (includeUnimportedPkgs && !this.isGoMod) {
suggestions = suggestions.concat(getPackageCompletions(document, currentWord, this.pkgsList, packageSuggestions));
}
// 'Smart Snippet' for package clause
@ -471,40 +445,6 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
});
}
// Return importable packages that match given word as Completion Items
private getMatchingPackages(document: vscode.TextDocument, word: string, suggestionSet: Set<string>): vscode.CompletionItem[] {
if (!word) return [];
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[] = [];
this.pkgsList.forEach((pkgName: string, pkgPath: string) => {
if (pkgName.startsWith(word) && !suggestionSet.has(pkgName)) {
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: [pkgPath]
};
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;
}
// 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;
@ -533,3 +473,137 @@ export class GoCompletionItemProvider implements vscode.CompletionItemProvider,
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 isPositionInComment(document: vscode.TextDocument, position: vscode.Position): boolean {
const lineText = document.lineAt(position.line).text;
// prevent completion when typing in a line comment that doesnt start from the beginning of the line
const commentIndex = lineText.indexOf('//');
if (commentIndex >= 0 && position.character > commentIndex) {
const commentPosition = new vscode.Position(position.line, commentIndex);
const isCommentInString = isPositionInString(document, commentPosition);
return !isCommentInString;
}
return false;
}
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, string>, 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((pkgName: string, pkgPath: string) => {
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;
}
export async function getCompletionsWithoutGoCode(document: vscode.TextDocument, position: vscode.Position): Promise<vscode.CompletionItem[]> {
const lineText = document.lineAt(position.line).text;
const config = vscode.workspace.getConfiguration('go', 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);
}