1- import { EventEmitter , MarkdownString } from 'vscode' ;
1+ import { EventEmitter , MarkdownString , ProgressLocation , Uri } from 'vscode' ;
22import {
33 CreateEnvironmentOptions ,
44 CreateEnvironmentScope ,
55 DidChangeEnvironmentEventArgs ,
66 DidChangeEnvironmentsEventArgs ,
7+ EnvironmentChangeKind ,
78 EnvironmentManager ,
89 GetEnvironmentScope ,
910 GetEnvironmentsScope ,
1011 IconPath ,
1112 PythonEnvironment ,
1213 PythonEnvironmentApi ,
14+ PythonProject ,
1315 QuickCreateConfig ,
1416 RefreshEnvironmentsScope ,
1517 ResolveEnvironmentContext ,
1618 SetEnvironmentScope ,
1719} from '../../api' ;
1820import { PipenvStrings } from '../../common/localize' ;
21+ import { createDeferred , Deferred } from '../../common/utils/deferred' ;
22+ import { withProgress } from '../../common/window.apis' ;
1923import { NativePythonFinder } from '../common/nativePythonFinder' ;
24+ import {
25+ clearPipenvCache ,
26+ getPipenvForGlobal ,
27+ getPipenvForWorkspace ,
28+ refreshPipenv ,
29+ resolvePipenvPath ,
30+ setPipenvForGlobal ,
31+ setPipenvForWorkspace ,
32+ setPipenvForWorkspaces ,
33+ } from './pipenvUtils' ;
2034
2135export class PipenvManager implements EnvironmentManager {
36+ private collection : PythonEnvironment [ ] = [ ] ;
37+ private fsPathToEnv : Map < string , PythonEnvironment > = new Map ( ) ;
38+ private globalEnv : PythonEnvironment | undefined ;
39+
2240 private readonly _onDidChangeEnvironment = new EventEmitter < DidChangeEnvironmentEventArgs > ( ) ;
2341 public readonly onDidChangeEnvironment = this . _onDidChangeEnvironment . event ;
2442
@@ -32,6 +50,8 @@ export class PipenvManager implements EnvironmentManager {
3250 public readonly tooltip : string | MarkdownString ;
3351 public readonly iconPath ?: IconPath ;
3452
53+ private _initialized : Deferred < void > | undefined ;
54+
3555 constructor (
3656 public readonly nativeFinder : NativePythonFinder ,
3757 public readonly api : PythonEnvironmentApi
@@ -43,10 +63,63 @@ export class PipenvManager implements EnvironmentManager {
4363 }
4464
4565 public dispose ( ) {
66+ this . collection = [ ] ;
67+ this . fsPathToEnv . clear ( ) ;
4668 this . _onDidChangeEnvironment . dispose ( ) ;
4769 this . _onDidChangeEnvironments . dispose ( ) ;
4870 }
4971
72+ async initialize ( ) : Promise < void > {
73+ if ( this . _initialized ) {
74+ return this . _initialized . promise ;
75+ }
76+
77+ this . _initialized = createDeferred ( ) ;
78+
79+ await withProgress (
80+ {
81+ location : ProgressLocation . Window ,
82+ title : PipenvStrings . pipenvDiscovering ,
83+ } ,
84+ async ( ) => {
85+ this . collection = await refreshPipenv ( false , this . nativeFinder , this . api , this ) ;
86+ await this . loadEnvMap ( ) ;
87+
88+ this . _onDidChangeEnvironments . fire (
89+ this . collection . map ( ( e ) => ( { environment : e , kind : EnvironmentChangeKind . add } ) ) ,
90+ ) ;
91+ } ,
92+ ) ;
93+ this . _initialized . resolve ( ) ;
94+ }
95+
96+ private async loadEnvMap ( ) {
97+ // Load environment mappings for projects
98+ const projects = this . api . getPythonProjects ( ) ;
99+ for ( const project of projects ) {
100+ const envPath = await getPipenvForWorkspace ( project . uri . fsPath ) ;
101+ if ( envPath ) {
102+ const env = this . findEnvironmentByPath ( envPath ) ;
103+ if ( env ) {
104+ this . fsPathToEnv . set ( project . uri . fsPath , env ) ;
105+ }
106+ }
107+ }
108+
109+ // Load global environment
110+ const globalEnvPath = await getPipenvForGlobal ( ) ;
111+ if ( globalEnvPath ) {
112+ this . globalEnv = this . findEnvironmentByPath ( globalEnvPath ) ;
113+ }
114+ }
115+
116+ private findEnvironmentByPath ( fsPath : string ) : PythonEnvironment | undefined {
117+ return this . collection . find ( ( env ) =>
118+ env . environmentPath . fsPath === fsPath ||
119+ env . execInfo ?. run . executable === fsPath
120+ ) ;
121+ }
122+
50123 quickCreateConfig ?( ) : QuickCreateConfig | undefined {
51124 // To be implemented
52125 return undefined ;
@@ -64,34 +137,162 @@ export class PipenvManager implements EnvironmentManager {
64137 // To be implemented
65138 }
66139
67- async refresh ( _scope : RefreshEnvironmentsScope ) : Promise < void > {
68- // TODO: Implement pipenv environment refresh
69- // This should discover pipenv environments and update the collection
140+ async refresh ( scope : RefreshEnvironmentsScope ) : Promise < void > {
141+ const hardRefresh = scope === undefined ; // hard refresh when scope is undefined
142+
143+ await withProgress (
144+ {
145+ location : ProgressLocation . Window ,
146+ title : PipenvStrings . pipenvRefreshing ,
147+ } ,
148+ async ( ) => {
149+ const oldCollection = [ ...this . collection ] ;
150+ this . collection = await refreshPipenv ( hardRefresh , this . nativeFinder , this . api , this ) ;
151+ await this . loadEnvMap ( ) ;
152+
153+ // Fire change events for environments that were added or removed
154+ const changes : { environment : PythonEnvironment ; kind : EnvironmentChangeKind } [ ] = [ ] ;
155+
156+ // Find removed environments
157+ oldCollection . forEach ( ( oldEnv ) => {
158+ if ( ! this . collection . find ( ( newEnv ) => newEnv . envId . id === oldEnv . envId . id ) ) {
159+ changes . push ( { environment : oldEnv , kind : EnvironmentChangeKind . remove } ) ;
160+ }
161+ } ) ;
162+
163+ // Find added environments
164+ this . collection . forEach ( ( newEnv ) => {
165+ if ( ! oldCollection . find ( ( oldEnv ) => oldEnv . envId . id === newEnv . envId . id ) ) {
166+ changes . push ( { environment : newEnv , kind : EnvironmentChangeKind . add } ) ;
167+ }
168+ } ) ;
169+
170+ if ( changes . length > 0 ) {
171+ this . _onDidChangeEnvironments . fire ( changes ) ;
172+ }
173+ } ,
174+ ) ;
70175 }
71176
72- async getEnvironments ( _scope : GetEnvironmentsScope ) : Promise < PythonEnvironment [ ] > {
73- // TODO: Implement pipenv environment discovery
74- // This should return all discovered pipenv environments
177+ async getEnvironments ( scope : GetEnvironmentsScope ) : Promise < PythonEnvironment [ ] > {
178+ await this . initialize ( ) ;
179+
180+ if ( scope === 'all' ) {
181+ return Array . from ( this . collection ) ;
182+ }
183+
184+ if ( scope === 'global' ) {
185+ // Return all environments for global scope
186+ return Array . from ( this . collection ) ;
187+ }
188+
189+ if ( scope instanceof Uri ) {
190+ const project = this . api . getPythonProject ( scope ) ;
191+ if ( project ) {
192+ const env = this . fsPathToEnv . get ( project . uri . fsPath ) ;
193+ return env ? [ env ] : [ ] ;
194+ }
195+ }
196+
75197 return [ ] ;
76198 }
77199
78- async set ( _scope : SetEnvironmentScope , _environment ?: PythonEnvironment ) : Promise < void > {
79- // TODO: Implement setting pipenv environment for a scope
80- // This should update the selected environment for the given scope
200+ async set ( scope : SetEnvironmentScope , environment ?: PythonEnvironment ) : Promise < void > {
201+ if ( scope === undefined ) {
202+ // Global scope
203+ const before = this . globalEnv ;
204+ this . globalEnv = environment ;
205+ await setPipenvForGlobal ( environment ?. environmentPath . fsPath ) ;
206+
207+ if ( before ?. envId . id !== this . globalEnv ?. envId . id ) {
208+ this . _onDidChangeEnvironment . fire ( { uri : undefined , old : before , new : this . globalEnv } ) ;
209+ }
210+ return ;
211+ }
212+
213+ if ( scope instanceof Uri ) {
214+ // Single project scope
215+ const project = this . api . getPythonProject ( scope ) ;
216+ if ( ! project ) {
217+ return ;
218+ }
219+
220+ const before = this . fsPathToEnv . get ( project . uri . fsPath ) ;
221+ if ( environment ) {
222+ this . fsPathToEnv . set ( project . uri . fsPath , environment ) ;
223+ } else {
224+ this . fsPathToEnv . delete ( project . uri . fsPath ) ;
225+ }
226+
227+ await setPipenvForWorkspace ( project . uri . fsPath , environment ?. environmentPath . fsPath ) ;
228+
229+ if ( before ?. envId . id !== environment ?. envId . id ) {
230+ this . _onDidChangeEnvironment . fire ( { uri : scope , old : before , new : environment } ) ;
231+ }
232+ }
233+
234+ if ( Array . isArray ( scope ) && scope . every ( ( u ) => u instanceof Uri ) ) {
235+ // Multiple projects scope
236+ const projects : PythonProject [ ] = [ ] ;
237+ scope
238+ . map ( ( s ) => this . api . getPythonProject ( s ) )
239+ . forEach ( ( p ) => {
240+ if ( p ) {
241+ projects . push ( p ) ;
242+ }
243+ } ) ;
244+
245+ const before : Map < string , PythonEnvironment | undefined > = new Map ( ) ;
246+ projects . forEach ( ( p ) => {
247+ before . set ( p . uri . fsPath , this . fsPathToEnv . get ( p . uri . fsPath ) ) ;
248+ if ( environment ) {
249+ this . fsPathToEnv . set ( p . uri . fsPath , environment ) ;
250+ } else {
251+ this . fsPathToEnv . delete ( p . uri . fsPath ) ;
252+ }
253+ } ) ;
254+
255+ await setPipenvForWorkspaces (
256+ projects . map ( ( p ) => p . uri . fsPath ) ,
257+ environment ?. environmentPath . fsPath ,
258+ ) ;
259+
260+ projects . forEach ( ( p ) => {
261+ const b = before . get ( p . uri . fsPath ) ;
262+ if ( b ?. envId . id !== environment ?. envId . id ) {
263+ this . _onDidChangeEnvironment . fire ( { uri : p . uri , old : b , new : environment } ) ;
264+ }
265+ } ) ;
266+ }
81267 }
82268
83- async get ( _scope : GetEnvironmentScope ) : Promise < PythonEnvironment | undefined > {
84- // TODO: Implement getting the selected pipenv environment for a scope
269+ async get ( scope : GetEnvironmentScope ) : Promise < PythonEnvironment | undefined > {
270+ await this . initialize ( ) ;
271+
272+ if ( scope === undefined ) {
273+ return this . globalEnv ;
274+ }
275+
276+ if ( scope instanceof Uri ) {
277+ const project = this . api . getPythonProject ( scope ) ;
278+ if ( project ) {
279+ return this . fsPathToEnv . get ( project . uri . fsPath ) ;
280+ }
281+ }
282+
85283 return undefined ;
86284 }
87285
88- async resolve ( _context : ResolveEnvironmentContext ) : Promise < PythonEnvironment | undefined > {
89- // TODO: Implement resolving a path to a pipenv environment
90- return undefined ;
286+ async resolve ( context : ResolveEnvironmentContext ) : Promise < PythonEnvironment | undefined > {
287+ await this . initialize ( ) ;
288+ return resolvePipenvPath ( context . fsPath , this . nativeFinder , this . api , this ) ;
91289 }
92290
93291 async clearCache ?( ) : Promise < void > {
94- // TODO: Implement cache clearing
95- // This should clear any cached environment discovery data
292+ await clearPipenvCache ( ) ;
293+ this . collection = [ ] ;
294+ this . fsPathToEnv . clear ( ) ;
295+ this . globalEnv = undefined ;
296+ this . _initialized = undefined ;
96297 }
97298}
0 commit comments