| /* eslint-disable no-prototype-builtins */ | |
| /* eslint-disable prefer-const */ | |
| /*--------------------------------------------------------- | |
| * Copyright 2020 The Go Authors. All rights reserved. | |
| * Licensed under the MIT License. See LICENSE in the project root for license information. | |
| *--------------------------------------------------------*/ | |
| 'use strict'; | |
| import cp = require('child_process'); | |
| import fs = require('fs'); | |
| import moment = require('moment'); | |
| import os = require('os'); | |
| import path = require('path'); | |
| import { promisify } from 'util'; | |
| import semverCoerce from 'semver/functions/coerce'; | |
| import { getGoConfig, extensionInfo } from './config'; | |
| import { toolInstallationEnvironment } from './goEnv'; | |
| import { addGoStatus, goEnvStatusbarItem, outputChannel, removeGoStatus } from './goStatus'; | |
| import { getFromGlobalState, getFromWorkspaceState, updateGlobalState, updateWorkspaceState } from './stateUtils'; | |
| import { getBinPath, getCheckForToolsUpdatesConfig, getGoVersion, GoVersion } from './util'; | |
| import { | |
| correctBinname, | |
| executableFileExists, | |
| fixDriveCasingInWindows, | |
| getBinPathFromEnvVar, | |
| getCurrentGoRoot, | |
| dirExists | |
| } from './utils/pathUtils'; | |
| import vscode = require('vscode'); | |
| import { installTool } from './goInstallTools'; | |
| import { CommandFactory } from './commands'; | |
| import fetch from 'node-fetch'; | |
| export class GoEnvironmentOption implements vscode.QuickPickItem { | |
| readonly description: string; | |
| constructor( | |
| readonly binpath: string, | |
| readonly label: string, | |
| readonly available = true | |
| ) { | |
| this.description = available ? binpath : `download ${binpath}`; | |
| } | |
| } | |
| export let terminalCreationListener: vscode.Disposable | undefined; | |
| let environmentVariableCollection: vscode.EnvironmentVariableCollection; | |
| export function setEnvironmentVariableCollection(env: vscode.EnvironmentVariableCollection) { | |
| environmentVariableCollection = env; | |
| } | |
| // QuickPickItem names for chooseGoEnvironment menu. | |
| const CLEAR_SELECTION = '$(clear-all) Clear selection'; | |
| const CHOOSE_FROM_FILE_BROWSER = '$(folder) Choose from file browser'; | |
| function canChooseGoEnvironment() { | |
| // if there is no workspace, show GOROOT with message | |
| if (!vscode.workspace.name) { | |
| return { ok: false, reason: 'Switching Go version is not yet supported in single-file mode.' }; | |
| } | |
| if (getGoConfig().get('goroot')) { | |
| return { ok: false, reason: 'Switching Go version when "go.goroot" is set is unsupported.' }; | |
| } | |
| if (process.env['GOROOT']) { | |
| return { ok: false, reason: 'Switching Go version when process.env["GOROOT"] is set is unsupported.' }; | |
| } | |
| return { ok: true }; | |
| } | |
| /** | |
| * Presents a command palette menu to the user to select their go binary. | |
| */ | |
| export const chooseGoEnvironment: CommandFactory = () => async () => { | |
| if (!goEnvStatusbarItem) { | |
| return; | |
| } | |
| const { ok, reason } = canChooseGoEnvironment(); | |
| if (!ok) { | |
| vscode.window.showInformationMessage(`GOROOT: ${getCurrentGoRoot()}. ${reason}`); | |
| return; | |
| } | |
| let options: vscode.QuickPickItem[] = [ | |
| // Option to choose go binary from file browser. | |
| { | |
| label: CHOOSE_FROM_FILE_BROWSER, | |
| description: 'Select the go binary to use' | |
| }, | |
| // Option to clear the existing selection. | |
| { label: CLEAR_SELECTION } | |
| ]; | |
| try { | |
| const seenDescriptions = new Set<string>(); | |
| const seenLabels = new Set<string>(); | |
| // addOption adds the option to the input array only if it is unique, | |
| // based on its description and label. | |
| const addOption = (options: GoEnvironmentOption[], option: GoEnvironmentOption | undefined) => { | |
| if (option === undefined) { | |
| return; | |
| } | |
| if (!seenDescriptions.has(option.description) && !seenLabels.has(option.label)) { | |
| seenDescriptions.add(option.description); | |
| seenLabels.add(option.label); | |
| options.push(option); | |
| } | |
| }; | |
| const defaultOption = await Promise.resolve(getDefaultGoOption()); | |
| const goSDKOptions = await getSDKGoOptions(); | |
| const local: GoEnvironmentOption[] = []; | |
| addOption(local, defaultOption); | |
| goSDKOptions.forEach((option) => addOption(local, option)); | |
| if (local.length > 0) { | |
| options.push({ kind: vscode.QuickPickItemKind.Separator, label: 'Locally discovered' }); | |
| options.push(...local); | |
| } | |
| const downloadableOptions = await getDownloadableGoVersions(); | |
| const downloadable: GoEnvironmentOption[] = []; | |
| downloadableOptions.forEach((option) => addOption(downloadable, option)); | |
| if (downloadable.length > 0) { | |
| options.push({ kind: vscode.QuickPickItemKind.Separator, label: 'Downloadable' }); | |
| options.push(...downloadable); | |
| } | |
| } catch (e) { | |
| vscode.window.showErrorMessage((e as Error).message); | |
| return; | |
| } | |
| // Get user's selection, return if none was made. | |
| const selection = await vscode.window.showQuickPick<vscode.QuickPickItem>(options); | |
| if (!selection) { | |
| return; | |
| } | |
| // Update currently selected go. | |
| try { | |
| await setSelectedGo(selection); | |
| } catch (e) { | |
| vscode.window.showErrorMessage((e as Error).message); | |
| } | |
| }; | |
| /** | |
| * Updates the selected go path and label in the workspace state. | |
| * @returns true if set successfully, false otherwise. | |
| */ | |
| export async function setSelectedGo(goOption: vscode.QuickPickItem, promptReload = true): Promise<boolean> { | |
| if (!goOption) { | |
| return false; | |
| } | |
| // If the selected go version is not installed, install it. | |
| if (goOption instanceof GoEnvironmentOption) { | |
| const o = goOption.available ? (goOption as GoEnvironmentOption) : await downloadGo(goOption); | |
| // Check that the given binary is not already at the beginning of the PATH. | |
| const go = await getGoVersion(); | |
| if (!!go && (go.binaryPath === o.binpath || 'Go ' + go.format() === o.label)) { | |
| return false; | |
| } | |
| await updateWorkspaceState('selectedGo', o); | |
| } else if (goOption.label === CLEAR_SELECTION) { | |
| if (!getSelectedGo()) { | |
| return false; // do nothing. | |
| } | |
| await updateWorkspaceState('selectedGo', undefined); | |
| } else if (goOption.label === CHOOSE_FROM_FILE_BROWSER) { | |
| const currentGOROOT = getCurrentGoRoot(); | |
| const defaultUri = currentGOROOT ? vscode.Uri.file(path.join(currentGOROOT, 'bin')) : undefined; | |
| const newGoUris = await vscode.window.showOpenDialog({ | |
| canSelectFiles: true, | |
| canSelectFolders: false, | |
| canSelectMany: false, | |
| defaultUri | |
| }); | |
| if (!newGoUris || newGoUris.length !== 1) { | |
| return false; | |
| } | |
| const newGoBin = fixDriveCasingInWindows(newGoUris[0].fsPath); | |
| const oldGoBin = fixDriveCasingInWindows(path.join(defaultUri?.fsPath ?? '', correctBinname('go'))); | |
| if (newGoBin === oldGoBin) { | |
| return false; | |
| } | |
| if (!executableFileExists(newGoBin)) { | |
| vscode.window.showErrorMessage(`${newGoBin} is not an executable`); | |
| return false; | |
| } | |
| let newGo: GoVersion | undefined; | |
| try { | |
| newGo = await getGoVersion(newGoBin); | |
| await updateWorkspaceState('selectedGo', new GoEnvironmentOption(newGo.binaryPath, formatGoVersion(newGo))); | |
| } catch (e) { | |
| if (!newGo || !newGo.isValid()) { | |
| vscode.window.showErrorMessage(`failed to get "${newGoBin} version", invalid Go binary:\n${e}`); | |
| return false; | |
| } | |
| } | |
| } | |
| // Show modal dialog to the user to reload the window, this require user's | |
| // immediate attention. | |
| // promptReload defaults to true and should only be false for tests. | |
| if (promptReload) { | |
| const choice = await vscode.window.showWarningMessage( | |
| 'Please reload the window to finish applying Go version changes.', | |
| { | |
| modal: true | |
| }, | |
| 'Reload Window' | |
| ); | |
| if (choice === 'Reload Window') { | |
| await vscode.commands.executeCommand('workbench.action.reloadWindow'); | |
| } | |
| } | |
| goEnvStatusbarItem.text = 'Go: reload required'; | |
| goEnvStatusbarItem.command = 'workbench.action.reloadWindow'; | |
| return true; | |
| } | |
| /** | |
| * Downloads the specified go version available in dl.golang.org. | |
| */ | |
| async function downloadGo(goOption: GoEnvironmentOption): Promise<GoEnvironmentOption> { | |
| if (goOption.available) { | |
| return Promise.resolve(goOption); | |
| } | |
| const execFile = promisify(cp.execFile); | |
| const newExecutableName = goOption.binpath.split('/').splice(-1)[0]; | |
| return await vscode.window.withProgress( | |
| { | |
| title: `Downloading ${goOption.label}`, | |
| location: vscode.ProgressLocation.Notification | |
| }, | |
| async () => { | |
| outputChannel.info(`go install ${goOption.binpath}@latest`); | |
| const result = await installTool({ | |
| name: newExecutableName, | |
| importPath: goOption.binpath, | |
| modulePath: goOption.binpath, | |
| description: newExecutableName, | |
| isImportant: false | |
| }); | |
| if (result) { | |
| outputChannel.error(`Error installing ${goOption.binpath}: ${result}`); | |
| throw new Error(`Could not install ${goOption.binpath} - check logs in the "Go" output channel`); | |
| } | |
| // run `goX.X download` | |
| const goXExecutable = getBinPath(newExecutableName); | |
| outputChannel.info(`${goXExecutable} download`); | |
| try { | |
| await execFile(goXExecutable, ['download']); | |
| } catch (downloadErr) { | |
| outputChannel.error(`Error finishing installation: ${downloadErr}`); | |
| throw new Error('Could not download Go version.'); | |
| } | |
| outputChannel.info(`Checking newly downloaded ${goOption.label} SDK`); | |
| let sdkPath = ''; | |
| try { | |
| const { stdout } = await execFile(goXExecutable, ['env', 'GOROOT'], { | |
| env: toolInstallationEnvironment() | |
| }); | |
| if (stdout) { | |
| sdkPath = stdout.trim(); | |
| } | |
| } catch (downloadErr) { | |
| outputChannel.error(`Error finishing installation: ${downloadErr}`); | |
| throw new Error('Could not download Go version.'); | |
| } | |
| if (!sdkPath || !(await dirExists(sdkPath))) { | |
| outputChannel.error(`SDK path does not exist: ${sdkPath}`); | |
| throw new Error(`SDK path does not exist: ${sdkPath}`); | |
| } | |
| outputChannel.info(`${goOption.label} is available in ${sdkPath}`); | |
| const binpath = path.join(sdkPath, 'bin', correctBinname('go')); | |
| const newOption = new GoEnvironmentOption(binpath, goOption.label); | |
| outputChannel.info('Success!'); | |
| return newOption; | |
| } | |
| ); | |
| } | |
| /** | |
| * PATH value cached before addGoRuntimeBaseToPath modified. | |
| */ | |
| let defaultPathEnv = ''; | |
| function pathEnvVarName(): string | undefined { | |
| if (process.env.hasOwnProperty('PATH')) { | |
| return 'PATH'; | |
| } else if (process.platform === 'win32' && process.env.hasOwnProperty('Path')) { | |
| return 'Path'; | |
| } else { | |
| return; | |
| } | |
| } | |
| /** | |
| * These options overwrite profiles such as `.bashrc`. | |
| * Users who do not want this behavior should set `go.terminal.activateEnvironment` to `false`. | |
| */ | |
| const envVarMutatorOptions: vscode.EnvironmentVariableMutatorOptions = { | |
| applyAtProcessCreation: true, | |
| applyAtShellIntegration: true | |
| }; | |
| /** | |
| * addGoRuntimeBaseToPATH adds the given path to the front of the PATH | |
| * environment variable. It removes duplicates. | |
| * TODO: can we avoid changing PATH but utilize toolExecutionEnv? | |
| */ | |
| export function addGoRuntimeBaseToPATH(newGoRuntimeBase: string) { | |
| if (!newGoRuntimeBase) { | |
| return; | |
| } | |
| const goCfg = getGoConfig(); | |
| if (!goCfg.get('terminal.activateEnvironment')) { | |
| return; | |
| } | |
| const pathEnvVar = pathEnvVarName(); | |
| if (!pathEnvVar) { | |
| outputChannel.debug("couldn't find PATH property in process.env"); | |
| return; | |
| } | |
| if (!defaultPathEnv) { | |
| // cache the default value | |
| defaultPathEnv = <string>process.env[pathEnvVar]; | |
| } | |
| outputChannel.debug(`addGoRuntimeBase(${newGoRuntimeBase}) when PATH=${defaultPathEnv}`); | |
| // Calling this multiple times will override the previous value. | |
| // environmentVariableCollection.clear(); | |
| if (process.platform !== 'darwin') { | |
| environmentVariableCollection?.prepend(pathEnvVar, newGoRuntimeBase + path.delimiter, envVarMutatorOptions); | |
| } else { | |
| // When '-l' or '--login' flags are set, the terminal will invoke a login | |
| // shell again and the paths from the user's login shell will be prepended | |
| // again in front of the path mutated by environmentVariableCollection API. | |
| // That causes the mutated path to be ignored which we don't want. | |
| // So, let's not use the API if those flags are set, but go with the old way | |
| // -- i.e. send the export shell command. | |
| // See the open issue and the discussion here: | |
| // https://github.com/microsoft/vscode/issues/99878#issuecomment-642808852 | |
| const terminalShellArgs = <string[]>( | |
| (vscode.workspace.getConfiguration('terminal.integrated.shellArgs').get('osx') || []) | |
| ); | |
| if (terminalShellArgs.includes('-l') || terminalShellArgs.includes('--login')) { | |
| for (const term of vscode.window.terminals) { | |
| void updateIntegratedTerminal(term); | |
| } | |
| if (terminalCreationListener) { | |
| terminalCreationListener.dispose(); | |
| } | |
| terminalCreationListener = vscode.window.onDidOpenTerminal(updateIntegratedTerminal); | |
| } else { | |
| environmentVariableCollection?.prepend(pathEnvVar, newGoRuntimeBase + path.delimiter, envVarMutatorOptions); | |
| } | |
| } | |
| let pathVars = defaultPathEnv.split(path.delimiter); | |
| pathVars = pathVars.filter((p) => p !== newGoRuntimeBase); | |
| pathVars.unshift(newGoRuntimeBase); | |
| process.env[pathEnvVar] = pathVars.join(path.delimiter); | |
| } | |
| /** | |
| * Clears terminal PATH environment modification previously installed using | |
| * addGoRuntimeBaseToPATH. | |
| * In particular, changes to vscode.EnvironmentVariableCollection persist across | |
| * vscode sessions, so when we decide not to mutate PATH, we need to clear the | |
| * preexisting changes. | |
| */ | |
| export function clearGoRuntimeBaseFromPATH() { | |
| if (terminalCreationListener) { | |
| const l = terminalCreationListener; | |
| terminalCreationListener = undefined; | |
| l.dispose(); | |
| } | |
| const pathEnvVar = pathEnvVarName(); | |
| if (!pathEnvVar) { | |
| outputChannel.debug("couldn't find PATH property in process.env"); | |
| return; | |
| } | |
| environmentVariableCollection?.delete(pathEnvVar); | |
| } | |
| function isTerminalOptions( | |
| opts: vscode.TerminalOptions | vscode.ExtensionTerminalOptions | |
| ): opts is vscode.TerminalOptions { | |
| return 'shellPath' in opts; | |
| } | |
| /** | |
| * Updates the PATH variable in the given terminal to default to the currently | |
| * selected Go. | |
| */ | |
| export async function updateIntegratedTerminal(terminal: vscode.Terminal): Promise<void> { | |
| if ( | |
| !terminal || | |
| // Don't interfere if this terminal was created to run a Go task (goTaskProvider.ts). | |
| // Go task uses ProcessExecution which results in the terminal having `go` or `go.exe` | |
| // as its shellPath. | |
| (isTerminalOptions(terminal.creationOptions) && | |
| path.basename(terminal.creationOptions.shellPath || '') === correctBinname('go')) | |
| ) { | |
| return; | |
| } | |
| const gorootBin = path.join(getCurrentGoRoot(), 'bin'); | |
| const defaultGoRuntime = getBinPathFromEnvVar('go', defaultPathEnv, false); | |
| if (defaultGoRuntime && gorootBin === path.dirname(defaultGoRuntime)) { | |
| return; | |
| } | |
| // Append the goroot to the beginning of the PATH so it takes precedence. | |
| // TODO: add support for more terminal names | |
| if (vscode.env.shell.search(/(powershell|pwsh)$/i) !== -1) { | |
| terminal.sendText(`$env:Path="${gorootBin};$env:Path"`, true); | |
| terminal.sendText('clear'); | |
| } else if (vscode.env.shell.search(/fish$/i) !== -1) { | |
| terminal.sendText(`set -gx PATH ${gorootBin} $PATH`); | |
| terminal.sendText('clear'); | |
| } else if (vscode.env.shell.search(/\/(bash|sh|zsh|ksh)$/i) !== -1) { | |
| terminal.sendText(`export PATH=${gorootBin}:$PATH`, true); | |
| terminal.sendText('clear'); | |
| } | |
| } | |
| /** | |
| * Retreives the current selected Go from the workspace state. | |
| */ | |
| export function getSelectedGo(): GoEnvironmentOption { | |
| return getFromWorkspaceState('selectedGo'); | |
| } | |
| /** | |
| * @returns reference to the statusbar item. | |
| */ | |
| export function getGoEnvironmentStatusbarItem(): vscode.StatusBarItem { | |
| return goEnvStatusbarItem; | |
| } | |
| export function formatGoVersion(version?: GoVersion): string { | |
| if (!version || !version.isValid()) { | |
| return '(unknown)'; | |
| } | |
| const versionStr = version.format(true); | |
| const versionWords = versionStr.split(' '); | |
| if (versionWords.length > 1 && versionWords[0] === 'devel') { | |
| // go devel +hash or go devel go1.17-hash | |
| return versionWords[1].startsWith('go') ? `${versionWords[1].slice(2)}` : `${versionWords[1]}`; | |
| } else { | |
| return `${versionWords[0]}`; | |
| } | |
| } | |
| /** | |
| * @returns go versions available in `$HOME/sdk`. | |
| */ | |
| async function getSDKGoOptions(): Promise<GoEnvironmentOption[]> { | |
| // Get list of Go versions. | |
| const sdkPath = path.join(os.homedir(), 'sdk'); | |
| if (!(await dirExists(sdkPath))) { | |
| return []; | |
| } | |
| const readdir = promisify(fs.readdir); | |
| const subdirs = await readdir(sdkPath); | |
| const stat = promisify(fs.stat); | |
| // The dir happens to be the version, which will be used as the label. | |
| // The path is assembled and used as the description. | |
| const options = await Promise.all( | |
| subdirs.map(async (dir) => { | |
| const binpath = path.join(sdkPath, dir, 'bin', correctBinname('go')); | |
| try { | |
| if ((await stat(binpath)).isFile()) { | |
| return new GoEnvironmentOption(binpath, dir.replace('go', 'Go ')); | |
| } | |
| } catch { | |
| // ignore | |
| } | |
| }) | |
| ); | |
| return options.filter(Boolean) as GoEnvironmentOption[]; | |
| } | |
| export async function getDefaultGoOption(): Promise<GoEnvironmentOption | undefined> { | |
| // Make goroot default to "go.goroot" in vscode-go settings. | |
| const goroot = getCurrentGoRoot(); | |
| if (!goroot) { | |
| return undefined; | |
| } | |
| // Set Go version and command. | |
| const version = await getGoVersion(); | |
| return new GoEnvironmentOption(path.join(goroot, 'bin', correctBinname('go')), formatGoVersion(version)); | |
| } | |
| /** | |
| * Makes a web request to get versions of Go. | |
| */ | |
| interface GoVersionWebResult { | |
| version: string; | |
| stable: boolean; | |
| } | |
| /** | |
| * @returns downloadable go versions from `golang.org/dl`. | |
| */ | |
| async function getDownloadableGoVersions(): Promise<GoEnvironmentOption[]> { | |
| // TODO: use `go list -m --versions -json go` when go1.20+ is the minimum supported version. | |
| // fetch information about what Go versions are available to install | |
| let webResults; | |
| try { | |
| const response = await fetch('https://go.dev/dl/?mode=json'); | |
| webResults = (await response.json()) as GoVersionWebResult[]; | |
| } catch { | |
| return []; | |
| } | |
| if (!webResults) { | |
| return []; | |
| } | |
| // Turn the web result into GoEnvironmentOption model. | |
| return webResults.reduce((opts: GoEnvironmentOption[], result: GoVersionWebResult) => { | |
| // TODO: allow downloading from different sites | |
| const dlPath = `golang.org/dl/${result.version}`; | |
| const label = result.version.replace('go', 'Go '); | |
| return [...opts, new GoEnvironmentOption(dlPath, label, false)]; | |
| }, []); | |
| } | |
| export const latestGoVersionKey = 'latestGoVersions'; | |
| const oneday = 60 * 60 * 24 * 1000; // 24 hours in milliseconds | |
| export async function getLatestGoVersions(): Promise<GoEnvironmentOption[]> { | |
| const timeout = oneday; | |
| const now = moment.now(); | |
| let results: GoEnvironmentOption[]; | |
| // Check if we can use cached results | |
| const cachedResults = getFromGlobalState(latestGoVersionKey); | |
| if (cachedResults && now - cachedResults.timestamp < timeout) { | |
| results = cachedResults.goVersions; | |
| } else { | |
| // Fetch the latest supported Go versions. | |
| try { | |
| // Fetch the latest Go versions and cache the results. | |
| results = await getDownloadableGoVersions(); | |
| await updateGlobalState(latestGoVersionKey, { | |
| timestamp: now, | |
| goVersions: results | |
| }); | |
| } catch { | |
| results = []; | |
| } | |
| } | |
| return results; | |
| } | |
| const STATUS_BAR_ITEM_NAME = 'Go Update Notification'; | |
| /** | |
| * Key for the global state that tracks Go versions for which the user has | |
| * explicitly dismissed the upgrade notification. | |
| */ | |
| const DISMISSED_GO_VERSION_KEY = 'dismissedGoVersionUpdates'; | |
| export async function offerToInstallLatestGoVersion(ctx: Pick<vscode.ExtensionContext, 'subscriptions'>) { | |
| if (extensionInfo.isInCloudIDE) { | |
| // TODO: As we use the language status bar, the notification is less visible | |
| // and we can consider to remove this condition check. | |
| return; | |
| } | |
| const goConfig = getGoConfig(); | |
| const checkForUpdate = getCheckForToolsUpdatesConfig(goConfig); | |
| if (checkForUpdate === 'off' || checkForUpdate === 'local') { | |
| // 'proxy' or misconfiguration.. | |
| return; | |
| } | |
| let latestVersions = await getLatestGoVersions(); | |
| const currentVersion = await getGoVersion(); | |
| // The official support for vscode-go is last three minor versions of Go. | |
| // Try to start with last four minor versions. | |
| let minimumVersion = semverCoerce(latestVersions[0].label); | |
| if (minimumVersion) { | |
| minimumVersion.minor = minimumVersion.minor - 3; | |
| minimumVersion.patch = 0; | |
| } | |
| const download = { | |
| title: 'Download', | |
| async command() { | |
| await vscode.env.openExternal(vscode.Uri.parse('https://go.dev/dl/')); | |
| } | |
| }; | |
| // Popup if the Go version is beyond support. | |
| if (minimumVersion && currentVersion.lt(minimumVersion.format())) { | |
| let text = `The minimum supported Go version is ${minimumVersion.format()}. Please update your Go to ensure the extension functions correctly. You are currently using ${formatGoVersion( | |
| currentVersion | |
| )}.`; | |
| vscode.window.showInformationMessage(text, download).then((selection) => { | |
| void selection?.command(); | |
| }); | |
| } | |
| // Filter out Go versions the user has already dismissed. | |
| const dismissedVersions: GoEnvironmentOption[] = await getFromGlobalState(DISMISSED_GO_VERSION_KEY); | |
| if (dismissedVersions) { | |
| latestVersions = latestVersions.filter((version) => !dismissedVersions.find((x) => x.label === version.label)); | |
| } | |
| // Filter out Go versions below the current go versions. | |
| if (currentVersion) { | |
| latestVersions = latestVersions.filter((version) => currentVersion.lt(version.label)); | |
| } | |
| // Notify user that there is a newer version of Go available. | |
| if (latestVersions.length > 0) { | |
| ctx.subscriptions.push( | |
| vscode.commands.registerCommand('go.promptforgoinstall', () => { | |
| const neverAgain = { | |
| title: "Don't Show Again", | |
| async command() { | |
| // Mark these versions as seen. | |
| let dismissedVersions: GoEnvironmentOption[] = | |
| await getFromGlobalState(DISMISSED_GO_VERSION_KEY); | |
| if (!dismissedVersions) { | |
| dismissedVersions = []; | |
| } | |
| latestVersions.forEach((version) => { | |
| dismissedVersions.push(version); | |
| }); | |
| await updateGlobalState(DISMISSED_GO_VERSION_KEY, dismissedVersions); | |
| } | |
| }; | |
| let text: string; | |
| if (latestVersions.length > 1) { | |
| text = `${latestVersions | |
| .map((x) => x.label) | |
| .reduce((prev, next) => { | |
| return prev + ' and ' + next; | |
| })} are available`; | |
| } else { | |
| text = `${latestVersions[0].label} is available.`; | |
| } | |
| text = text + ` You are currently using ${formatGoVersion(currentVersion)}.`; | |
| vscode.window.showInformationMessage(text, download, neverAgain).then((selection) => { | |
| void selection?.command(); | |
| removeGoStatus(STATUS_BAR_ITEM_NAME); | |
| }); | |
| }) | |
| ); | |
| const statusBarItem = addGoStatus(STATUS_BAR_ITEM_NAME); | |
| statusBarItem.name = STATUS_BAR_ITEM_NAME; | |
| statusBarItem.text = 'New Go version is available'; | |
| statusBarItem.detail = latestVersions.map((x) => x.label).join(', '); | |
| statusBarItem.command = { | |
| title: 'Upgrade', | |
| command: 'go.promptforgoinstall', | |
| arguments: [latestVersions], | |
| tooltip: 'Upgrade or silence notification' | |
| }; | |
| // TODO: Error level is more visible. Consider to make it configurable? | |
| statusBarItem.severity = vscode.LanguageStatusSeverity.Warning; | |
| } | |
| } |