src/goEnvironmentStatus.ts: create command palette menu for Go selection

This CL is the second in a series which should follow this general
outline:

1. Create status bar item for switching Go binary (not implemented)
2. Create command palette menu for choosing the Go binary
3. Track the currently selected Go binary using workspace context
4. Update the go command used by vscode to match the one selected by the
user
5. Show versions of Go that are not installed and allow them to be
selected and installed
6. Ensure new integrated terminals use the selected environment
7. Detect if Go is not installed and prompt to install it
8. Detect if user has the latest version of Go installed and prompt them
to install it
9. Cache Go paths upon extension initialization for faster menu loading

This CL creates an entry in the workspace state to track the currently
selected Go as well as a command palette menu for selecting it. This CL
does not make vscode actually use your selection.

Change-Id: I2967549d6d7ff514b1f31922ce0725b59021f964
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/238859
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
diff --git a/src/goEnvironmentStatus.ts b/src/goEnvironmentStatus.ts
index dbb3d8a..a1650a2 100644
--- a/src/goEnvironmentStatus.ts
+++ b/src/goEnvironmentStatus.ts
@@ -5,12 +5,21 @@
 
 'use strict';
 
+import fs = require('fs-extra');
+import os = require('os');
+import path = require('path');
 import vscode = require('vscode');
 
 import { updateGoVarsFromConfig } from './goInstallTools';
 import { getCurrentGoRoot } from './goPath';
+import { getFromWorkspaceState, updateWorkspaceState } from './stateUtils';
 import { getGoVersion } from './util';
 
+interface GoEnvironmentOption {
+	path: string;
+	label: string;
+}
+
 // statusbar item for switching the Go environment
 let goEnvStatusbarItem: vscode.StatusBarItem;
 
@@ -59,10 +68,78 @@
 
 /**
  * Present a command palette menu to the user to select their go binary
- * TODO: remove temporary alert and implement correct functionality
  */
-export function chooseGoEnvironment() {
-	vscode.window.showInformationMessage(`Current GOROOT: ${getCurrentGoRoot()}`);
+export async function chooseGoEnvironment() {
+	if (!goEnvStatusbarItem) {
+		return;
+	}
+
+	// get list of Go versions
+	const sdkPath = path.join(os.homedir(), 'sdk');
+	if (!await fs.pathExists(sdkPath)) {
+		vscode.window.showErrorMessage(`SDK path does not exist: ${sdkPath}`);
+		return;
+	}
+	const subdirs = await fs.readdir(sdkPath);
+
+	// create quick pick items
+	// the dir happens to be the version, which will be used as the label
+	// the path is assembled and used as the description
+	const goSdkOptions: vscode.QuickPickItem[] = subdirs.map((dir: string) => {
+		return {
+			label: dir.replace('go', 'Go '),
+			description: path.join(sdkPath, dir, 'bin', 'go'),
+		};
+	});
+
+	// get default option
+	let defaultOption: vscode.QuickPickItem;
+	try {
+		const defaultGo = await getDefaultGoOption();
+		defaultOption = {
+			label: defaultGo.label,
+			description: defaultGo.path
+		};
+	} catch (err) {
+		vscode.window.showErrorMessage(err.message);
+		return;
+	}
+
+	// dedup options by eliminating duplicate paths (description)
+	const options = [defaultOption, ...goSdkOptions].reduce((opts, nextOption) => {
+		if (opts.find((op) => op.description === nextOption.description)) {
+			return opts;
+		}
+		return [...opts, nextOption];
+	}, [] as vscode.QuickPickItem[]);
+
+	// show quick pick to select new go version
+	const { label, description } = await vscode.window.showQuickPick<vscode.QuickPickItem>(options);
+	vscode.window.showInformationMessage(`Current GOROOT: ${description}`);
+
+	// update currently selected go
+	await setSelectedGo({
+		label,
+		path: description,
+	});
+}
+
+/**
+ * update the selected go path and label in the workspace state
+ */
+async function setSelectedGo(selectedGo: GoEnvironmentOption) {
+	// the go-environment state should follow the below format
+	// TODO: restart language server when the Go binary is switched
+	// TODO: determine if changes to settings.json need to be made
+	await updateWorkspaceState('selected-go', selectedGo);
+	goEnvStatusbarItem.text = selectedGo.label;
+}
+
+/**
+ * retreive the current selected Go from the workspace state
+ */
+export async function getSelectedGo(): Promise<GoEnvironmentOption> {
+	return await getFromWorkspaceState('selected-go');
 }
 
 /**
@@ -95,3 +172,18 @@
 		return `Go ${versionWords[0]}`;
 	}
 }
+
+export async function getDefaultGoOption(): Promise<GoEnvironmentOption> {
+	// make goroot default to go.goroot
+	const goroot = await getActiveGoRoot();
+	if (!goroot) {
+		throw new Error('No Go command could be found.');
+	}
+
+	// set Go version and command
+	const version = await getGoVersion();
+	return {
+		path: path.join(goroot, 'bin', 'go'),
+		label: formatGoVersion(version.format()),
+	};
+}