@@ -18,6 +18,7 @@ import { getUserHomeDir } from '../../../../common/utils/platform';
1818import { createLogOutputChannel } from '../../../../common/vscodeApis/windowApis' ;
1919import { PythonEnvKind } from '../../info' ;
2020import { sendNativeTelemetry , NativePythonTelemetry } from './nativePythonTelemetry' ;
21+ import { traceError } from '../../../../logging' ;
2122
2223const untildify = require ( 'untildify' ) ;
2324
@@ -29,7 +30,7 @@ export interface NativeEnvInfo {
2930 displayName ?: string ;
3031 name ?: string ;
3132 executable ?: string ;
32- kind ?: string ;
33+ kind ?: PythonEnvironmentKind ;
3334 version ?: string ;
3435 prefix ?: string ;
3536 manager ?: NativeEnvManagerInfo ;
@@ -41,12 +42,38 @@ export interface NativeEnvInfo {
4142 symlinks ?: string [ ] ;
4243}
4344
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+
4464export interface NativeEnvManagerInfo {
4565 tool : string ;
4666 executable : string ;
4767 version ?: string ;
4868}
4969
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+
5077export type NativeCondaInfo = {
5178 canSpawnConda : boolean ;
5279 userProvidedEnvFound ?: boolean ;
@@ -58,12 +85,62 @@ export type NativeCondaInfo = {
5885} ;
5986
6087export 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+ */
61102 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+ */
65107 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 ;
67144}
68145
69146interface NativeLog {
@@ -94,47 +171,11 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
94171 return environment ;
95172 }
96173
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 ) ;
135176 }
136177
137- async * refresh ( ) : AsyncIterable < NativeEnvInfo > {
178+ async * refresh ( options ?: PythonEnvironmentKind | Uri [ ] ) : AsyncIterable < NativeEnvInfo > {
138179 if ( this . firstRefreshResults ) {
139180 // If this is the first time we are refreshing,
140181 // Then get the results from the first refresh.
@@ -143,12 +184,12 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
143184 this . firstRefreshResults = undefined ;
144185 yield * results ;
145186 } else {
146- const result = this . doRefresh ( ) ;
187+ const result = this . doRefresh ( options ) ;
147188 let completed = false ;
148189 void result . completed . finally ( ( ) => {
149190 completed = true ;
150191 } ) ;
151- const envs : NativeEnvInfo [ ] = [ ] ;
192+ const envs : ( NativeEnvInfo | NativeEnvManagerInfo ) [ ] = [ ] ;
152193 let discovered = createDeferred ( ) ;
153194 const disposable = result . discovered ( ( data ) => {
154195 envs . push ( data ) ;
@@ -173,10 +214,6 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
173214 }
174215 }
175216
176- logger ( ) : LogOutputChannel {
177- return this . outputChannel ;
178- }
179-
180217 refreshFirstTime ( ) {
181218 const result = this . doRefresh ( ) ;
182219 const completed = createDeferredFrom ( result . completed ) ;
@@ -283,9 +320,11 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
283320 return connection ;
284321 }
285322
286- private doRefresh ( ) : { completed : Promise < void > ; discovered : Event < NativeEnvInfo > } {
323+ private doRefresh (
324+ options ?: PythonEnvironmentKind | Uri [ ] ,
325+ ) : { completed : Promise < void > ; discovered : Event < NativeEnvInfo | NativeEnvManagerInfo > } {
287326 const disposable = this . _register ( new DisposableStore ( ) ) ;
288- const discovered = disposable . add ( new EventEmitter < NativeEnvInfo > ( ) ) ;
327+ const discovered = disposable . add ( new EventEmitter < NativeEnvInfo | NativeEnvManagerInfo > ( ) ) ;
289328 const completed = createDeferred < void > ( ) ;
290329 const pendingPromises : Promise < void > [ ] = [ ] ;
291330
@@ -306,6 +345,8 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
306345 notifyUponCompletion ( ) ;
307346 } ;
308347
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.
309350 disposable . add (
310351 this . connection . onNotification ( 'environment' , ( data : NativeEnvInfo ) => {
311352 this . outputChannel . info ( `Discovered env: ${ data . executable || data . prefix } ` ) ;
@@ -334,11 +375,28 @@ class NativeGlobalPythonFinderImpl extends DisposableBase implements NativePytho
334375 }
335376 } ) ,
336377 ) ;
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+ ) ;
337384
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+ }
338396 trackPromiseAndNotifyOnCompletion (
339397 this . configure ( ) . then ( ( ) =>
340398 this . connection
341- . sendRequest < { duration : number } > ( 'refresh' )
399+ . sendRequest < { duration : number } > ( 'refresh' , refreshOptions )
342400 . then ( ( { duration } ) => this . outputChannel . info ( `Refresh completed in ${ duration } ms` ) )
343401 . catch ( ( ex ) => this . outputChannel . error ( 'Refresh error' , ex ) ) ,
344402 ) ,
0 commit comments