@@ -18,6 +18,7 @@ import { getUserHomeDir } from '../../../../common/utils/platform';
18
18
import { createLogOutputChannel } from '../../../../common/vscodeApis/windowApis' ;
19
19
import { PythonEnvKind } from '../../info' ;
20
20
import { sendNativeTelemetry , NativePythonTelemetry } from './nativePythonTelemetry' ;
21
+ import { traceError } from '../../../../logging' ;
21
22
22
23
const untildify = require ( 'untildify' ) ;
23
24
@@ -29,7 +30,7 @@ export interface NativeEnvInfo {
29
30
displayName ?: string ;
30
31
name ?: string ;
31
32
executable ?: string ;
32
- kind ?: string ;
33
+ kind ?: PythonEnvironmentKind ;
33
34
version ?: string ;
34
35
prefix ?: string ;
35
36
manager ?: NativeEnvManagerInfo ;
@@ -41,12 +42,38 @@ export interface NativeEnvInfo {
41
42
symlinks ?: string [ ] ;
42
43
}
43
44
45
+ export enum PythonEnvironmentKind {
46
+ Conda = 'Conda' ,
47
+ Homebrew = 'Homebrew' ,
48
+ Pyenv = 'Pyenv' ,
49
+ GlobalPaths = 'GlobalPaths' ,
50
+ PyenvVirtualEnv = 'PyenvVirtualEnv' ,
51
+ Pipenv = 'Pipenv' ,
52
+ Poetry = 'Poetry' ,
53
+ MacPythonOrg = 'MacPythonOrg' ,
54
+ MacCommandLineTools = 'MacCommandLineTools' ,
55
+ LinuxGlobal = 'LinuxGlobal' ,
56
+ MacXCode = 'MacXCode' ,
57
+ Venv = 'Venv' ,
58
+ VirtualEnv = 'VirtualEnv' ,
59
+ VirtualEnvWrapper = 'VirtualEnvWrapper' ,
60
+ WindowsStore = 'WindowsStore' ,
61
+ WindowsRegistry = 'WindowsRegistry' ,
62
+ }
63
+
44
64
export interface NativeEnvManagerInfo {
45
65
tool : string ;
46
66
executable : string ;
47
67
version ?: string ;
48
68
}
49
69
70
+ export function isNativeInfoEnvironment ( info : NativeEnvInfo | NativeEnvManagerInfo ) : info is NativeEnvInfo {
71
+ if ( ( info as NativeEnvManagerInfo ) . tool ) {
72
+ return false ;
73
+ }
74
+ return true ;
75
+ }
76
+
50
77
export type NativeCondaInfo = {
51
78
canSpawnConda : boolean ;
52
79
userProvidedEnvFound ?: boolean ;
@@ -58,12 +85,62 @@ export type NativeCondaInfo = {
58
85
} ;
59
86
60
87
export interface NativePythonFinder extends Disposable {
88
+ /**
89
+ * Refresh the list of python environments.
90
+ * Returns an async iterable that can be used to iterate over the list of python environments.
91
+ * Internally this will take all of the current workspace folders and search for python environments.
92
+ *
93
+ * If a Uri is provided, then it will search for python environments in that location (ignoring workspaces).
94
+ * Uri can be a file or a folder.
95
+ * If a PythonEnvironmentKind is provided, then it will search for python environments of that kind (ignoring workspaces).
96
+ */
97
+ refresh ( options ?: PythonEnvironmentKind | Uri [ ] ) : AsyncIterable < NativeEnvInfo | NativeEnvManagerInfo > ;
98
+ /**
99
+ * Will spawn the provided Python executable and return information about the environment.
100
+ * @param executable
101
+ */
61
102
resolve ( executable : string ) : Promise < NativeEnvInfo > ;
62
- refresh ( ) : AsyncIterable < NativeEnvInfo > ;
63
- categoryToKind ( category ?: string ) : PythonEnvKind ;
64
- logger ( ) : LogOutputChannel ;
103
+ categoryToKind ( category ?: PythonEnvironmentKind ) : PythonEnvKind ;
104
+ /**
105
+ * Used only for telemetry.
106
+ */
65
107
getCondaInfo ( ) : Promise < NativeCondaInfo > ;
66
- find ( searchPath : string ) : Promise < NativeEnvInfo [ ] > ;
108
+ }
109
+
110
+ const mapping = new Map < PythonEnvironmentKind , PythonEnvKind > ( [
111
+ [ PythonEnvironmentKind . Conda , PythonEnvKind . Conda ] ,
112
+ [ PythonEnvironmentKind . GlobalPaths , PythonEnvKind . OtherGlobal ] ,
113
+ [ PythonEnvironmentKind . Pyenv , PythonEnvKind . Pyenv ] ,
114
+ [ PythonEnvironmentKind . PyenvVirtualEnv , PythonEnvKind . Pyenv ] ,
115
+ [ PythonEnvironmentKind . Pipenv , PythonEnvKind . Pipenv ] ,
116
+ [ PythonEnvironmentKind . Poetry , PythonEnvKind . Poetry ] ,
117
+ [ PythonEnvironmentKind . VirtualEnv , PythonEnvKind . VirtualEnv ] ,
118
+ [ PythonEnvironmentKind . VirtualEnvWrapper , PythonEnvKind . VirtualEnvWrapper ] ,
119
+ [ PythonEnvironmentKind . Venv , PythonEnvKind . Venv ] ,
120
+ [ PythonEnvironmentKind . WindowsRegistry , PythonEnvKind . System ] ,
121
+ [ PythonEnvironmentKind . WindowsStore , PythonEnvKind . MicrosoftStore ] ,
122
+ [ PythonEnvironmentKind . Homebrew , PythonEnvKind . System ] ,
123
+ [ PythonEnvironmentKind . LinuxGlobal , PythonEnvKind . System ] ,
124
+ [ PythonEnvironmentKind . MacCommandLineTools , PythonEnvKind . System ] ,
125
+ [ PythonEnvironmentKind . MacPythonOrg , PythonEnvKind . System ] ,
126
+ [ PythonEnvironmentKind . MacXCode , PythonEnvKind . System ] ,
127
+ ] ) ;
128
+
129
+ export function categoryToKind ( category ?: PythonEnvironmentKind , logger ?: LogOutputChannel ) : PythonEnvKind {
130
+ if ( ! category ) {
131
+ return PythonEnvKind . Unknown ;
132
+ }
133
+ const kind = mapping . get ( category ) ;
134
+ if ( kind ) {
135
+ return kind ;
136
+ }
137
+
138
+ if ( logger ) {
139
+ logger . error ( `Unknown Python Environment category '${ category } ' from Native Locator.` ) ;
140
+ } else {
141
+ traceError ( `Unknown Python Environment category '${ category } ' from Native Locator.` ) ;
142
+ }
143
+ return PythonEnvKind . Unknown ;
67
144
}
68
145
69
146
interface NativeLog {
@@ -94,47 +171,11 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
94
171
return environment ;
95
172
}
96
173
97
- categoryToKind ( category ?: string ) : PythonEnvKind {
98
- if ( ! category ) {
99
- return PythonEnvKind . Unknown ;
100
- }
101
- switch ( category . toLowerCase ( ) ) {
102
- case 'conda' :
103
- return PythonEnvKind . Conda ;
104
- case 'system' :
105
- case 'homebrew' :
106
- case 'macpythonorg' :
107
- case 'maccommandlinetools' :
108
- case 'macxcode' :
109
- case 'windowsregistry' :
110
- case 'linuxglobal' :
111
- return PythonEnvKind . System ;
112
- case 'globalpaths' :
113
- return PythonEnvKind . OtherGlobal ;
114
- case 'pyenv' :
115
- return PythonEnvKind . Pyenv ;
116
- case 'poetry' :
117
- return PythonEnvKind . Poetry ;
118
- case 'pipenv' :
119
- return PythonEnvKind . Pipenv ;
120
- case 'pyenvvirtualenv' :
121
- return PythonEnvKind . VirtualEnv ;
122
- case 'venv' :
123
- return PythonEnvKind . Venv ;
124
- case 'virtualenv' :
125
- return PythonEnvKind . VirtualEnv ;
126
- case 'virtualenvwrapper' :
127
- return PythonEnvKind . VirtualEnvWrapper ;
128
- case 'windowsstore' :
129
- return PythonEnvKind . MicrosoftStore ;
130
- default : {
131
- this . outputChannel . info ( `Unknown Python Environment category '${ category } ' from Native Locator.` ) ;
132
- return PythonEnvKind . Unknown ;
133
- }
134
- }
174
+ categoryToKind ( category ?: PythonEnvironmentKind ) : PythonEnvKind {
175
+ return categoryToKind ( category , this . outputChannel ) ;
135
176
}
136
177
137
- async * refresh ( ) : AsyncIterable < NativeEnvInfo > {
178
+ async * refresh ( options ?: PythonEnvironmentKind | Uri [ ] ) : AsyncIterable < NativeEnvInfo > {
138
179
if ( this . firstRefreshResults ) {
139
180
// If this is the first time we are refreshing,
140
181
// Then get the results from the first refresh.
@@ -143,12 +184,12 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
143
184
this . firstRefreshResults = undefined ;
144
185
yield * results ;
145
186
} else {
146
- const result = this . doRefresh ( ) ;
187
+ const result = this . doRefresh ( options ) ;
147
188
let completed = false ;
148
189
void result . completed . finally ( ( ) => {
149
190
completed = true ;
150
191
} ) ;
151
- const envs : NativeEnvInfo [ ] = [ ] ;
192
+ const envs : ( NativeEnvInfo | NativeEnvManagerInfo ) [ ] = [ ] ;
152
193
let discovered = createDeferred ( ) ;
153
194
const disposable = result . discovered ( ( data ) => {
154
195
envs . push ( data ) ;
@@ -173,10 +214,6 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
173
214
}
174
215
}
175
216
176
- logger ( ) : LogOutputChannel {
177
- return this . outputChannel ;
178
- }
179
-
180
217
refreshFirstTime ( ) {
181
218
const result = this . doRefresh ( ) ;
182
219
const completed = createDeferredFrom ( result . completed ) ;
@@ -283,9 +320,11 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
283
320
return connection ;
284
321
}
285
322
286
- private doRefresh ( ) : { completed : Promise < void > ; discovered : Event < NativeEnvInfo > } {
323
+ private doRefresh (
324
+ options ?: PythonEnvironmentKind | Uri [ ] ,
325
+ ) : { completed : Promise < void > ; discovered : Event < NativeEnvInfo | NativeEnvManagerInfo > } {
287
326
const disposable = this . _register ( new DisposableStore ( ) ) ;
288
- const discovered = disposable . add ( new EventEmitter < NativeEnvInfo > ( ) ) ;
327
+ const discovered = disposable . add ( new EventEmitter < NativeEnvInfo | NativeEnvManagerInfo > ( ) ) ;
289
328
const completed = createDeferred < void > ( ) ;
290
329
const pendingPromises : Promise < void > [ ] = [ ] ;
291
330
@@ -306,6 +345,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
306
345
notifyUponCompletion ( ) ;
307
346
} ;
308
347
348
+ // Assumption is server will ensure there's only one refresh at a time.
349
+ // Perhaps we should have a request Id or the like to map the results back to the `refresh` request.
309
350
disposable . add (
310
351
this . connection . onNotification ( 'environment' , ( data : NativeEnvInfo ) => {
311
352
this . outputChannel . info ( `Discovered env: ${ data . executable || data . prefix } ` ) ;
@@ -334,11 +375,28 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
334
375
}
335
376
} ) ,
336
377
) ;
378
+ disposable . add (
379
+ this . connection . onNotification ( 'manager' , ( data : NativeEnvManagerInfo ) => {
380
+ this . outputChannel . info ( `Discovered manager: (${ data . tool } ) ${ data . executable } ` ) ;
381
+ discovered . fire ( data ) ;
382
+ } ) ,
383
+ ) ;
337
384
385
+ type RefreshOptions = {
386
+ searchKind ?: PythonEnvironmentKind ;
387
+ searchPaths ?: string [ ] ;
388
+ } ;
389
+
390
+ const refreshOptions : RefreshOptions = { } ;
391
+ if ( options && Array . isArray ( options ) && options . length > 0 ) {
392
+ refreshOptions . searchPaths = options . map ( ( item ) => item . fsPath ) ;
393
+ } else if ( options && typeof options === 'string' ) {
394
+ refreshOptions . searchKind = options ;
395
+ }
338
396
trackPromiseAndNotifyOnCompletion (
339
397
this . configure ( ) . then ( ( ) =>
340
398
this . connection
341
- . sendRequest < { duration : number } > ( 'refresh' )
399
+ . sendRequest < { duration : number } > ( 'refresh' , refreshOptions )
342
400
. then ( ( { duration } ) => this . outputChannel . info ( `Refresh completed in ${ duration } ms` ) )
343
401
. catch ( ( ex ) => this . outputChannel . error ( 'Refresh error' , ex ) ) ,
344
402
) ,
0 commit comments