Skip to content

Commit ace7101

Browse files
debontewesm
authored andcommitted
Support for LSP notebooks experiment (microsoft/vscode-python#19087)
1 parent b87a02c commit ace7101

22 files changed

+366
-37
lines changed

extensions/positron-python/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,15 @@
906906
"scope": "machine-overridable",
907907
"type": "string"
908908
},
909+
"python.pylanceLspNotebooksEnabled": {
910+
"type": "boolean",
911+
"default": false,
912+
"description": "Determines if Pylance's experimental LSP notebooks support is used or not.",
913+
"scope": "machine",
914+
"tags": [
915+
"experimental"
916+
]
917+
},
909918
"python.sortImports.args": {
910919
"default": [],
911920
"description": "Arguments passed in. Each argument is a separate item in the array.",
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { IServiceContainer } from '../../ioc/types';
5+
import { LanguageClientMiddleware } from '../languageClientMiddleware';
6+
import { LanguageServerType } from '../types';
7+
8+
export class JediLanguageClientMiddleware extends LanguageClientMiddleware {
9+
public constructor(serviceContainer: IServiceContainer, serverVersion?: string) {
10+
super(serviceContainer, LanguageServerType.Jedi, serverVersion);
11+
this.setupHidingMiddleware(serviceContainer);
12+
}
13+
}

extensions/positron-python/src/client/activation/jedi/languageServerProxy.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IInterpreterPathService, Resource } from '../../common/types';
1414
import { PythonEnvironment } from '../../pythonEnvironments/info';
1515
import { captureTelemetry } from '../../telemetry';
1616
import { EventName } from '../../telemetry/constants';
17-
import { LanguageClientMiddleware } from '../languageClientMiddleware';
17+
import { JediLanguageClientMiddleware } from './languageClientMiddleware';
1818
import { ProgressReporting } from '../progress';
1919
import { ILanguageClientFactory, ILanguageServerProxy } from '../types';
2020
import { killPid } from '../../common/process/rawProcessApis';
@@ -57,7 +57,8 @@ export class JediLanguageServerProxy implements ILanguageServerProxy {
5757
options: LanguageClientOptions,
5858
): Promise<void> {
5959
this.lsVersion =
60-
(options.middleware ? (<LanguageClientMiddleware>options.middleware).serverVersion : undefined) ?? '0.19.3';
60+
(options.middleware ? (<JediLanguageClientMiddleware>options.middleware).serverVersion : undefined) ??
61+
'0.19.3';
6162

6263
this.languageClient = await this.factory.createLanguageClient(resource, interpreter, options);
6364
this.registerHandlers();

extensions/positron-python/src/client/activation/jedi/manager.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,16 @@ import { PythonEnvironment } from '../../pythonEnvironments/info';
1414
import { captureTelemetry } from '../../telemetry';
1515
import { EventName } from '../../telemetry/constants';
1616
import { Commands } from '../commands';
17-
import { LanguageClientMiddleware } from '../languageClientMiddleware';
18-
import {
19-
ILanguageServerAnalysisOptions,
20-
ILanguageServerManager,
21-
ILanguageServerProxy,
22-
LanguageServerType,
23-
} from '../types';
17+
import { JediLanguageClientMiddleware } from './languageClientMiddleware';
18+
import { ILanguageServerAnalysisOptions, ILanguageServerManager, ILanguageServerProxy } from '../types';
2419
import { traceDecoratorError, traceDecoratorVerbose, traceVerbose } from '../../logging';
2520

2621
export class JediLanguageServerManager implements ILanguageServerManager {
2722
private resource!: Resource;
2823

2924
private interpreter: PythonEnvironment | undefined;
3025

31-
private middleware: LanguageClientMiddleware | undefined;
26+
private middleware: JediLanguageClientMiddleware | undefined;
3227

3328
private disposables: IDisposable[] = [];
3429

@@ -132,7 +127,7 @@ export class JediLanguageServerManager implements ILanguageServerManager {
132127
@traceDecoratorVerbose('Starting language server')
133128
protected async startLanguageServer(): Promise<void> {
134129
const options = await this.analysisOptions.getAnalysisOptions();
135-
this.middleware = new LanguageClientMiddleware(this.serviceContainer, LanguageServerType.Jedi, this.lsVersion);
130+
this.middleware = new JediLanguageClientMiddleware(this.serviceContainer, this.lsVersion);
136131
options.middleware = this.middleware;
137132

138133
// Make sure the middleware is connected if we restart and we we're already connected.

extensions/positron-python/src/client/activation/languageClientMiddleware.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,45 @@ import { createHidingMiddleware } from '@vscode/jupyter-lsp-middleware';
1414
export class LanguageClientMiddleware extends LanguageClientMiddlewareBase {
1515
public constructor(serviceContainer: IServiceContainer, serverType: LanguageServerType, serverVersion?: string) {
1616
super(serviceContainer, serverType, sendTelemetryEvent, serverVersion);
17+
}
1718

18-
if (serverType === LanguageServerType.None) {
19-
return;
20-
}
21-
19+
/**
20+
* Creates the HidingMiddleware if needed and sets up code to do so if needed after
21+
* Jupyter is installed.
22+
*
23+
* This method should be called from the constructor of derived classes. It is separated
24+
* from the constructor to allow derived classes to initialize before it is called.
25+
*/
26+
protected setupHidingMiddleware(serviceContainer: IServiceContainer) {
2227
const jupyterDependencyManager = serviceContainer.get<IJupyterExtensionDependencyManager>(
2328
IJupyterExtensionDependencyManager,
2429
);
2530
const disposables = serviceContainer.get<IDisposableRegistry>(IDisposableRegistry) || [];
2631
const extensions = serviceContainer.get<IExtensions>(IExtensions);
2732

2833
// Enable notebook support if jupyter support is installed
29-
if (jupyterDependencyManager && jupyterDependencyManager.isJupyterExtensionInstalled) {
34+
if (this.shouldCreateHidingMiddleware(jupyterDependencyManager)) {
3035
this.notebookAddon = createHidingMiddleware();
3136
}
37+
3238
disposables.push(
33-
extensions?.onDidChange(() => {
34-
if (jupyterDependencyManager) {
35-
if (this.notebookAddon && !jupyterDependencyManager.isJupyterExtensionInstalled) {
36-
this.notebookAddon = undefined;
37-
} else if (!this.notebookAddon && jupyterDependencyManager.isJupyterExtensionInstalled) {
38-
this.notebookAddon = createHidingMiddleware();
39-
}
40-
}
39+
extensions?.onDidChange(async () => {
40+
await this.onExtensionChange(jupyterDependencyManager);
4141
}),
4242
);
4343
}
44+
45+
protected shouldCreateHidingMiddleware(jupyterDependencyManager: IJupyterExtensionDependencyManager): boolean {
46+
return jupyterDependencyManager && jupyterDependencyManager.isJupyterExtensionInstalled;
47+
}
48+
49+
protected async onExtensionChange(jupyterDependencyManager: IJupyterExtensionDependencyManager): Promise<void> {
50+
if (jupyterDependencyManager) {
51+
if (this.notebookAddon && !this.shouldCreateHidingMiddleware(jupyterDependencyManager)) {
52+
this.notebookAddon = undefined;
53+
} else if (!this.notebookAddon && this.shouldCreateHidingMiddleware(jupyterDependencyManager)) {
54+
this.notebookAddon = createHidingMiddleware();
55+
}
56+
}
57+
}
4458
}

extensions/positron-python/src/client/activation/languageClientMiddlewareBase.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ export class LanguageClientMiddlewareBase implements Middleware {
8686
const settingDict: LSPObject & { pythonPath: string; _envPYTHONPATH: string } = settings[
8787
i
8888
] as LSPObject & { pythonPath: string; _envPYTHONPATH: string };
89-
settingDict.pythonPath = configService.getSettings(uri).pythonPath;
89+
90+
settingDict.pythonPath =
91+
(await this.getPythonPathOverride(uri)) ?? configService.getSettings(uri).pythonPath;
9092

9193
const env = await envService.getEnvironmentVariables(uri);
9294
const envPYTHONPATH = env.PYTHONPATH;
@@ -100,6 +102,11 @@ export class LanguageClientMiddlewareBase implements Middleware {
100102
},
101103
};
102104

105+
// eslint-disable-next-line class-methods-use-this
106+
protected async getPythonPathOverride(_uri: Uri | undefined): Promise<string | undefined> {
107+
return undefined;
108+
}
109+
103110
private get connected(): Promise<boolean> {
104111
return this.connectedPromise.promise;
105112
}

extensions/positron-python/src/client/activation/node/analysisOptions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ import { IWorkspaceService } from '../../common/application/types';
66

77
import { LanguageServerAnalysisOptionsBase } from '../common/analysisOptions';
88
import { ILanguageServerOutputChannel } from '../types';
9+
import { LspNotebooksExperiment } from './lspNotebooksExperiment';
910

1011
export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOptionsBase {
1112
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
12-
constructor(lsOutputChannel: ILanguageServerOutputChannel, workspace: IWorkspaceService) {
13+
constructor(
14+
lsOutputChannel: ILanguageServerOutputChannel,
15+
workspace: IWorkspaceService,
16+
private readonly lspNotebooksExperiment: LspNotebooksExperiment,
17+
) {
1318
super(lsOutputChannel, workspace);
1419
}
1520

@@ -18,6 +23,7 @@ export class NodeLanguageServerAnalysisOptions extends LanguageServerAnalysisOpt
1823
return ({
1924
experimentationSupport: true,
2025
trustedWorkspaceSupport: true,
26+
lspNotebooksSupport: this.lspNotebooksExperiment.isInNotebooksExperiment(),
2127
} as unknown) as LanguageClientOptions;
2228
}
2329
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { Uri } from 'vscode';
5+
import { IJupyterExtensionDependencyManager } from '../../common/application/types';
6+
import { IServiceContainer } from '../../ioc/types';
7+
import { JupyterExtensionIntegration } from '../../jupyter/jupyterIntegration';
8+
import { traceLog } from '../../logging';
9+
import { LanguageClientMiddleware } from '../languageClientMiddleware';
10+
11+
import { LanguageServerType } from '../types';
12+
13+
import { LspNotebooksExperiment } from './lspNotebooksExperiment';
14+
15+
export class NodeLanguageClientMiddleware extends LanguageClientMiddleware {
16+
private readonly lspNotebooksExperiment: LspNotebooksExperiment;
17+
18+
public constructor(serviceContainer: IServiceContainer, serverVersion?: string) {
19+
super(serviceContainer, LanguageServerType.Node, serverVersion);
20+
21+
this.lspNotebooksExperiment = serviceContainer.get<LspNotebooksExperiment>(LspNotebooksExperiment);
22+
this.setupHidingMiddleware(serviceContainer);
23+
}
24+
25+
protected shouldCreateHidingMiddleware(jupyterDependencyManager: IJupyterExtensionDependencyManager): boolean {
26+
return (
27+
super.shouldCreateHidingMiddleware(jupyterDependencyManager) &&
28+
!this.lspNotebooksExperiment.isInNotebooksExperiment()
29+
);
30+
}
31+
32+
protected async onExtensionChange(jupyterDependencyManager: IJupyterExtensionDependencyManager): Promise<void> {
33+
if (jupyterDependencyManager && jupyterDependencyManager.isJupyterExtensionInstalled) {
34+
await this.lspNotebooksExperiment.onJupyterInstalled();
35+
}
36+
37+
super.onExtensionChange(jupyterDependencyManager);
38+
}
39+
40+
protected async getPythonPathOverride(uri: Uri | undefined): Promise<string | undefined> {
41+
if (!uri || !this.lspNotebooksExperiment.isInNotebooksExperiment()) {
42+
return undefined;
43+
}
44+
45+
const jupyterExtensionIntegration = this.serviceContainer?.get<JupyterExtensionIntegration>(
46+
JupyterExtensionIntegration,
47+
);
48+
const jupyterPythonPathFunction = jupyterExtensionIntegration?.getJupyterPythonPathFunction();
49+
if (!jupyterPythonPathFunction) {
50+
return undefined;
51+
}
52+
53+
const result = await jupyterPythonPathFunction(uri);
54+
55+
if (result) {
56+
traceLog(`Jupyter provided interpreter path override: ${result}`);
57+
}
58+
59+
return result;
60+
}
61+
}

0 commit comments

Comments
 (0)