all: make Go binary default to memento setting

This CL adds a memento setting 'selectedGo' which acts as the default
option for the 'go' binary. It also updates the statusbar item to
respect this change.

Change-Id: I9aed6345dbaf2e428366aec207d0c0192e0c41c6
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/242898
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/goEnvironmentStatus.ts b/src/goEnvironmentStatus.ts
index 7eef510..9fc73b3 100644
--- a/src/goEnvironmentStatus.ts
+++ b/src/goEnvironmentStatus.ts
@@ -14,8 +14,9 @@
 import WebRequest = require('web-request');
 import { toolInstallationEnvironment } from './goEnv';
 import { outputChannel } from './goStatus';
-import { getBinPath, getGoConfig, getGoVersion, getTempFilePath, rmdirRecursive } from './util';
-import { getBinPathFromEnvVar, getCurrentGoRoot, pathExists } from './utils/goPath';
+import { getFromWorkspaceState, updateWorkspaceState } from './stateUtils';
+import { getBinPath, getGoVersion, getTempFilePath, rmdirRecursive } from './util';
+import { correctBinname, getBinPathFromEnvVar, getCurrentGoRoot, pathExists } from './utils/goPath';
 
 export class GoEnvironmentOption {
 	public static fromQuickPickItem({ description, label }: vscode.QuickPickItem): GoEnvironmentOption {
@@ -131,12 +132,14 @@
 	const goSDKQuickPicks = goSDKOptions.map((op) => op.toQuickPickItem());
 
 	// dedup options by eliminating duplicate paths (description)
-	const options = [defaultQuickPick, ...goSDKQuickPicks, ...uninstalledQuickPicks].reduce((opts, nextOption) => {
-		if (opts.find((op) => op.description === nextOption.description || op.label === nextOption.label)) {
-			return opts;
-		}
-		return [...opts, nextOption];
-	}, [] as vscode.QuickPickItem[]);
+	const clearOption: vscode.QuickPickItem = { label: 'Clear selection' };
+	const options = [clearOption, defaultQuickPick, ...goSDKQuickPicks, ...uninstalledQuickPicks]
+		.reduce((opts, nextOption) => {
+			if (opts.find((op) => op.description === nextOption.description || op.label === nextOption.label)) {
+				return opts;
+			}
+			return [...opts, nextOption];
+		}, [] as vscode.QuickPickItem[]);
 
 	// get user's selection, return if none was made
 	const selection = await vscode.window.showQuickPick<vscode.QuickPickItem>(options);
@@ -146,7 +149,7 @@
 
 	// update currently selected go
 	try {
-		await setSelectedGo(GoEnvironmentOption.fromQuickPickItem(selection), vscode.ConfigurationTarget.Workspace);
+		await setSelectedGo(GoEnvironmentOption.fromQuickPickItem(selection));
 		vscode.window.showInformationMessage(`Switched to ${selection.label}`);
 	} catch (e) {
 		vscode.window.showErrorMessage(e.message);
@@ -156,18 +159,13 @@
 /**
  * update the selected go path and label in the workspace state
  */
-export async function setSelectedGo(
-	selectedGo: GoEnvironmentOption, scope: vscode.ConfigurationTarget, promptReload = true
-) {
+export async function setSelectedGo(goOption: GoEnvironmentOption, promptReload = true) {
 	const execFile = promisify(cp.execFile);
-
-	const goConfig = getGoConfig();
-	const alternateTools: any = goConfig.get('alternateTools') || {};
 	// if the selected go version is not installed, install it
-	if (selectedGo.binpath.startsWith('go get')) {
+	if (goOption.binpath?.startsWith('go get')) {
 		// start a loading indicator
 		await vscode.window.withProgress({
-			title: `Downloading ${selectedGo.label}`,
+			title: `Downloading ${goOption.label}`,
 			location: vscode.ProgressLocation.Notification,
 		}, async () => {
 			outputChannel.show();
@@ -198,7 +196,7 @@
 				...toolInstallationEnvironment(),
 				GO111MODULE: 'on',
 			};
-			const [, ...args] = selectedGo.binpath.split(' ');
+			const [, ...args] = goOption.binpath.split(' ');
 			outputChannel.appendLine(`Running ${goExecutable} ${args.join(' ')}`);
 			try {
 				await execFile(goExecutable, args, {
@@ -236,32 +234,28 @@
 				throw new Error('Could not install Go version.');
 			}
 
-			const binpath = path.join(sdkPath, dir, 'bin', 'go');
-			const newAlternateTools = {
-				...alternateTools,
-				go: binpath,
-			};
-			await goConfig.update('alternateTools', newAlternateTools, scope);
-			goEnvStatusbarItem.text = selectedGo.label;
+			const binpath = path.join(sdkPath, dir, 'bin', correctBinname('go'));
+			const newOption = new GoEnvironmentOption(binpath, goOption.label);
+			await updateWorkspaceState('selectedGo', newOption);
+			goEnvStatusbarItem.text = goOption.label;
 
 			// remove tmp directories
 			outputChannel.appendLine('Cleaning up...');
 			rmdirRecursive(toolsTmpDir);
 			outputChannel.appendLine('Success!');
 		});
+	} else if (goOption.label === 'Clear selection') {
+		updateWorkspaceState('selectedGo', undefined);
 	} else {
 		// check that the given binary is not already at the beginning of the PATH
 		const go = await getGoVersion();
-		if (go.binaryPath === selectedGo.binpath) {
+		if (go.binaryPath === goOption.binpath) {
 			return;
 		}
 
-		const newAlternateTools = {
-			...alternateTools,
-			go: selectedGo.binpath,
-		};
-		await goConfig.update('alternateTools', newAlternateTools, scope);
-		goEnvStatusbarItem.text = selectedGo.label;
+		console.log('updating selectedGo: ', goOption);
+		await updateWorkspaceState('selectedGo', goOption);
+		goEnvStatusbarItem.text = goOption.label;
 	}
 	// prompt the user to reload the window
 	// promptReload defaults to true and should only be false for tests
@@ -305,9 +299,8 @@
 /**
  * retreive the current selected Go from the workspace state
  */
-export async function getSelectedGo(): Promise<GoEnvironmentOption> {
-	const goVersion = await getGoVersion();
-	return new GoEnvironmentOption(goVersion.binaryPath, formatGoVersion(goVersion.format()));
+export function getSelectedGo(): GoEnvironmentOption {
+	return getFromWorkspaceState('selectedGo');
 }
 
 /**
diff --git a/src/goInstallTools.ts b/src/goInstallTools.ts
index 218095e..eb51b4a 100644
--- a/src/goInstallTools.ts
+++ b/src/goInstallTools.ts
@@ -378,6 +378,7 @@
 			} else {
 				cachePath = process.env.Path;
 			}
+
 			addGoRuntimeBaseToPATH(path.join(getCurrentGoRoot(), 'bin'));
 			initGoStatusBar(cachePath);
 			// TODO: restart language server or synchronize with language server update.
diff --git a/src/util.ts b/src/util.ts
index e8a77f2..0274b7d 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -16,6 +16,7 @@
 import { buildDiagnosticCollection, lintDiagnosticCollection, vetDiagnosticCollection } from './goMain';
 import { getCurrentPackage } from './goModules';
 import { outputChannel } from './goStatus';
+import { getFromWorkspaceState } from './stateUtils';
 import {
 	envPath,
 	fixDriveCasingInWindows,
@@ -456,11 +457,16 @@
 	const alternateTools: { [key: string]: string } = cfg.get('alternateTools');
 	const alternateToolPath: string = alternateTools[tool];
 
+	let selectedGoPath: string | undefined;
+	if (tool === 'go') {
+		selectedGoPath = getFromWorkspaceState('selectedGo')?.binpath;
+	}
+
 	return getBinPathWithPreferredGopathGoroot(
 		tool,
 		tool === 'go' ? [] : [getToolsGopath(), getCurrentGoPath()],
 		tool === 'go' && cfg.get('goroot') ? cfg.get('goroot') : undefined,
-		resolvePath(alternateToolPath),
+		selectedGoPath ?? resolvePath(alternateToolPath),
 		useCache
 	);
 }
diff --git a/src/utils/goPath.ts b/src/utils/goPath.ts
index c29c572..6ad1b1a 100644
--- a/src/utils/goPath.ts
+++ b/src/utils/goPath.ts
@@ -38,7 +38,7 @@
 	preferredGoroot?: string,
 	alternateTool?: string,
 	useCache = true,
-) {
+): string {
 	if (alternateTool && path.isAbsolute(alternateTool) && executableFileExists(alternateTool)) {
 		binPathCache[toolName] = alternateTool;
 		return alternateTool;
@@ -107,7 +107,7 @@
 	currentGoRoot = goroot;
 }
 
-function correctBinname(toolName: string) {
+export function correctBinname(toolName: string) {
 	if (process.platform === 'win32') {
 		return toolName + '.exe';
 	}
diff --git a/test/integration/statusbar.test.ts b/test/integration/statusbar.test.ts
index 05a103d..7873ab4 100644
--- a/test/integration/statusbar.test.ts
+++ b/test/integration/statusbar.test.ts
@@ -65,7 +65,7 @@
 		sandbox = sinon.createSandbox();
 	});
 	this.afterEach(async () => {
-		await setSelectedGo(goOption, vscode.ConfigurationTarget.Workspace, false);
+		await setSelectedGo(goOption, false);
 		sandbox.restore();
 	});
 
@@ -90,7 +90,7 @@
 
 		// set workspace setting
 		const workspaceTestOption = new GoEnvironmentOption('workspacetestpath', 'testlabel');
-		await setSelectedGo(workspaceTestOption, vscode.ConfigurationTarget.Workspace, false);
+		await setSelectedGo(workspaceTestOption, false);
 
 		// check that the stub was called
 		sandbox.assert.calledWith(getGoConfigStub);
@@ -128,7 +128,7 @@
 
 		// set selected go as a version to download
 		const option = new GoEnvironmentOption('go get golang.org/dl/go1.13.12', 'Go 1.13.12');
-		await setSelectedGo(option, vscode.ConfigurationTarget.Workspace, false);
+		await setSelectedGo(option, false);
 		sandbox.assert.calledWith(getGoConfigStub);
 
 		// the temp sdk directory should now contain go1.13.12