src: prompt user to file an issue after gopls crashes
This change adds support for filing a gopls issue when the user restarts `gopls` or when `gopls` crashes. In a follow-up, we might be able to suggest attaching the `gopls` log.
The suggestion to file an issue after a manual restart is disabled by default for now. We can enable it once gopls is more stable. I'm moving the bulk of https://golang.org/cl/232863 here so that I can work on refactoring the env variables separately without causing a ton of merge conflicts.
Change-Id: I0c98bd526562dd50bdc7b127ddfb95f7de926075
GitHub-Last-Rev: df990d732515fa11bee78fdca45643b8cfef2cc8
GitHub-Pull-Request: golang/vscode-go#34
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/233325
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index 2704989..202cac8 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -14,8 +14,8 @@
import util = require('util');
import vscode = require('vscode');
import {
- Command, HandleDiagnosticsSignature, LanguageClient, ProvideCompletionItemsSignature,
- ProvideDocumentLinksSignature, RevealOutputChannelOn
+ CloseAction, Command, ErrorAction, HandleDiagnosticsSignature, InitializeError, LanguageClient,
+ Message, ProvideCompletionItemsSignature, ProvideDocumentLinksSignature, RevealOutputChannelOn,
} from 'vscode-languageclient';
import WebRequest = require('web-request');
import { GoDefinitionProvider } from './goDeclaration';
@@ -35,6 +35,7 @@
import { GoWorkspaceSymbolProvider } from './goSymbol';
import { getTool, Tool } from './goTools';
import { GoTypeDefinitionProvider } from './goTypeDefinition';
+import { getFromGlobalState, updateGlobalState } from './stateUtils';
import { getBinPath, getCurrentGoPath, getGoConfig, getToolsEnvVars } from './util';
interface LanguageServerConfig {
@@ -124,7 +125,13 @@
// Set up the command to allow the user to manually restart the
// language server.
if (!restartCommand) {
- restartCommand = vscode.commands.registerCommand('go.languageserver.restart', restartLanguageServer);
+ restartCommand = vscode.commands.registerCommand('go.languageserver.restart', async () => {
+ // TODO(rstambler): Enable this behavior when gopls reaches v1.0.
+ if (false) {
+ await suggestGoplsIssueReport(`Looks like you're about to manually restart the language server.`);
+ }
+ restartLanguageServer();
+ });
ctx.subscriptions.push(restartCommand);
}
@@ -161,6 +168,31 @@
},
outputChannel: serverOutputChannel,
revealOutputChannelOn: RevealOutputChannelOn.Never,
+ initializationFailedHandler: (error: WebRequest.ResponseError<InitializeError>): boolean => {
+ vscode.window.showErrorMessage(
+ `The language server is not able to serve any features. Initialization failed: ${error}. `
+ );
+ serverOutputChannel.show();
+ suggestGoplsIssueReport(`The gopls server failed to initialize.`);
+ return false;
+ },
+ errorHandler: {
+ error: (error: Error, message: Message, count: number): ErrorAction => {
+ vscode.window.showErrorMessage(
+ `Error communicating with the language server: ${error}: ${message}.`
+ );
+ // Stick with the default number of 5 crashes before shutdown.
+ if (count >= 5) {
+ return ErrorAction.Shutdown;
+ }
+ return ErrorAction.Continue;
+ },
+ closed: (): CloseAction => {
+ serverOutputChannel.show();
+ suggestGoplsIssueReport(`The connection to gopls has been closed. The gopls server may have crashed.`);
+ return CloseAction.DoNotRestart;
+ },
+ },
middleware: {
handleDiagnostics: (
uri: vscode.Uri,
@@ -237,14 +269,6 @@
}
}
);
- 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;
}
@@ -621,3 +645,56 @@
}
return null;
}
+
+// suggestGoplsIssueReport prompts users to file an issue with gopls.
+async function suggestGoplsIssueReport(msg: string) {
+ if (latestConfig.serverName !== 'gopls') {
+ return;
+ }
+ const promptForIssueOnGoplsRestartKey = `promptForIssueOnGoplsRestart`;
+ let saved: any;
+ try {
+ saved = JSON.parse(getFromGlobalState(promptForIssueOnGoplsRestartKey, true));
+ } catch (err) {
+ console.log(`Failed to parse as JSON ${getFromGlobalState(promptForIssueOnGoplsRestartKey, true)}: ${err}`);
+ return;
+ }
+ // If the user has already seen this prompt, they may have opted-out for
+ // the future. Only prompt again if it's been more than a year since.
+ if (saved['date'] && saved['prompt']) {
+ const dateSaved = new Date(saved['date']);
+ const prompt = <boolean>saved['prompt'];
+ if (!prompt && daysBetween(new Date(), dateSaved) <= 365) {
+ return;
+ }
+ }
+ const selected = await vscode.window.showInformationMessage(`${msg} Would you like to report a gopls issue?`, 'Yes', 'Next time', 'Never');
+ switch (selected) {
+ case 'Yes':
+ // Run the `gopls bug` command directly for now. When
+ // https://github.com/golang/go/issues/38942 is
+ // resolved, we'll be able to do this through the
+ // language client.
+
+ // Wait for the command to finish before restarting the
+ // server, but don't bother handling errors.
+ const execFile = util.promisify(cp.execFile);
+ await execFile(latestConfig.path, ['bug'], { env: getToolsEnvVars() });
+ break;
+ case 'Next time':
+ break;
+ case 'Never':
+ updateGlobalState(promptForIssueOnGoplsRestartKey, JSON.stringify({
+ prompt: false,
+ date: new Date(),
+ }));
+ break;
+ }
+}
+
+// daysBetween returns the number of days between a and b,
+// assuming that a occurs after b.
+function daysBetween(a: Date, b: Date) {
+ const ms = a.getTime() - b.getTime();
+ return ms / (1000 * 60 * 60 * 24);
+}
diff --git a/test/gopls/update.test.ts b/test/gopls/update.test.ts
index f9bf110..31f9ed9 100644
--- a/test/gopls/update.test.ts
+++ b/test/gopls/update.test.ts
@@ -4,7 +4,6 @@
*--------------------------------------------------------*/
import * as assert from 'assert';
-import moment = require('moment');
import semver = require('semver');
import sinon = require('sinon');
import lsp = require('../../src/goLanguageServer');