-
Notifications
You must be signed in to change notification settings - Fork 287
/
notebookPythonPathService.node.ts
156 lines (134 loc) · 6.87 KB
/
notebookPythonPathService.node.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
'use strict';
import { inject, injectable } from 'inversify';
import { Disposable, extensions, Uri, workspace } from 'vscode';
import { INotebookEditorProvider } from '../../notebooks/types';
import { IExtensionSingleActivationService } from '../../platform/activation/types';
import { IPythonApiProvider } from '../../platform/api/types';
import { PylanceExtension, PythonExtension } from '../../platform/common/constants';
import { getFilePath } from '../../platform/common/platform/fs-paths';
import { IConfigurationService } from '../../platform/common/types';
import { IInterpreterService } from '../../platform/interpreter/contracts';
import * as semver from 'semver';
import { traceInfo, traceVerbose } from '../../platform/logging';
import { IControllerSelection } from '../../notebooks/controllers/types';
/**
* Manages use of the Python extension's registerJupyterPythonPathFunction API which
* enables us to provide the python.exe path for a notebook as required for Pylance's
* LSP-based notebooks support.
*/
@injectable()
export class NotebookPythonPathService implements IExtensionSingleActivationService {
private extensionChangeHandler: Disposable | undefined;
private _isEnabled: boolean | undefined;
constructor(
@inject(IPythonApiProvider) private readonly apiProvider: IPythonApiProvider,
@inject(INotebookEditorProvider) private readonly notebookEditorProvider: INotebookEditorProvider,
@inject(IControllerSelection) private readonly notebookControllerSelection: IControllerSelection,
@inject(IInterpreterService) private readonly interpreterService: IInterpreterService,
@inject(IConfigurationService) private readonly configService: IConfigurationService
) {
if (!this._isPylanceExtensionInstalled()) {
this.extensionChangeHandler = extensions.onDidChange(this.extensionsChangeHandler.bind(this));
}
}
public async activate() {
if (!this.isPylanceUsingLspNotebooks()) {
return;
}
await this.apiProvider.getApi().then((api) => {
if (api.registerJupyterPythonPathFunction !== undefined) {
api.registerJupyterPythonPathFunction((uri) => this._jupyterPythonPathFunction(uri));
}
if (api.registerGetNotebookUriForTextDocumentUriFunction !== undefined) {
api.registerGetNotebookUriForTextDocumentUriFunction((uri) =>
this._getNotebookUriForTextDocumentUri(uri)
);
}
});
}
private async reset() {
this._isEnabled = undefined;
await this.activate();
}
private _isPylanceExtensionInstalled() {
return extensions.getExtension(PylanceExtension) !== undefined;
}
private async extensionsChangeHandler(): Promise<void> {
if (this._isPylanceExtensionInstalled() && this.extensionChangeHandler) {
this.extensionChangeHandler.dispose();
this.extensionChangeHandler = undefined;
await this.reset();
}
}
/**
* Returns a boolean indicating whether Pylance's LSP notebooks experiment is enabled.
* When this is True, the Python extension starts Pylance for notebooks instead of us.
*/
public isPylanceUsingLspNotebooks() {
if (this._isEnabled === undefined) {
const isInTreatmentGroup = this.configService.getSettings().pylanceLspNotebooksEnabled;
const pythonVersion = extensions.getExtension(PythonExtension)?.packageJSON.version;
const pylanceVersion = extensions.getExtension(PylanceExtension)?.packageJSON.version;
const pythonConfig = workspace.getConfiguration('python');
const languageServer = pythonConfig?.get<string>('languageServer');
// Only enable the experiment if we're in the treatment group and the installed
// versions of Python and Pylance support the experiment.
this._isEnabled = false;
if (languageServer !== 'Pylance' && languageServer !== 'Default') {
traceInfo(`LSP Notebooks experiment is disabled -- not using Pylance`);
} else if (!isInTreatmentGroup) {
traceInfo(`LSP Notebooks experiment is disabled -- not in treatment group`);
} else if (!pythonVersion) {
traceInfo(`LSP Notebooks experiment is disabled -- Python disabled or not installed`);
} else if (
semver.lte(pythonVersion, '2022.7.11371008') &&
!semver.prerelease(pythonVersion)?.includes('dev')
) {
traceInfo(`LSP Notebooks experiment is disabled -- Python does not support experiment`);
} else if (!pylanceVersion) {
traceInfo(`LSP Notebooks experiment is disabled -- Pylance disabled or not installed`);
} else if (
semver.lt(pylanceVersion, '2022.5.3-pre.1') &&
!semver.prerelease(pylanceVersion)?.includes('dev')
) {
traceInfo(`LSP Notebooks experiment is disabled -- Pylance does not support experiment`);
} else {
this._isEnabled = true;
traceInfo(`LSP Notebooks experiment is enabled`);
}
}
return this._isEnabled;
}
/**
* Called by the Python extension to give Jupyter a chance to override the python.exe
* path used by Pylance. Return undefined to allow Python to determine the path.
*/
private async _jupyterPythonPathFunction(uri: Uri): Promise<string | undefined> {
const notebook = this.notebookEditorProvider.findAssociatedNotebookDocument(uri);
if (!notebook) {
traceVerbose(`_jupyterPythonPathFunction: "${uri}" is not a notebook`);
return undefined;
}
const controller = this.notebookControllerSelection.getSelected(notebook);
const interpreter = controller
? controller.connection.interpreter
: await this.interpreterService.getActiveInterpreter(uri);
if (!interpreter) {
traceVerbose(`_jupyterPythonPathFunction: Couldn't find interpreter for "${uri}"`);
return undefined;
}
const pythonPath = getFilePath(interpreter.uri);
traceVerbose(`_jupyterPythonPathFunction: Giving Pylance "${pythonPath}" as python path for "${uri}"`);
return pythonPath;
}
private _getNotebookUriForTextDocumentUri(textDocumentUri: Uri): Uri | undefined {
if (textDocumentUri.scheme !== 'vscode-interactive-input') {
return undefined;
}
const notebookPath = `${textDocumentUri.fsPath.replace('\\InteractiveInput-', 'Interactive-')}.interactive`;
const notebookUri = textDocumentUri.with({ scheme: 'vscode-interactive', path: notebookPath });
return notebookUri;
}
}