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,157 @@ 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 { ILocator , Locator , 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
+ /**
34
+ * A wrapper around all locators used by the extension.
35
+ */
36
+ export class ExtensionLocators extends Locators {
37
+ constructor (
38
+ // These are expected to be low-level locators (e.g. system).
39
+ nonWorkspace : ILocator [ ] ,
40
+ // This is expected to be a locator wrapping any found in
41
+ // the workspace (i.e. WorkspaceLocators).
42
+ workspace : ILocator
43
+ ) {
44
+ super ( [ ...nonWorkspace , workspace ] ) ;
45
+ }
46
+ }
47
+
48
+ type WorkspaceLocatorFactory = ( root : Uri ) => ILocator [ ] ;
49
+
50
+ interface IWorkspaceFolders {
51
+ readonly roots : ReadonlyArray < Uri > ;
52
+ readonly onAdded : Event < Uri > ;
53
+ readonly onRemoved : Event < Uri > ;
54
+ }
55
+
56
+ type RootURI = string ;
57
+
58
+ /**
59
+ * The collection of all workspace-specific locators used by the extension.
60
+ *
61
+ * The factories are used to produce the locators for each workspace folder.
62
+ */
63
+ export class WorkspaceLocators extends Locator {
64
+ private readonly locators : Record < RootURI , DisableableLocator > = { } ;
65
+ private readonly roots : Record < RootURI , Uri > = { } ;
66
+ constructor (
67
+ // used to produce the per-root locators:
68
+ private readonly factories : WorkspaceLocatorFactory [ ]
69
+ ) {
70
+ super ( ) ;
71
+ }
72
+
73
+ /**
74
+ * Activate the locator.
75
+ *
76
+ * @param folders - the info used to keep track of the workspace folders
77
+ */
78
+ public activate ( folders : IWorkspaceFolders ) {
79
+ for ( const root of folders . roots ) {
80
+ this . addRoot ( root ) ;
81
+ }
82
+ folders . onAdded ( ( root : Uri ) => this . addRoot ( root ) ) ;
83
+ folders . onRemoved ( ( root : Uri ) => this . removeRoot ( root ) ) ;
84
+ }
85
+
86
+ public iterEnvs ( query ?: PythonLocatorQuery ) : PythonEnvsIterator {
87
+ const iterators = Object . keys ( this . locators ) . map ( ( key ) => {
88
+ if ( query ?. searchLocations ) {
89
+ const root = this . roots [ key ] ;
90
+ if ( ! matchURI ( root , ...query . searchLocations ) ) {
91
+ return NOOP_ITERATOR ;
92
+ }
93
+ }
94
+ // The query matches or was not location-specific.
95
+ const locator = this . locators [ key ] ;
96
+ return locator . iterEnvs ( query ) ;
97
+ } ) ;
98
+ return chain ( iterators ) ;
99
+ }
100
+
101
+ public async resolveEnv ( env : string | PythonEnvInfo ) : Promise < PythonEnvInfo | undefined > {
102
+ if ( typeof env !== 'string' && env . searchLocation ) {
103
+ const rootLocator = this . locators [ env . searchLocation . toString ( ) ] ;
104
+ if ( rootLocator ) {
105
+ return rootLocator . resolveEnv ( env ) ;
106
+ }
107
+ }
108
+ // Fall back to checking all the roots.
109
+ for ( const key of Object . keys ( this . locators ) ) {
110
+ const resolved = await this . locators [ key ] . resolveEnv ( env ) ;
111
+ if ( resolved !== undefined ) {
112
+ return resolved ;
113
+ }
114
+ }
115
+ return undefined ;
116
+ }
117
+
118
+ private addRoot ( root : Uri ) {
119
+ // Drop the old one, if necessary.
120
+ this . removeRoot ( root ) ;
121
+ // Create the root's locator, wrapping each factory-generated locator.
122
+ const locators : ILocator [ ] = [ ] ;
123
+ for ( const create of this . factories ) {
124
+ locators . push ( ...create ( root ) ) ;
125
+ }
126
+ const locator = new DisableableLocator ( new Locators ( locators ) ) ;
127
+ // Cache it.
128
+ const key = root . toString ( ) ;
129
+ this . locators [ key ] = locator ;
130
+ this . roots [ key ] = root ;
131
+ this . emitter . fire ( { searchLocation : root } ) ;
132
+ // Hook up the watchers.
133
+ locator . onChanged ( ( e ) => {
134
+ if ( e . searchLocation === undefined ) {
135
+ e . searchLocation = root ;
136
+ }
137
+ this . emitter . fire ( e ) ;
138
+ } ) ;
139
+ }
140
+
141
+ private removeRoot ( root : Uri ) {
142
+ const key = root . toString ( ) ;
143
+ const locator = this . locators [ key ] ;
144
+ if ( locator === undefined ) {
145
+ return ;
146
+ }
147
+ delete this . locators [ key ] ;
148
+ delete this . roots [ key ] ;
149
+ locator . disable ( ) ;
150
+ this . emitter . fire ( { searchLocation : root } ) ;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Determine if the given URI matches one of the candidates.
156
+ *
157
+ * The scheme must match, as well as path. The path must match exactly
158
+ * or the URI must be a parent of one of the candidates.
159
+ */
160
+ function matchURI ( uri : Uri , ...candidates : Uri [ ] ) : boolean {
161
+ const uriPath = uri . path . endsWith ( '/' ) ? uri . path : `{uri.path}/` ;
162
+ for ( const candidate of candidates ) {
163
+ if ( candidate . scheme === uri . scheme ) {
164
+ if ( candidate . path === uri . path ) {
165
+ return true ;
166
+ } else if ( candidate . path . startsWith ( uriPath ) ) {
167
+ return true ;
168
+ }
169
+ }
170
+ }
171
+ return false ;
172
+ }
173
+
30
174
/**
31
175
* Facilitates locating Python interpreters.
32
176
*/
0 commit comments