src/goExplorer.ts: display tools configuration detail in explorer view
Added a tree item to the Go explorer view that displays
the status and version detail of installed tools.
Snapshot: https://drive.google.com/file/d/17s1qWzl-7slTHLIfovfNZqQERveVVcAN/view?usp=sharing
For golang/vscode-go#2049
Change-Id: I074a4a087db56e5e0cdcfcf8a90d234d083dc483
Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/388754
Trust: Jamal Carvalho <jamal@golang.org>
Run-TryBot: Jamal Carvalho <jamal@golang.org>
Reviewed-by: Hyang-Ah Hana Kim <hyangah@gmail.com>
Reviewed-by: Polina Sokolova <polina@google.com>
TryBot-Result: kokoro <noreply+kokoro@google.com>
diff --git a/src/goExplorer.ts b/src/goExplorer.ts
index a11bce2..aa1abff 100644
--- a/src/goExplorer.ts
+++ b/src/goExplorer.ts
@@ -8,16 +8,19 @@
import util = require('util');
import os = require('os');
import path = require('path');
-import { getGoConfig } from './config';
-import { getBinPath } from './util';
+import { getGoConfig, getGoplsConfig } from './config';
+import { getBinPath, getGoVersion } from './util';
import { toolExecutionEnvironment } from './goEnv';
+import { getConfiguredTools } from './goTools';
+import { inspectGoToolVersion } from './goInstallTools';
/**
* GoExplorerProvider provides data for the Go tree view in the Explorer
* Tree View Container.
*/
export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
- private goEnvCache = new Cache((uri) => GoEnv.get(uri ? vscode.Uri.parse(uri) : undefined), 1000 * 60);
+ private goEnvCache = new Cache((uri) => GoEnv.get(uri ? vscode.Uri.parse(uri) : undefined), Time.MINUTE);
+ private toolDetailCache = new Cache((name) => getToolDetail(name), Time.HOUR);
private activeFolder?: vscode.WorkspaceFolder;
private activeDocument?: vscode.TextDocument;
@@ -51,15 +54,24 @@
}
getChildren(element?: vscode.TreeItem) {
+ if (!element) {
+ return [this.envTree(), this.toolTree()];
+ }
if (isEnvTree(element)) {
return this.envTreeItems(element.workspace);
}
- return [this.envTree()];
+ if (isToolTree(element)) {
+ return this.toolTreeItems();
+ }
+ if (isToolTreeItem(element)) {
+ return element.children;
+ }
}
private update(clearCache = false) {
if (clearCache) {
this.goEnvCache.clear();
+ this.toolDetailCache.clear();
}
const { activeTextEditor } = vscode.window;
const { getWorkspaceFolder, workspaceFolders } = vscode.workspace;
@@ -104,10 +116,21 @@
}
return items;
}
-}
-function isEnvTree(item?: vscode.TreeItem): item is EnvTree {
- return item?.contextValue === 'go:explorer:env';
+ private toolTree() {
+ return new ToolTree();
+ }
+
+ private async toolTreeItems() {
+ const goVersion = await getGoVersion();
+ const allTools = getConfiguredTools(goVersion, getGoConfig(), getGoplsConfig());
+ const toolsInfo = await Promise.all(allTools.map((tool) => this.toolDetailCache.get(tool.name)));
+ const items = [];
+ for (const t of toolsInfo) {
+ items.push(new ToolTreeItem(t));
+ }
+ return items;
+ }
}
class EnvTree implements vscode.TreeItem {
@@ -118,13 +141,17 @@
constructor(public description = '', public workspace?: vscode.Uri) {}
}
+function isEnvTree(item?: vscode.TreeItem): item is EnvTree {
+ return item?.contextValue === 'go:explorer:env';
+}
+
class EnvTreeItem implements vscode.TreeItem {
file?: vscode.Uri;
label: string;
contextValue?: string;
tooltip?: string;
constructor(public key: string, public value: string) {
- this.label = `${key}=${value.replace(new RegExp(`^${os.homedir()}`), '~')}`;
+ this.label = `${key}=${replaceHome(value)}`;
this.contextValue = 'go:explorer:envitem';
if (GoEnv.fileVars.includes(key)) {
this.contextValue = 'go:explorer:envitem:file';
@@ -188,6 +215,88 @@
}
}
+class ToolTree implements vscode.TreeItem {
+ label = 'tools';
+ contextValue = 'go:explorer:tools';
+ collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
+ iconPath = new vscode.ThemeIcon('package');
+}
+
+function isToolTree(item?: vscode.TreeItem): item is ToolTree {
+ return item?.contextValue === 'go:explorer:tools';
+}
+
+class ToolTreeItem implements vscode.TreeItem {
+ contextValue = 'go:explorer:toolitem';
+ description = 'not installed';
+ label: string;
+ children: vscode.TreeItem[];
+ collapsibleState?: vscode.TreeItemCollapsibleState;
+ tooltip: string;
+ constructor({ name, version, goVersion, binPath, error }: ToolDetail) {
+ this.label = name;
+ if (binPath) {
+ this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
+ this.children = [new ToolDetailTreeItem(binPath, goVersion)];
+ this.description = version;
+ this.tooltip = `${name}@${version}`;
+ }
+ if (error) {
+ const msg = `go version -m failed: ${error}`;
+ this.description = msg;
+ this.tooltip = msg;
+ }
+ }
+}
+
+function isToolTreeItem(item?: vscode.TreeItem): item is ToolTreeItem {
+ return item?.contextValue === 'go:explorer:toolitem';
+}
+
+class ToolDetailTreeItem implements vscode.TreeItem {
+ contextValue = 'go:explorer:tooldetail';
+ label: string;
+ description: string;
+ tooltip: string;
+ constructor(bin: string, goVersion: string) {
+ this.label = replaceHome(bin);
+ this.description = goVersion;
+ this.tooltip = `${bin} ${goVersion}`;
+ }
+}
+
+interface ToolDetail {
+ name: string;
+ goVersion?: string;
+ version?: string;
+ binPath?: string;
+ error?: Error;
+}
+
+async function getToolDetail(name: string): Promise<ToolDetail> {
+ const toolPath = getBinPath(name);
+ if (!path.isAbsolute(toolPath)) {
+ return { name: name };
+ }
+ try {
+ const { goVersion, moduleVersion } = await inspectGoToolVersion(toolPath);
+ return {
+ name: name,
+ binPath: toolPath,
+ goVersion: goVersion,
+ version: moduleVersion
+ };
+ } catch (e) {
+ return { name: name, error: e };
+ }
+}
+
+const enum Time {
+ SECOND = 1000,
+ MINUTE = SECOND * 60,
+ HOUR = MINUTE * 60
+}
+
interface CacheEntry<T> {
entry: T;
updatedAt: number;
@@ -217,3 +326,12 @@
return this.cache.delete(key);
}
}
+
+/**
+ * replaceHome replaces the home directory prefix of a string with `~`.
+ * @param maybePath a string that might be a file system path.
+ * @returns the string with os.homedir() replaced by `~`.
+ */
+function replaceHome(maybePath: string) {
+ return maybePath.replace(new RegExp(`^${os.homedir()}`), '~');
+}