Skip to content

Commit a114bcc

Browse files
committed
first draft for pylance client move
1 parent e9ff78d commit a114bcc

File tree

6 files changed

+89
-12
lines changed

6 files changed

+89
-12
lines changed

src/client/activation/node/languageClientFactory.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { PythonEnvironment } from '../../pythonEnvironments/info';
1111
import { FileBasedCancellationStrategy } from '../common/cancellationUtils';
1212
import { ILanguageClientFactory } from '../types';
1313

14-
const languageClientName = 'Pylance';
14+
export const PYLANCE_NAME = 'Pylance';
1515

1616
export class NodeLanguageClientFactory implements ILanguageClientFactory {
1717
constructor(private readonly fs: IFileSystem, private readonly extensions: IExtensions) {}
@@ -50,6 +50,6 @@ export class NodeLanguageClientFactory implements ILanguageClientFactory {
5050
},
5151
};
5252

53-
return new LanguageClient(PYTHON_LANGUAGE, languageClientName, serverOptions, clientOptions);
53+
return new LanguageClient(PYTHON_LANGUAGE, PYLANCE_NAME, serverOptions, clientOptions);
5454
}
5555
}

src/client/activation/node/languageServerProxy.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { ILanguageClientFactory, ILanguageServerProxy } from '../types';
2020
import { traceDecoratorError, traceDecoratorVerbose, traceError } from '../../logging';
2121
import { IWorkspaceService } from '../../common/application/types';
2222
import { PYLANCE_EXTENSION_ID } from '../../common/constants';
23+
import { PylanceApi } from './pylanceApi';
2324

2425
// eslint-disable-next-line @typescript-eslint/no-namespace
2526
namespace InExperiment {
@@ -56,6 +57,8 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
5657

5758
private lsVersion: string | undefined;
5859

60+
private pylanceApi: PylanceApi | undefined;
61+
5962
constructor(
6063
private readonly factory: ILanguageClientFactory,
6164
private readonly experimentService: IExperimentService,
@@ -89,9 +92,16 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
8992
interpreter: PythonEnvironment | undefined,
9093
options: LanguageClientOptions,
9194
): Promise<void> {
92-
const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID);
95+
const extension = await this.getPylanceExtension();
9396
this.lsVersion = extension?.packageJSON.version || '0';
9497

98+
const api = extension?.exports as PylanceApi | undefined;
99+
if (api && api.client && api.client.isEnabled()) {
100+
this.pylanceApi = api;
101+
await api.client.start();
102+
return;
103+
}
104+
95105
this.cancellationStrategy = new FileBasedCancellationStrategy();
96106
options.connectionOptions = { cancellationStrategy: this.cancellationStrategy };
97107

@@ -111,6 +121,12 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
111121

112122
@traceDecoratorVerbose('Disposing language server')
113123
public async stop(): Promise<void> {
124+
if (this.pylanceApi) {
125+
const api = this.pylanceApi;
126+
this.pylanceApi = undefined;
127+
await api.client!.stop();
128+
}
129+
114130
while (this.disposables.length > 0) {
115131
const d = this.disposables.shift()!;
116132
d.dispose();
@@ -203,4 +219,17 @@ export class NodeLanguageServerProxy implements ILanguageServerProxy {
203219
})),
204220
);
205221
}
222+
223+
private async getPylanceExtension() {
224+
const extension = this.extensions.getExtension(PYLANCE_EXTENSION_ID);
225+
if (!extension) {
226+
return undefined;
227+
}
228+
229+
if (!extension.isActive) {
230+
await extension.activate();
231+
}
232+
233+
return extension;
234+
}
206235
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
export interface PylanceApi {
5+
client?: {
6+
isEnabled(): boolean;
7+
start(): Promise<void>;
8+
stop(): Promise<void>;
9+
};
10+
}

src/client/api.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55

66
import { noop } from 'lodash';
77
import { Uri, Event } from 'vscode';
8+
import { BaseLanguageClient } from 'vscode-languageclient';
9+
import { LanguageClient } from 'vscode-languageclient/node';
10+
import { PYLANCE_NAME } from './activation/node/languageClientFactory';
811
import { IExtensionApi } from './apiTypes';
9-
import { isTestExecution } from './common/constants';
12+
import { isTestExecution, PYTHON_LANGUAGE } from './common/constants';
1013
import { IConfigurationService, Resource } from './common/types';
1114
import { IEnvironmentVariablesProvider } from './common/variables/types';
1215
import { getDebugpyLauncherArgs, getDebugpyPackagePath } from './debugger/extension/adapter/remoteLaunchers';
@@ -33,6 +36,9 @@ export function buildApi(
3336
pylance: {
3437
getPythonPathVar: (resource?: Uri) => Promise<string | undefined>;
3538
readonly onDidEnvironmentVariablesChange: Event<Uri | undefined>;
39+
createClient(...args: any[]): BaseLanguageClient;
40+
start(client: BaseLanguageClient): Promise<void>;
41+
stop(client: BaseLanguageClient): Promise<void>;
3642
};
3743
} = {
3844
// 'ready' will propagate the exception, but we must log it here first.
@@ -83,6 +89,10 @@ export function buildApi(
8389
return envs.PYTHONPATH;
8490
},
8591
onDidEnvironmentVariablesChange: envService.onDidEnvironmentVariablesChange,
92+
createClient: (...args: any[]): BaseLanguageClient =>
93+
new LanguageClient(PYTHON_LANGUAGE, PYLANCE_NAME, args[0], args[1]),
94+
start: (client: BaseLanguageClient): Promise<void> => client.start(),
95+
stop: (client: BaseLanguageClient): Promise<void> => client.stop(),
8696
},
8797
};
8898

src/client/browser/extension.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ import { LanguageServerType } from '../activation/types';
1010
import { AppinsightsKey, PYLANCE_EXTENSION_ID } from '../common/constants';
1111
import { EventName } from '../telemetry/constants';
1212
import { createStatusItem } from './intellisenseStatus';
13+
import { PylanceApi } from '../activation/node/pylanceApi';
1314

1415
interface BrowserConfig {
1516
distUrl: string; // URL to Pylance's dist folder.
1617
}
1718

1819
let languageClient: LanguageClient | undefined;
20+
let pylanceApi: PylanceApi | undefined;
1921

2022
export async function activate(context: vscode.ExtensionContext): Promise<void> {
2123
const pylanceExtension = vscode.extensions.getExtension(PYLANCE_EXTENSION_ID);
@@ -34,17 +36,35 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
3436
}
3537

3638
export async function deactivate(): Promise<void> {
37-
const client = languageClient;
38-
languageClient = undefined;
39+
if (pylanceApi) {
40+
const api = pylanceApi;
41+
pylanceApi = undefined;
42+
await api.client!.stop();
43+
}
44+
45+
if (languageClient) {
46+
const client = languageClient;
47+
languageClient = undefined;
3948

40-
await client?.stop();
41-
await client?.dispose();
49+
await client.stop();
50+
await client.dispose();
51+
}
4252
}
4353

4454
async function runPylance(
4555
context: vscode.ExtensionContext,
4656
pylanceExtension: vscode.Extension<unknown>,
4757
): Promise<void> {
58+
context.subscriptions.push(createStatusItem());
59+
60+
pylanceExtension = await getActivatedExtension(pylanceExtension);
61+
const api = pylanceExtension.exports as PylanceApi;
62+
if (api.client && api.client.isEnabled()) {
63+
pylanceApi = api;
64+
await api.client.start();
65+
return;
66+
}
67+
4868
const { extensionUri, packageJSON } = pylanceExtension;
4969
const distUrl = vscode.Uri.joinPath(extensionUri, 'dist');
5070

@@ -111,8 +131,6 @@ async function runPylance(
111131
);
112132

113133
await client.start();
114-
115-
context.subscriptions.push(createStatusItem());
116134
} catch (e) {
117135
console.log(e);
118136
}
@@ -196,3 +214,11 @@ function sendTelemetryEventBrowser(
196214
reporter.sendTelemetryEvent(eventNameSent, customProperties, measures);
197215
}
198216
}
217+
218+
async function getActivatedExtension<T>(extension: vscode.Extension<T>): Promise<vscode.Extension<T>> {
219+
if (!extension.isActive) {
220+
await extension.activate();
221+
}
222+
223+
return extension;
224+
}

src/client/languageServer/watcher.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,11 +262,11 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang
262262
return lsManager;
263263
}
264264

265-
private async refreshLanguageServer(resource?: Resource): Promise<void> {
265+
private async refreshLanguageServer(resource?: Resource, forced?: boolean): Promise<void> {
266266
const lsResource = this.getWorkspaceUri(resource);
267267
const languageServerType = this.configurationService.getSettings(lsResource).languageServer;
268268

269-
if (languageServerType !== this.languageServerType) {
269+
if (languageServerType !== this.languageServerType || forced) {
270270
await this.stopLanguageServer(resource);
271271
await this.startLanguageServer(languageServerType, lsResource);
272272
}
@@ -283,6 +283,8 @@ export class LanguageServerWatcher implements IExtensionActivationService, ILang
283283
workspacesUris.forEach(async (resource) => {
284284
if (event.affectsConfiguration(`python.languageServer`, resource)) {
285285
await this.refreshLanguageServer(resource);
286+
} else if (event.affectsConfiguration(`python.analysis.pylanceLspClientEnabled`, resource)) {
287+
await this.refreshLanguageServer(resource, /* forced */ true);
286288
}
287289
});
288290
}

0 commit comments

Comments
 (0)