@@ -8,16 +8,19 @@ import cp = require('child_process');
8
8
import util = require( 'util' ) ;
9
9
import os = require( 'os' ) ;
10
10
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' ;
13
13
import { toolExecutionEnvironment } from './goEnv' ;
14
+ import { getConfiguredTools } from './goTools' ;
15
+ import { inspectGoToolVersion } from './goInstallTools' ;
14
16
15
17
/**
16
18
* GoExplorerProvider provides data for the Go tree view in the Explorer
17
19
* Tree View Container.
18
20
*/
19
21
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 ) ;
21
24
private activeFolder ?: vscode . WorkspaceFolder ;
22
25
private activeDocument ?: vscode . TextDocument ;
23
26
@@ -51,15 +54,24 @@ export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeIt
51
54
}
52
55
53
56
getChildren ( element ?: vscode . TreeItem ) {
57
+ if ( ! element ) {
58
+ return [ this . envTree ( ) , this . toolTree ( ) ] ;
59
+ }
54
60
if ( isEnvTree ( element ) ) {
55
61
return this . envTreeItems ( element . workspace ) ;
56
62
}
57
- return [ this . envTree ( ) ] ;
63
+ if ( isToolTree ( element ) ) {
64
+ return this . toolTreeItems ( ) ;
65
+ }
66
+ if ( isToolTreeItem ( element ) ) {
67
+ return element . children ;
68
+ }
58
69
}
59
70
60
71
private update ( clearCache = false ) {
61
72
if ( clearCache ) {
62
73
this . goEnvCache . clear ( ) ;
74
+ this . toolDetailCache . clear ( ) ;
63
75
}
64
76
const { activeTextEditor } = vscode . window ;
65
77
const { getWorkspaceFolder, workspaceFolders } = vscode . workspace ;
@@ -104,10 +116,21 @@ export class GoExplorerProvider implements vscode.TreeDataProvider<vscode.TreeIt
104
116
}
105
117
return items ;
106
118
}
107
- }
108
119
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
+ }
111
134
}
112
135
113
136
class EnvTree implements vscode . TreeItem {
@@ -118,13 +141,17 @@ class EnvTree implements vscode.TreeItem {
118
141
constructor ( public description = '' , public workspace ?: vscode . Uri ) { }
119
142
}
120
143
144
+ function isEnvTree ( item ?: vscode . TreeItem ) : item is EnvTree {
145
+ return item ?. contextValue === 'go:explorer:env' ;
146
+ }
147
+
121
148
class EnvTreeItem implements vscode . TreeItem {
122
149
file ?: vscode . Uri ;
123
150
label : string ;
124
151
contextValue ?: string ;
125
152
tooltip ?: string ;
126
153
constructor ( public key : string , public value : string ) {
127
- this . label = `${ key } =${ value . replace ( new RegExp ( `^ ${ os . homedir ( ) } ` ) , '~' ) } ` ;
154
+ this . label = `${ key } =${ replaceHome ( value ) } ` ;
128
155
this . contextValue = 'go:explorer:envitem' ;
129
156
if ( GoEnv . fileVars . includes ( key ) ) {
130
157
this . contextValue = 'go:explorer:envitem:file' ;
@@ -188,6 +215,88 @@ class GoEnv {
188
215
}
189
216
}
190
217
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
+
191
300
interface CacheEntry < T > {
192
301
entry : T ;
193
302
updatedAt : number ;
@@ -217,3 +326,12 @@ class Cache<T> {
217
326
return this . cache . delete ( key ) ;
218
327
}
219
328
}
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