src/goLanguageServer: show language server start progress

Prevent duplicate language server restart requests
while another restart is in progress.

Allow users to cancel language server restart requests, which
results in asking users to reload the window.
We observed, when gopls is in a really bad state, it fails to respond
to the shutdown request promptly and the restart request looks hang.
In such cases, reloading the window is a quicker way to kill the
language server and recover.

Tested by using a broken language server that sleeps for a while
upon shutdown request.

Fixes golang/vscode-go#1011

Change-Id: Id1317315370ecfc8e124341a170848f7b1786335
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/276973
Reviewed-by: Suzy Mueller <suzmue@golang.org>
Trust: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/goLanguageServer.ts b/src/goLanguageServer.ts
index 285c681..b0e82f4 100644
--- a/src/goLanguageServer.ts
+++ b/src/goLanguageServer.ts
@@ -81,6 +81,9 @@
 let latestConfig: LanguageServerConfig;
 export let serverOutputChannel: vscode.OutputChannel;
 export let languageServerIsRunning = false;
+// TODO: combine languageServerIsRunning & languageServerStartInProgress
+// as one languageServerStatus variable.
+let languageServerStartInProgress = false;
 let serverTraceChannel: vscode.OutputChannel;
 let crashCount = 0;
 
@@ -98,6 +101,12 @@
 // startLanguageServerWithFallback starts the language server, if enabled,
 // or falls back to the default language providers.
 export async function startLanguageServerWithFallback(ctx: vscode.ExtensionContext, activation: boolean) {
+	if (!activation && languageServerStartInProgress) {
+		console.log('language server restart is already in progress...');
+		return;
+	}
+	languageServerStartInProgress = true;
+
 	const cfg = buildLanguageServerConfig(getGoConfig());
 
 	// If the language server is gopls, we enable a few additional features.
@@ -109,17 +118,39 @@
 		}
 	}
 
-	const started = await startLanguageServer(ctx, cfg);
+	const progressMsg = languageServerIsRunning ? 'Restarting language service' : 'Starting language service';
+	await vscode.window.withProgress({
+		title: progressMsg,
+		cancellable: !activation,
+		location: vscode.ProgressLocation.Notification,
+	}, async (progress, token) => {
+		let disposable: vscode.Disposable;
+		if (token) {
+			disposable = token.onCancellationRequested(async () => {
+				const choice = await vscode.window.showErrorMessage(
+					'Language service restart request was interrupted and language service may be in a bad state. ' +
+					'Please reload the window.',
+					'Reload Window');
+				if (choice === 'Reload Window') {
+					await vscode.commands.executeCommand('workbench.action.reloadWindow');
+				}
+			});
+		}
 
-	// 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);
-	}
+		const started = await startLanguageServer(ctx, cfg);
 
-	languageServerIsRunning = started;
-	updateLanguageServerIconGoStatusBar(started, cfg.serverName);
+		// 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);
+		}
+
+		if (disposable) { disposable.dispose(); }
+		languageServerIsRunning = started;
+		updateLanguageServerIconGoStatusBar(started, cfg.serverName);
+		languageServerStartInProgress = false;
+	});
 }
 
 // scheduleGoplsSuggestions sets timeouts for the various gopls-specific