5
5
import { traceDecorators } from '../../../common/logger' ;
6
6
import { IPlatformService } from '../../../common/platform/types' ;
7
7
import { IDisposableRegistry } from '../../../common/types' ;
8
- import { createDeferred , Deferred } from '../../../common/utils/async' ;
8
+ import { chain , createDeferred , Deferred } from '../../../common/utils/async' ;
9
9
import { OSType } from '../../../common/utils/platform' ;
10
10
import {
11
11
CONDA_ENV_FILE_SERVICE ,
@@ -20,13 +20,136 @@ import {
20
20
WORKSPACE_VIRTUAL_ENV_SERVICE ,
21
21
} from '../../../interpreter/contracts' ;
22
22
import { IServiceContainer } from '../../../ioc/types' ;
23
+ import { PythonEnvInfo } from '../../base/info' ;
24
+ import { Locator , ILocator , NOOP_ITERATOR , PythonEnvsIterator , PythonLocatorQuery } from '../../base/locator' ;
25
+ import { DisableableLocator , Locators } from '../../base/locators' ;
23
26
import { PythonEnvironment } from '../../info' ;
24
27
import { isHiddenInterpreter } from './services/interpreterFilter' ;
25
28
import { GetInterpreterLocatorOptions } from './types' ;
26
29
27
30
// tslint:disable-next-line:no-require-imports no-var-requires
28
31
const flatten = require ( 'lodash/flatten' ) as typeof import ( 'lodash/flatten' ) ;
29
32
33
+ type WorkspaceLocatorFactory = ( root : Uri ) => ILocator [ ] ;
34
+
35
+ interface IWorkspaceFolders {
36
+ readonly roots : ReadonlyArray < Uri > ;
37
+ readonly onAdded : Event < Uri > ;
38
+ readonly onRemoved : Event < Uri > ;
39
+ }
40
+
41
+ type RootURI = string ;
42
+
43
+ /**
44
+ * The collection of all workspace-specific locators used by the extension.
45
+ *
46
+ * The factories are used to produce the locators for each workspace folder.
47
+ */
48
+ export class WorkspaceLocators extends Locator {
49
+ private readonly locators : Record < RootURI , DisableableLocator > = { } ;
50
+ private readonly roots : Record < RootURI , Uri > = { } ;
51
+ constructor (
52
+ // used to produce the per-root locators:
53
+ private readonly factories : WorkspaceLocatorFactory [ ]
54
+ ) {
55
+ super ( ) ;
56
+ }
57
+
58
+ /**
59
+ * Activate the locator.
60
+ *
61
+ * @param folders - the info used to keep track of the workspace folders
62
+ */
63
+ public activate ( folders : IWorkspaceFolders ) {
64
+ for ( const root of folders . roots ) {
65
+ this . addRoot ( root ) ;
66
+ }
67
+ folders . onAdded ( ( root : Uri ) => this . addRoot ( root ) ) ;
68
+ folders . onRemoved ( ( root : Uri ) => this . removeRoot ( root ) ) ;
69
+ }
70
+
71
+ public iterEnvs ( query ?: PythonLocatorQuery ) : PythonEnvsIterator {
72
+ const iterators = Object . keys ( this . locators ) . map ( ( key ) => {
73
+ if ( query ?. searchLocations ) {
74
+ const root = this . roots [ key ] ;
75
+ if ( ! matchURI ( root , ...query . searchLocations ) ) {
76
+ return NOOP_ITERATOR ;
77
+ }
78
+ }
79
+ // The query matches or was not location-specific.
80
+ const locator = this . locators [ key ] ;
81
+ return locator . iterEnvs ( query ) ;
82
+ } ) ;
83
+ return chain < PythonEnvInfo > ( iterators ) ;
84
+ }
85
+
86
+ public async resolveEnv ( env : PythonEnvInfo ) : Promise < PythonEnvInfo | undefined > {
87
+ if ( env . searchLocation ) {
88
+ const rootLocator = this . locators [ env . searchLocation . toString ( ) ] ;
89
+ if ( rootLocator ) {
90
+ return rootLocator . resolveEnv ( env ) ;
91
+ }
92
+ }
93
+ // Fall back to checking all the roots.
94
+ for ( const key of Object . keys ( this . locators ) ) {
95
+ const resolved = await this . locators [ key ] . resolveEnv ( env ) ;
96
+ if ( resolved !== undefined ) {
97
+ return resolved ;
98
+ }
99
+ }
100
+ return undefined ;
101
+ }
102
+
103
+ private addRoot ( root : Uri ) {
104
+ // Drop the old one, if necessary.
105
+ this . removeRoot ( root ) ;
106
+ // Create the root's locator, wrapping each factory-generated locator.
107
+ const locators : ILocator [ ] = [ ] ;
108
+ for ( const create of this . factories ) {
109
+ locators . push ( ...create ( root ) ) ;
110
+ }
111
+ const locator = new DisableableLocator ( new Locators ( locators ) ) ;
112
+ // Cache it.
113
+ const key = root . toString ( ) ;
114
+ this . locators [ key ] = locator ;
115
+ this . roots [ key ] = root ;
116
+ this . emitter . fire ( { searchLocation : root } ) ;
117
+ // Hook up the watchers.
118
+ locator . onChanged ( ( e ) => {
119
+ if ( e . searchLocation === undefined ) {
120
+ e . searchLocation = root ;
121
+ }
122
+ this . emitter . fire ( e ) ;
123
+ } ) ;
124
+ }
125
+
126
+ private removeRoot ( root : Uri ) {
127
+ const key = root . toString ( ) ;
128
+ const locator = this . locators [ key ] ;
129
+ if ( locator === undefined ) {
130
+ return ;
131
+ }
132
+ delete this . locators [ key ] ;
133
+ delete this . roots [ key ] ;
134
+ locator . disable ( ) ;
135
+ this . emitter . fire ( { searchLocation : root } ) ;
136
+ }
137
+ }
138
+
139
+ function matchURI ( uri : Uri , ...candidates : Uri [ ] ) : boolean {
140
+ const uriPath = uri . path . endsWith ( '/' ) ? uri . path : `{uri.path}/` ;
141
+ for ( const candidate of candidates ) {
142
+ if ( candidate . scheme === uri . scheme ) {
143
+ if ( candidate . path === uri . path ) {
144
+ return true ;
145
+ } else if ( candidate . path . startsWith ( uriPath ) ) {
146
+ return true ;
147
+ }
148
+ }
149
+ }
150
+ return false ;
151
+ }
152
+
30
153
/**
31
154
* Facilitates locating Python interpreters.
32
155
*/
0 commit comments