33
44import * as path from 'path' ;
55import { inject , injectable } from 'inversify' ;
6- import { ConfigurationChangeEvent , Uri } from 'vscode' ;
6+ import { ConfigurationChangeEvent , Uri , WorkspaceFoldersChangeEvent } from 'vscode' ;
77import { LanguageServerChangeHandler } from '../activation/common/languageServerChangeHandler' ;
88import {
99 IExtensionActivationService ,
@@ -50,7 +50,9 @@ export class LanguageServerWatcher
5050
5151 private workspaceInterpreters : Map < string , PythonEnvironment | undefined > ;
5252
53- // In a multiroot workspace scenario we will have one language server per folder.
53+ // In a multiroot workspace scenario we may have multiple language servers running:
54+ // When using Jedi, there will be one language server per workspace folder.
55+ // When using Pylance, there will only be one language server for the project.
5456 private workspaceLanguageServers : Map < string , ILanguageServerExtensionManager | undefined > ;
5557
5658 private languageServerChangeHandler : LanguageServerChangeHandler ;
@@ -77,6 +79,10 @@ export class LanguageServerWatcher
7779
7880 disposables . push ( this . workspaceService . onDidChangeConfiguration ( this . onDidChangeConfiguration . bind ( this ) ) ) ;
7981
82+ disposables . push (
83+ this . workspaceService . onDidChangeWorkspaceFolders ( this . onDidChangeWorkspaceFolders . bind ( this ) ) ,
84+ ) ;
85+
8086 if ( this . workspaceService . isTrusted ) {
8187 disposables . push ( this . interpreterPathService . onDidChange ( this . onDidChangeInterpreter . bind ( this ) ) ) ;
8288 }
@@ -113,7 +119,7 @@ export class LanguageServerWatcher
113119 languageServerType : LanguageServerType ,
114120 resource ?: Resource ,
115121 ) : Promise < ILanguageServerExtensionManager > {
116- const lsResource = this . getWorkspaceKey ( resource ) ;
122+ const lsResource = this . getWorkspaceUri ( resource ) ;
117123 const currentInterpreter = this . workspaceInterpreters . get ( lsResource . fsPath ) ;
118124 const interpreter = await this . interpreterService ?. getActiveInterpreter ( resource ) ;
119125
@@ -142,6 +148,15 @@ export class LanguageServerWatcher
142148 serverType = LanguageServerType . None ;
143149 }
144150
151+ // If the language server type is Pylance or None,
152+ // We only need to instantiate the language server once, even in multiroot workspace scenarios,
153+ // so we only need one language server extension manager.
154+ const key = this . getWorkspaceKey ( resource , serverType ) ;
155+ const languageServer = this . workspaceLanguageServers . get ( key ) ;
156+ if ( ( serverType === LanguageServerType . Node || serverType === LanguageServerType . None ) && languageServer ) {
157+ return languageServer ;
158+ }
159+
145160 // Instantiate the language server extension manager.
146161 const languageServerExtensionManager = this . createLanguageServer ( serverType ) ;
147162
@@ -156,16 +171,16 @@ export class LanguageServerWatcher
156171 await languageServerExtensionManager . languageServerNotAvailable ( ) ;
157172 }
158173
159- this . workspaceLanguageServers . set ( lsResource . fsPath , languageServerExtensionManager ) ;
174+ this . workspaceLanguageServers . set ( key , languageServerExtensionManager ) ;
160175
161176 return languageServerExtensionManager ;
162177 }
163178
164179 // ILanguageServerCache
165180
166181 public async get ( resource ?: Resource ) : Promise < ILanguageServer > {
167- const lsResource = this . getWorkspaceKey ( resource ) ;
168- let languageServerExtensionManager = this . workspaceLanguageServers . get ( lsResource . fsPath ) ;
182+ const key = this . getWorkspaceKey ( resource , this . languageServerType ) ;
183+ let languageServerExtensionManager = this . workspaceLanguageServers . get ( key ) ;
169184
170185 if ( ! languageServerExtensionManager ) {
171186 languageServerExtensionManager = await this . startAndGetLanguageServer ( this . languageServerType , resource ) ;
@@ -177,13 +192,13 @@ export class LanguageServerWatcher
177192 // Private methods
178193
179194 private stopLanguageServer ( resource ?: Resource ) : void {
180- const lsResource = this . getWorkspaceKey ( resource ) ;
181- const languageServerExtensionManager = this . workspaceLanguageServers . get ( lsResource . fsPath ) ;
195+ const key = this . getWorkspaceKey ( resource , this . languageServerType ) ;
196+ const languageServerExtensionManager = this . workspaceLanguageServers . get ( key ) ;
182197
183198 if ( languageServerExtensionManager ) {
184199 languageServerExtensionManager . stopLanguageServer ( ) ;
185200 languageServerExtensionManager . dispose ( ) ;
186- this . workspaceLanguageServers . delete ( lsResource . fsPath ) ;
201+ this . workspaceLanguageServers . delete ( key ) ;
187202 }
188203 }
189204
@@ -228,11 +243,11 @@ export class LanguageServerWatcher
228243 }
229244
230245 private async refreshLanguageServer ( resource ?: Resource ) : Promise < void > {
231- const lsResource = this . getWorkspaceKey ( resource ) ;
246+ const lsResource = this . getWorkspaceUri ( resource ) ;
232247 const languageServerType = this . configurationService . getSettings ( lsResource ) . languageServer ;
233248
234249 if ( languageServerType !== this . languageServerType ) {
235- this . stopLanguageServer ( lsResource ) ;
250+ this . stopLanguageServer ( resource ) ;
236251 await this . startLanguageServer ( languageServerType , lsResource ) ;
237252 }
238253 }
@@ -267,8 +282,19 @@ export class LanguageServerWatcher
267282 }
268283 }
269284
270- // Get the workspace key for the given resource, in order to query this.workspaceInterpreters and this.workspaceLanguageServers.
271- private getWorkspaceKey ( resource ?: Resource ) : Uri {
285+ // Watch for workspace folder changes.
286+ private async onDidChangeWorkspaceFolders ( event : WorkspaceFoldersChangeEvent ) : Promise < void > {
287+ // Since Jedi is the only language server type where we instantiate multiple language servers,
288+ // Make sure to dispose of them only in that scenario.
289+ if ( event . removed . length && this . languageServerType === LanguageServerType . Jedi ) {
290+ event . removed . forEach ( ( workspace ) => {
291+ this . stopLanguageServer ( workspace . uri ) ;
292+ } ) ;
293+ }
294+ }
295+
296+ // Get the workspace Uri for the given resource, in order to query this.workspaceInterpreters and this.workspaceLanguageServers.
297+ private getWorkspaceUri ( resource ?: Resource ) : Uri {
272298 let uri ;
273299
274300 if ( resource ) {
@@ -279,6 +305,19 @@ export class LanguageServerWatcher
279305
280306 return uri ?? Uri . parse ( 'default' ) ;
281307 }
308+
309+ // Get the key used to identify which language server extension manager is associated to which workspace.
310+ // When using Pylance or having no LS enabled, we return a static key since there should only be one LS extension manager for these LS types.
311+ private getWorkspaceKey ( resource : Resource | undefined , languageServerType : LanguageServerType ) : string {
312+ switch ( languageServerType ) {
313+ case LanguageServerType . Node :
314+ return 'Pylance' ;
315+ case LanguageServerType . None :
316+ return 'None' ;
317+ default :
318+ return this . getWorkspaceUri ( resource ) . fsPath ;
319+ }
320+ }
282321}
283322
284323function logStartup ( languageServerType : LanguageServerType , resource : Uri ) : void {
@@ -290,10 +329,10 @@ function logStartup(languageServerType: LanguageServerType, resource: Uri): void
290329 outputLine = LanguageService . startingJedi ( ) . format ( basename ) ;
291330 break ;
292331 case LanguageServerType . Node :
293- outputLine = LanguageService . startingPylance ( ) . format ( basename ) ;
332+ outputLine = LanguageService . startingPylance ( ) ;
294333 break ;
295334 case LanguageServerType . None :
296- outputLine = LanguageService . startingNone ( ) . format ( basename ) ;
335+ outputLine = LanguageService . startingNone ( ) ;
297336 break ;
298337 default :
299338 throw new Error ( `Unknown language server type: ${ languageServerType } ` ) ;
0 commit comments