diff --git a/src/client/activation/node/languageServerProxy.ts b/src/client/activation/node/languageServerProxy.ts index dea261514702..035f50ea7035 100644 --- a/src/client/activation/node/languageServerProxy.ts +++ b/src/client/activation/node/languageServerProxy.ts @@ -47,6 +47,14 @@ namespace GetExperimentValue { } } +interface PylanceApi { + client?: { + isEnabled(): boolean; + start(): Promise; + stop(): Promise; + }; +} + export class NodeLanguageServerProxy implements ILanguageServerProxy { public languageClient: LanguageClient | undefined; @@ -56,6 +64,8 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { private lsVersion: string | undefined; + private pylanceApi: PylanceApi | undefined; + constructor( private readonly factory: ILanguageClientFactory, private readonly experimentService: IExperimentService, @@ -63,7 +73,9 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { private readonly environmentService: IEnvironmentVariablesProvider, private readonly workspace: IWorkspaceService, private readonly extensions: IExtensions, - ) {} + ) { + // Empty + } private static versionTelemetryProps(instance: NodeLanguageServerProxy) { return { @@ -89,9 +101,16 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { interpreter: PythonEnvironment | undefined, options: LanguageClientOptions, ): Promise { - const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + const extension = await this.getPylanceExtension(); this.lsVersion = extension?.packageJSON.version || '0'; + const api = extension?.exports as PylanceApi | undefined; + if (api && api.client && api.client.isEnabled()) { + this.pylanceApi = api; + await api.client.start(); + return; + } + this.cancellationStrategy = new FileBasedCancellationStrategy(); options.connectionOptions = { cancellationStrategy: this.cancellationStrategy }; @@ -105,12 +124,17 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { ); await client.start(); - this.languageClient = client; } @traceDecoratorVerbose('Disposing language server') public async stop(): Promise { + if (this.pylanceApi) { + const api = this.pylanceApi; + this.pylanceApi = undefined; + await api.client!.stop(); + } + while (this.disposables.length > 0) { const d = this.disposables.shift()!; d.dispose(); @@ -203,4 +227,17 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy { })), ); } + + private async getPylanceExtension() { + const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID); + if (!extension) { + return undefined; + } + + if (!extension.isActive) { + await extension.activate(); + } + + return extension; + } } diff --git a/src/client/browser/extension.ts b/src/client/browser/extension.ts index febaee24d652..590b5941a4f8 100644 --- a/src/client/browser/extension.ts +++ b/src/client/browser/extension.ts @@ -16,7 +16,16 @@ interface BrowserConfig { distUrl: string; // URL to Pylance's dist folder. } +interface PylanceApi { + client?: { + isEnabled(): boolean; + start(): Promise; + stop(): Promise; + }; +} + let languageClient: LanguageClient | undefined; +let pylanceApi: PylanceApi | undefined; export async function activate(context: vscode.ExtensionContext): Promise { const pylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID); @@ -35,6 +44,12 @@ export async function activate(context: vscode.ExtensionContext): Promise } export async function deactivate(): Promise { + if (pylanceApi) { + const api = pylanceApi; + pylanceApi = undefined; + await api.client!.stop(); + } + const client = languageClient; languageClient = undefined; @@ -46,6 +61,16 @@ async function runPylance( context: vscode.ExtensionContext, pylanceExtension: vscode.Extension, ): Promise { + context.subscriptions.push(createStatusItem()); + + pylanceExtension = await getActivatedExtension(pylanceExtension); + const api = pylanceExtension.exports as PylanceApi; + if (api.client && api.client.isEnabled()) { + pylanceApi = api; + await api.client.start(); + return; + } + const { extensionUri, packageJSON } = pylanceExtension; const distUrl = vscode.Uri.joinPath(extensionUri, 'dist'); @@ -112,8 +137,6 @@ async function runPylance( ); await client.start(); - - context.subscriptions.push(createStatusItem()); } catch (e) { console.log(e); } @@ -204,3 +227,11 @@ function sendTelemetryEventBrowser( reporter.sendTelemetryEvent(eventNameSent, customProperties, measures); } } + +async function getActivatedExtension(extension: vscode.Extension): Promise> { + if (!extension.isActive) { + await extension.activate(); + } + + return extension; +} diff --git a/src/client/languageServer/watcher.ts b/src/client/languageServer/watcher.ts index f7c4193bde90..2c29709ef681 100644 --- a/src/client/languageServer/watcher.ts +++ b/src/client/languageServer/watcher.ts @@ -265,11 +265,11 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang return lsManager; } - private async refreshLanguageServer(resource?: Resource): Promise { + private async refreshLanguageServer(resource?: Resource, forced?: boolean): Promise { const lsResource = this.getWorkspaceUri(resource); const languageServerType = this.configurationService.getSettings(lsResource).languageServer; - if (languageServerType !== this.languageServerType) { + if (languageServerType !== this.languageServerType || forced) { await this.stopLanguageServer(resource); await this.startLanguageServer(languageServerType, lsResource); } @@ -286,6 +286,8 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang workspacesUris.forEach(async (resource) => { if (event.affectsConfiguration(`python.languageServer`, resource)) { await this.refreshLanguageServer(resource); + } else if (event.affectsConfiguration(`python.analysis.pylanceLspClientEnabled`, resource)) { + await this.refreshLanguageServer(resource, /* forced */ true); } }); }