55import  {  traceDecorators  }  from  '../../../common/logger' ; 
66import  {  IPlatformService  }  from  '../../../common/platform/types' ; 
77import  {  IDisposableRegistry  }  from  '../../../common/types' ; 
8- import  {  createDeferred ,  Deferred  }  from  '../../../common/utils/async' ; 
8+ import  {  chain ,   createDeferred ,  Deferred  }  from  '../../../common/utils/async' ; 
99import  {  OSType  }  from  '../../../common/utils/platform' ; 
1010import  { 
1111 CONDA_ENV_FILE_SERVICE , 
@@ -20,13 +20,157 @@ import {
2020 WORKSPACE_VIRTUAL_ENV_SERVICE , 
2121}  from  '../../../interpreter/contracts' ; 
2222import  {  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' ; 
2326import  {  PythonEnvironment  }  from  '../../info' ; 
2427import  {  isHiddenInterpreter  }  from  './services/interpreterFilter' ; 
2528import  {  GetInterpreterLocatorOptions  }  from  './types' ; 
2629
2730// tslint:disable-next-line:no-require-imports no-var-requires 
2831const  flatten  =  require ( 'lodash/flatten' )  as  typeof  import ( 'lodash/flatten' ) ; 
2932
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+ 
30174/** 
31175 * Facilitates locating Python interpreters. 
32176 */ 
0 commit comments