Skip to content
This repository has been archived by the owner on Jul 15, 2023. It is now read-only.

Restart language server automatically when its configuration changes #3211

Closed
wants to merge 51 commits into from
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
36c182f
METADATA: add METADATA file
hyangah Jan 9, 2020
4263aca
telemetry.ts: send telemetry only if aiKey is not empty
hyangah Mar 6, 2020
ada0858
package.json: update for Go Nightly and disable telemetry
hyangah Jan 22, 2020
cc26d30
goLanguageServer.ts: allow Go Nightly to use pre-release versions.
hyangah Jan 23, 2020
57eea9b
README.md: add sections for Go Nightly
hyangah Jan 23, 2020
a8f31ee
builds: cloudbuild setup for Go Nightly
hyangah Jan 17, 2020
341a4ff
CHANGELOG.md: 2020.3.x
hyangah Mar 4, 2020
7bcec4e
github/workflows: add CI github action workflow
hyangah Mar 11, 2020
c6967b3
github/workflows: fix the syntax error in ci.yml
hyangah Mar 11, 2020
d60e8c1
Refactor handling for go-langserver installation
stamblerre Mar 7, 2020
ff99b03
test/integration: use default GOPATH when the env var is not set
hyangah Mar 17, 2020
2c09aca
github/workflow: daily release workflow
hyangah Mar 18, 2020
d585479
github/workflows: use <year>.<month>.<day>.<hour> as version
hyangah Mar 18, 2020
f19678d
github/workflows: use npm install for release testing
hyangah Mar 19, 2020
114da49
github/workflows: use npm install and change version to X.Y.Z format
hyangah Mar 19, 2020
19896f5
Sync @ 7da5077
hyangah Mar 20, 2020
908b513
goMain: go.locate.tools command
hyangah Mar 20, 2020
25c7bf5
Sync microsoft/vscode-go@d53b1b3
hyangah Mar 20, 2020
2eb3f22
Revert "Sync microsoft/vscode-go@d53b1b3"
hyangah Mar 23, 2020
f58e6b0
Revert "Sync @ 7da5077"
hyangah Mar 23, 2020
67fde72
merge microsoft/vscode-go@430362e
hyangah Mar 23, 2020
9af87f1
build/merge.bash: script to help sync nightly with upstream
hyangah Mar 23, 2020
39dadee
sync: merge microsoft/vscode-go@efb94c8 into master
hyangah Mar 24, 2020
3806624
sync: merge microsoft/vscode-go@79a6b01 into master
hyangah Mar 25, 2020
ccc284e
build/merge: fix gcloud command error
hyangah Mar 25, 2020
f4e5bfe
build/README: document CI/CD status
hyangah Mar 25, 2020
a198079
sync: merge microsoft/vscode-go@765f96d into master
hyangah Mar 26, 2020
3797aee
sync: Merge microsoft/vscode-go@134a1a0417 to master
hyangah Mar 27, 2020
fc8c683
sync: merge microsoft/vscode-go@b83ad81 to master
hyangah Mar 29, 2020
b865fc1
CHANGELOG.md: restore upstream CHANGELOG
hyangah Apr 2, 2020
e09cb91
sync: merge microsoft/vscode-go@0a63ec7 into master
hyangah Apr 6, 2020
921e3a0
extend installTools to choose the version
hyangah Jan 28, 2020
2685bdc
sync: merge microsoft/vscode-go@082bcfd into master
hyangah Apr 8, 2020
7966709
sync: merge microsoft/vscode-go@4b36da1 into master
hyangah Apr 14, 2020
a823f46
sync: merge microsoft/vscode-go@383c0c0
hyangah Apr 16, 2020
3b9c40b
upgrade all dependencies
stamblerre Apr 17, 2020
02900e7
sync: merge microsoft/vscode-go@c8ed3d8 into master
hyangah Apr 17, 2020
4957f66
sync: merge microsoft/vscode-go@46dfb5a into master
hyangah Apr 20, 2020
b06de5b
sync: merge microsoft/vscode-go@dfa9cbb into master
hyangah Apr 22, 2020
d538ccc
sync: merge microsoft/vscode-go@d3c0757 into master
hyangah Apr 23, 2020
43548fa
sync: merge microsoft/vscode-go@f3dd04b into master
hyangah Apr 27, 2020
be40927
test/gopls: fix the lint error
hyangah Apr 27, 2020
adf7410
sync: merge microsoft/vscode-go@e8e97e5 into master
hyangah Apr 28, 2020
1f0cb2a
restart the server automatically on a config change
stamblerre Apr 28, 2020
418d3d1
address Hana's comments
stamblerre Apr 30, 2020
6c66d8e
fix a small change that crept in
stamblerre Apr 30, 2020
799b4d3
fix incorrect import
stamblerre May 5, 2020
9b33e86
fix early return
stamblerre May 5, 2020
5abbe38
sync: merge microsoft/vscode-go@92fd305 into master
hyangah May 6, 2020
70e7c4b
src/goLanguageServer.ts: add go.trace.server configuration
hyangah May 6, 2020
29fd4d6
fix merge conflict
stamblerre May 6, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/goCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import path = require('path');
import vscode = require('vscode');
import { goBuild } from './goBuild';
import { parseLanguageServerConfig } from './goLanguageServer';
import { buildLanguageServerConfig } from './goLanguageServer';
import { goLint } from './goLint';
import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
import { isModSupported } from './goModules';
Expand Down Expand Up @@ -59,7 +59,7 @@ export function check(fileUri: vscode.Uri, goConfig: vscode.WorkspaceConfigurati

// If a user has enabled diagnostics via a language server,
// then we disable running build or vet to avoid duplicate errors and warnings.
const lspConfig = parseLanguageServerConfig();
const lspConfig = buildLanguageServerConfig();
const disableBuildAndVet = lspConfig.enabled && lspConfig.features.diagnostics;

let testPromise: Thenable<boolean>;
Expand Down
228 changes: 148 additions & 80 deletions src/goLanguageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,40 @@

import cp = require('child_process');
import deepEqual = require('deep-equal');
import fs = require('fs');
import moment = require('moment');
import path = require('path');
import semver = require('semver');
import util = require('util');
import vscode = require('vscode');
import {
Command,
HandleDiagnosticsSignature,
LanguageClient,
ProvideCompletionItemsSignature,
ProvideDocumentLinksSignature,
RevealOutputChannelOn
Command, HandleDiagnosticsSignature, LanguageClient, ProvideCompletionItemsSignature,
ProvideDocumentLinksSignature, RevealOutputChannelOn
} from 'vscode-languageclient';
import WebRequest = require('web-request');
import { GoDefinitionProvider } from './goDeclaration';
import { GoHoverProvider } from './goExtraInfo';
import { GoDocumentFormattingEditProvider } from './goFormat';
import { GoImplementationProvider } from './goImplementations';
import { promptForMissingTool, promptForUpdatingTool } from './goInstallTools';
import { parseLiveFile } from './goLiveErrors';
import { restartLanguageServer } from './goMain';
import { GO_MODE } from './goMode';
import { GoDocumentSymbolProvider } from './goOutline';
import { getToolFromToolPath } from './goPath';
import { GoReferenceProvider } from './goReferences';
import { GoRenameProvider } from './goRename';
import { GoSignatureHelpProvider } from './goSignature';
import { GoCompletionItemProvider } from './goSuggest';
import { GoWorkspaceSymbolProvider } from './goSymbol';
import { getTool, Tool } from './goTools';
import { GoTypeDefinitionProvider } from './goTypeDefinition';
import { getBinPath, getCurrentGoPath, getGoConfig, getToolsEnvVars } from './util';

interface LanguageServerConfig {
serverName: string;
path: string;
modtime: Date;
enabled: boolean;
flags: string[];
env: any;
Expand All @@ -47,38 +59,41 @@ let languageServerDisposable: vscode.Disposable;
let latestConfig: LanguageServerConfig;
let serverOutputChannel: vscode.OutputChannel;

// startLanguageServer starts the language server (if enabled), returning
// true on success.
export async function registerLanguageFeatures(ctx: vscode.ExtensionContext): Promise<boolean> {
// Subscribe to notifications for changes to the configuration of the language server.
ctx.subscriptions.push(vscode.workspace.onDidChangeConfiguration((e) => watchLanguageServerConfiguration(e)));

const config = parseLanguageServerConfig();
if (!config.enabled) {
return false;
// defaultLanguageProviders is the list of providers currently registered.
let defaultLanguageProviders: vscode.Disposable[] = [];

// restartCommand is the command used by the user to restart the language
// server.
let restartCommand: vscode.Disposable;

// startLanguageServerWithFallback starts the language server, if enabled,
// or falls back to the default language providers.
export async function startLanguageServerWithFallback(ctx: vscode.ExtensionContext, activation: boolean) {
const cfg = buildLanguageServerConfig();
if (cfg.enabled) {
// If the language server is gopls, we can check if the user needs to
// update their gopls version. We do this only once per VS Code
// activation to avoid inundating the user.
if (activation && cfg.serverName === 'gopls') {
const tool = getTool(cfg.serverName);
if (!tool) {
return false;
stamblerre marked this conversation as resolved.
Show resolved Hide resolved
stamblerre marked this conversation as resolved.
Show resolved Hide resolved
}
const versionToUpdate = await shouldUpdateLanguageServer(tool, cfg.path, cfg.checkForUpdates);
if (versionToUpdate) {
promptForUpdatingTool(tool.name);
}
}
}

// Support a command to restart the language server, if it's enabled.
ctx.subscriptions.push(vscode.commands.registerCommand('go.languageserver.restart', () => {
return startLanguageServer(ctx, parseLanguageServerConfig());
}));
const started = await startLanguageServer(ctx, cfg);

// If the language server is gopls, we can check if the user needs to
// update their gopls version.
if (config.serverName === 'gopls') {
const tool = getTool(config.serverName);
if (!tool) {
return false;
}
const versionToUpdate = await shouldUpdateLanguageServer(tool, config.path, config.checkForUpdates);
if (versionToUpdate) {
promptForUpdatingTool(tool.name);
}
// If the server has been disabled, or failed to start,
// fall back to the default providers, while making sure not to
// re-register any providers.
if (!started && defaultLanguageProviders.length === 0) {
registerDefaultProviders(ctx);
}

// This function handles the case when the server isn't started yet,
// so we can call it to start the language server.
return startLanguageServer(ctx, config);
}

async function startLanguageServer(ctx: vscode.ExtensionContext, config: LanguageServerConfig): Promise<boolean> {
Expand All @@ -97,28 +112,39 @@ async function startLanguageServer(ctx: vscode.ExtensionContext, config: Languag
// Check if we should recreate the language client. This may be necessary
// if the user has changed settings in their config.
if (!deepEqual(latestConfig, config)) {
// Track the latest config used to start the language server.
// Track the latest config used to start the language server,
// and rebuild the language client.
latestConfig = config;
languageClient = await buildLanguageClient(config);
stamblerre marked this conversation as resolved.
Show resolved Hide resolved
}

// If the user has not enabled or installed the language server, return.
if (!config.enabled || !config.path) {
return false;
}
buildLanguageClient(config);
// If the user has not enabled the language server, return early.
if (!config.enabled) {
return false;
}

// Set up the command to allow the user to manually restart the
// language server.
if (!restartCommand) {
restartCommand = vscode.commands.registerCommand('go.languageserver.restart', restartLanguageServer);
ctx.subscriptions.push(restartCommand);
}

// Before starting the language server, make sure to deregister any
// currently registered language providers.
disposeDefaultProviders();

languageServerDisposable = languageClient.start();
ctx.subscriptions.push(languageServerDisposable);

return true;
}

function buildLanguageClient(config: LanguageServerConfig) {
async function buildLanguageClient(config: LanguageServerConfig): Promise<LanguageClient> {
// Reuse the same output channel for each instance of the server.
if (!serverOutputChannel) {
if (config.enabled && !serverOutputChannel) {
serverOutputChannel = vscode.window.createOutputChannel(config.serverName);
}
languageClient = new LanguageClient(
const c = new LanguageClient(
config.serverName,
{
command: config.path,
Expand Down Expand Up @@ -212,60 +238,78 @@ function buildLanguageClient(config: LanguageServerConfig) {
}
}
);
languageClient.onReady().then(() => {
c.onReady().then(() => {
const capabilities = languageClient.initializeResult && languageClient.initializeResult.capabilities;
if (!capabilities) {
return vscode.window.showErrorMessage(
'The language server is not able to serve any features at the moment.'
);
}
});
return c;
}

function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) {
if (!e.affectsConfiguration('go')) {
return;
// registerUsualProviders registers the language feature providers if the language server is not enabled.
function registerDefaultProviders(ctx: vscode.ExtensionContext) {
const completionProvider = new GoCompletionItemProvider(ctx.globalState);
defaultLanguageProviders.push(completionProvider);
defaultLanguageProviders.push(vscode.languages.registerCompletionItemProvider(GO_MODE, completionProvider, '.', '"'));
defaultLanguageProviders.push(vscode.languages.registerHoverProvider(GO_MODE, new GoHoverProvider()));
defaultLanguageProviders.push(vscode.languages.registerDefinitionProvider(GO_MODE, new GoDefinitionProvider()));
defaultLanguageProviders.push(vscode.languages.registerReferenceProvider(GO_MODE, new GoReferenceProvider()));
defaultLanguageProviders.push(
vscode.languages.registerDocumentSymbolProvider(GO_MODE, new GoDocumentSymbolProvider())
);
defaultLanguageProviders.push(vscode.languages.registerWorkspaceSymbolProvider(new GoWorkspaceSymbolProvider()));
defaultLanguageProviders.push(
vscode.languages.registerSignatureHelpProvider(GO_MODE, new GoSignatureHelpProvider(), '(', ',')
);
defaultLanguageProviders.push(
vscode.languages.registerImplementationProvider(GO_MODE, new GoImplementationProvider())
);
defaultLanguageProviders.push(
vscode.languages.registerDocumentFormattingEditProvider(GO_MODE, new GoDocumentFormattingEditProvider())
);
defaultLanguageProviders.push(
vscode.languages.registerTypeDefinitionProvider(GO_MODE, new GoTypeDefinitionProvider())
);
defaultLanguageProviders.push(vscode.languages.registerRenameProvider(GO_MODE, new GoRenameProvider()));
defaultLanguageProviders.push(vscode.workspace.onDidChangeTextDocument(parseLiveFile, null, ctx.subscriptions));

for (const provider of defaultLanguageProviders) {
ctx.subscriptions.push(provider);
}
}

const config = parseLanguageServerConfig();
let reloadMessage: string;
function disposeDefaultProviders() {
for (const disposable of defaultLanguageProviders) {
disposable.dispose();
}
defaultLanguageProviders = [];
}

// If the user has disabled or enabled the language server.
if (e.affectsConfiguration('go.useLanguageServer')) {
if (config.enabled) {
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';
}
export function watchLanguageServerConfiguration(e: vscode.ConfigurationChangeEvent) {
if (!e.affectsConfiguration('go')) {
stamblerre marked this conversation as resolved.
Show resolved Hide resolved
return;
}

if (
e.affectsConfiguration('go.useLanguageServer') ||
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 the configuration of the language server,
// then ask the user to reload VS Code.
if (reloadMessage) {
vscode.window.showInformationMessage(reloadMessage, 'Reload').then((selected) => {
if (selected === 'Reload') {
vscode.commands.executeCommand('workbench.action.reloadWindow');
}
});
restartLanguageServer();
}
}

export function parseLanguageServerConfig(): LanguageServerConfig {
export function buildLanguageServerConfig(): LanguageServerConfig {
const goConfig = getGoConfig();
const toolsEnv = getToolsEnvVars();
const languageServerPath = getLanguageServerToolPath();
const languageServerName = getToolFromToolPath(languageServerPath);
return {
serverName: languageServerName,
path: languageServerPath,
enabled: goConfig['useLanguageServer'],
const cfg: LanguageServerConfig = {
serverName: '',
path: '',
modtime: null,
enabled: goConfig['useLanguageServer'] === true,
flags: goConfig['languageServerFlags'] || [],
features: {
// TODO: We should have configs that match these names.
Expand All @@ -276,21 +320,45 @@ export function parseLanguageServerConfig(): LanguageServerConfig {
env: toolsEnv,
checkForUpdates: goConfig['useGoProxyToCheckForToolUpdates']
};
// Don't look for the path if the server is not enabled.
if (!cfg.enabled) {
return cfg;
}
const languageServerPath = getLanguageServerToolPath();
if (!languageServerPath) {
// Assume the getLanguageServerToolPath will show the relevant
// errors to the user. Disable the language server.
cfg.enabled = false;
return cfg;
}
cfg.path = languageServerPath;
cfg.serverName = getToolFromToolPath(cfg.path);

// Get the mtime of the language server binary so that we always pick up
// the right version.
const stats = fs.statSync(languageServerPath);
if (!stats) {
vscode.window.showErrorMessage(`Unable to stat path to language server binary: ${languageServerPath}.
Please try reinstalling it.`);
// Disable the language server.
cfg.enabled = false;
return cfg;
}
cfg.modtime = stats.mtime;

return cfg;
}

/**
*
* If the user has enabled the language server, return the absolute path to the
* correct binary. If the required tool is not available, prompt the user to
* install it. Only gopls is officially supported.
* Return the absolute path to the correct binary. If the required tool is not available,
* prompt the user to install it. Only gopls is officially supported.
*/
export function getLanguageServerToolPath(): string {
// If language server is not enabled, return
const goConfig = getGoConfig();
if (!goConfig['useLanguageServer']) {
return;
}

// Check that all workspace folders are configured with the same GOPATH.
if (!allFoldersHaveSameGopath()) {
vscode.window.showInformationMessage(
Expand Down
Loading