Skip to content

Commit 5d86420

Browse files
committed
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 #2049 Change-Id: I074a4a087db56e5e0cdcfcf8a90d234d083dc483 Reviewed-on: https://go-review.googlesource.com/c/vscode-go/+/388754 Trust: Jamal Carvalho <[email protected]> Run-TryBot: Jamal Carvalho <[email protected]> Reviewed-by: Hyang-Ah Hana Kim <[email protected]> Reviewed-by: Polina Sokolova <[email protected]> TryBot-Result: kokoro <[email protected]>
1 parent 3f5f0cd commit 5d86420

File tree

1 file changed

+126
-8
lines changed

1 file changed

+126
-8
lines changed

src/goExplorer.ts

+126-8
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,19 @@ import cp = require('child_process');
88
import util = require('util');
99
import os = require('os');
1010
import path = require('path');
11-
import { getGoConfig } from './config';
12-
import { getBinPath } from './util';
11+
import { getGoConfig, getGoplsConfig } from './config';
12+
import { getBinPath, getGoVersion } from './util';
1313
import { toolExecutionEnvironment } from './goEnv';
14+
import { getConfiguredTools } from './goTools';
15+
import { inspectGoToolVersion } from './goInstallTools';
1416

1517
/**
1618
* GoExplorerProvider provides data for the Go tree view in the Explorer
1719
* Tree View Container.
1820
*/
1921
export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeItem> {
20-
private goEnvCache = new Cache((uri) => GoEnv.get(uri ? vscode.Uri.parse(uri) : undefined), 1000 * 60);
22+
private goEnvCache = new Cache((uri) => GoEnv.get(uri ? vscode.Uri.parse(uri) : undefined), Time.MINUTE);
23+
private toolDetailCache = new Cache((name) => getToolDetail(name), Time.HOUR);
2124
private activeFolder?: vscode.WorkspaceFolder;
2225
private activeDocument?: vscode.TextDocument;
2326

@@ -51,15 +54,24 @@ export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeIt
5154
}
5255

5356
getChildren(element?: vscode.TreeItem) {
57+
if (!element) {
58+
return [this.envTree(), this.toolTree()];
59+
}
5460
if (isEnvTree(element)) {
5561
return this.envTreeItems(element.workspace);
5662
}
57-
return [this.envTree()];
63+
if (isToolTree(element)) {
64+
return this.toolTreeItems();
65+
}
66+
if (isToolTreeItem(element)) {
67+
return element.children;
68+
}
5869
}
5970

6071
private update(clearCache = false) {
6172
if (clearCache) {
6273
this.goEnvCache.clear();
74+
this.toolDetailCache.clear();
6375
}
6476
const { activeTextEditor } = vscode.window;
6577
const { getWorkspaceFolder, workspaceFolders } = vscode.workspace;
@@ -104,10 +116,21 @@ export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeIt
104116
}
105117
return items;
106118
}
107-
}
108119

109-
function isEnvTree(item?: vscode.TreeItem): item is EnvTree {
110-
return item?.contextValue === 'go:explorer:env';
120+
private toolTree() {
121+
return new ToolTree();
122+
}
123+
124+
private async toolTreeItems() {
125+
const goVersion = await getGoVersion();
126+
const allTools = getConfiguredTools(goVersion, getGoConfig(), getGoplsConfig());
127+
const toolsInfo = await Promise.all(allTools.map((tool) => this.toolDetailCache.get(tool.name)));
128+
const items = [];
129+
for (const t of toolsInfo) {
130+
items.push(new ToolTreeItem(t));
131+
}
132+
return items;
133+
}
111134
}
112135

113136
class EnvTree implements vscode.TreeItem {
@@ -118,13 +141,17 @@ class EnvTree implements vscode.TreeItem {
118141
constructor(public description = '', public workspace?: vscode.Uri) {}
119142
}
120143

144+
function isEnvTree(item?: vscode.TreeItem): item is EnvTree {
145+
return item?.contextValue === 'go:explorer:env';
146+
}
147+
121148
class EnvTreeItem implements vscode.TreeItem {
122149
file?: vscode.Uri;
123150
label: string;
124151
contextValue?: string;
125152
tooltip?: string;
126153
constructor(public key: string, public value: string) {
127-
this.label = `${key}=${value.replace(new RegExp(`^${os.homedir()}`), '~')}`;
154+
this.label = `${key}=${replaceHome(value)}`;
128155
this.contextValue = 'go:explorer:envitem';
129156
if (GoEnv.fileVars.includes(key)) {
130157
this.contextValue = 'go:explorer:envitem:file';
@@ -188,6 +215,88 @@ class GoEnv {
188215
}
189216
}
190217

218+
class ToolTree implements vscode.TreeItem {
219+
label = 'tools';
220+
contextValue = 'go:explorer:tools';
221+
collapsibleState = vscode.TreeItemCollapsibleState.Expanded;
222+
iconPath = new vscode.ThemeIcon('package');
223+
}
224+
225+
function isToolTree(item?: vscode.TreeItem): item is ToolTree {
226+
return item?.contextValue === 'go:explorer:tools';
227+
}
228+
229+
class ToolTreeItem implements vscode.TreeItem {
230+
contextValue = 'go:explorer:toolitem';
231+
description = 'not installed';
232+
label: string;
233+
children: vscode.TreeItem[];
234+
collapsibleState?: vscode.TreeItemCollapsibleState;
235+
tooltip: string;
236+
constructor({ name, version, goVersion, binPath, error }: ToolDetail) {
237+
this.label = name;
238+
if (binPath) {
239+
this.collapsibleState = vscode.TreeItemCollapsibleState.Collapsed;
240+
this.children = [new ToolDetailTreeItem(binPath, goVersion)];
241+
this.description = version;
242+
this.tooltip = `${name}@${version}`;
243+
}
244+
if (error) {
245+
const msg = `go version -m failed: ${error}`;
246+
this.description = msg;
247+
this.tooltip = msg;
248+
}
249+
}
250+
}
251+
252+
function isToolTreeItem(item?: vscode.TreeItem): item is ToolTreeItem {
253+
return item?.contextValue === 'go:explorer:toolitem';
254+
}
255+
256+
class ToolDetailTreeItem implements vscode.TreeItem {
257+
contextValue = 'go:explorer:tooldetail';
258+
label: string;
259+
description: string;
260+
tooltip: string;
261+
constructor(bin: string, goVersion: string) {
262+
this.label = replaceHome(bin);
263+
this.description = goVersion;
264+
this.tooltip = `${bin} ${goVersion}`;
265+
}
266+
}
267+
268+
interface ToolDetail {
269+
name: string;
270+
goVersion?: string;
271+
version?: string;
272+
binPath?: string;
273+
error?: Error;
274+
}
275+
276+
async function getToolDetail(name: string): Promise<ToolDetail> {
277+
const toolPath = getBinPath(name);
278+
if (!path.isAbsolute(toolPath)) {
279+
return { name: name };
280+
}
281+
try {
282+
const { goVersion, moduleVersion } = await inspectGoToolVersion(toolPath);
283+
return {
284+
name: name,
285+
binPath: toolPath,
286+
goVersion: goVersion,
287+
version: moduleVersion
288+
};
289+
} catch (e) {
290+
return { name: name, error: e };
291+
}
292+
}
293+
294+
const enum Time {
295+
SECOND = 1000,
296+
MINUTE = SECOND * 60,
297+
HOUR = MINUTE * 60
298+
}
299+
191300
interface CacheEntry<T> {
192301
entry: T;
193302
updatedAt: number;
@@ -217,3 +326,12 @@ class Cache<T> {
217326
return this.cache.delete(key);
218327
}
219328
}
329+
330+
/**
331+
* replaceHome replaces the home directory prefix of a string with `~`.
332+
* @param maybePath a string that might be a file system path.
333+
* @returns the string with os.homedir() replaced by `~`.
334+
*/
335+
function replaceHome(maybePath: string) {
336+
return maybePath.replace(new RegExp(`^${os.homedir()}`), '~');
337+
}

0 commit comments

Comments
 (0)