diff --git a/news/3 Code Health/8096.md b/news/3 Code Health/8096.md new file mode 100644 index 00000000000..9e777d6f978 --- /dev/null +++ b/news/3 Code Health/8096.md @@ -0,0 +1 @@ +Add setting to allow usage of experimental pylance intellisense in notebooks. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c3b03ee0d30..d46f079ed67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3624,9 +3624,9 @@ } }, "@vscode/jupyter-lsp-middleware": { - "version": "0.2.17", - "resolved": "https://registry.npmjs.org/@vscode/jupyter-lsp-middleware/-/jupyter-lsp-middleware-0.2.17.tgz", - "integrity": "sha512-4aaNUpWB3BDj4gG/+awB0vCAQEQOrDbofPEbJ3QdpIZPrndlu6f6mxeDN+yDCZqh+aUU+b3uFGU489bdnCv+Qg==", + "version": "0.2.18", + "resolved": "https://registry.npmjs.org/@vscode/jupyter-lsp-middleware/-/jupyter-lsp-middleware-0.2.18.tgz", + "integrity": "sha512-IySB0yuU1+erzOCm6r1WvODzHErd8JZ2NlqfH+d+XdiqQbVfFJohivUzsrvCK62/HBPc3SQ1vuQ1Gx7Z4D267A==", "requires": { "vscode-languageclient": "7.0.0" } diff --git a/package.json b/package.json index 5d2edbb6dd5..2a2b30b5dc6 100644 --- a/package.json +++ b/package.json @@ -1859,6 +1859,12 @@ "default": true, "description": "Append a new empty cell to an interactive window file on running the currently last cell.", "scope": "resource" + }, + "jupyter.pylanceHandlesNotebooks": { + "type": "boolean", + "default": false, + "description": "Determines if pylance's experimental notebook support is used or not", + "scope": "machine" } } }, @@ -1974,7 +1980,7 @@ "@jupyterlab/services": "^6.1.17", "@lumino/widgets": "^1.28.0", "@nteract/messaging": "^7.0.0", - "@vscode/jupyter-lsp-middleware": "^0.2.17", + "@vscode/jupyter-lsp-middleware": "^0.2.18", "ansi-to-html": "^0.6.7", "arch": "^2.1.0", "bootstrap": "^4.3.1", diff --git a/src/client/common/configSettings.ts b/src/client/common/configSettings.ts index 16909f29b51..d5f4a01ee1e 100644 --- a/src/client/common/configSettings.ts +++ b/src/client/common/configSettings.ts @@ -97,6 +97,7 @@ export class JupyterSettings implements IWatchableJupyterSettings { public verboseLogging: boolean = false; public showVariableViewWhenDebugging: boolean = true; public newCellOnRunLast: boolean = true; + public pylanceHandlesNotebooks: boolean = false; public variableTooltipFields: IVariableTooltipFields = { python: { diff --git a/src/client/common/types.ts b/src/client/common/types.ts index 52fc3cffb09..15e693f4795 100644 --- a/src/client/common/types.ts +++ b/src/client/common/types.ts @@ -177,6 +177,7 @@ export interface IJupyterSettings { readonly variableTooltipFields: IVariableTooltipFields; readonly showVariableViewWhenDebugging: boolean; readonly newCellOnRunLast: boolean; + readonly pylanceHandlesNotebooks?: boolean; } export interface IVariableTooltipFields { diff --git a/src/client/datascience/notebook/intellisense/intellisenseProvider.ts b/src/client/datascience/notebook/intellisense/intellisenseProvider.ts index 9fde5710f3b..bcddd317cf0 100644 --- a/src/client/datascience/notebook/intellisense/intellisenseProvider.ts +++ b/src/client/datascience/notebook/intellisense/intellisenseProvider.ts @@ -2,10 +2,10 @@ // Licensed under the MIT License. 'use strict'; import { inject, injectable } from 'inversify'; -import { NotebookDocument, Uri } from 'vscode'; +import { ConfigurationChangeEvent, NotebookDocument, Uri } from 'vscode'; import { IExtensionSyncActivationService } from '../../../activation/types'; import { IVSCodeNotebook, IWorkspaceService } from '../../../common/application/types'; -import { IDisposableRegistry } from '../../../common/types'; +import { IConfigurationService, IDisposableRegistry } from '../../../common/types'; import { IInterpreterService } from '../../../interpreter/contracts'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { getInterpreterId } from '../../../pythonEnvironments/info/interpreter'; @@ -34,7 +34,8 @@ export class IntellisenseProvider implements IExtensionSyncActivationService { @inject(IVSCodeNotebook) private readonly notebooks: IVSCodeNotebook, @inject(IInterpreterService) private readonly interpreterService: IInterpreterService, @inject(IWorkspaceService) private readonly workspaceService: IWorkspaceService, - @inject(IInteractiveWindowProvider) private readonly interactiveWindowProvider: IInteractiveWindowProvider + @inject(IInteractiveWindowProvider) private readonly interactiveWindowProvider: IInteractiveWindowProvider, + @inject(IConfigurationService) private readonly configService: IConfigurationService ) {} public activate() { // Sign up for kernel change events on notebooks @@ -50,6 +51,9 @@ export class IntellisenseProvider implements IExtensionSyncActivationService { // can compare during intellisense operations. this.getActiveInterpreterSync(undefined); this.interpreterService.onDidChangeInterpreter(this.handleInterpreterChange, this, this.disposables); + + // If we change the language server type, we need to restart + this.workspaceService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); } private handleInterpreterChange() { @@ -157,12 +161,18 @@ export class IntellisenseProvider implements IExtensionSyncActivationService { private async ensureLanguageServer(interpreter: PythonEnvironment | undefined, notebook: NotebookDocument) { // We should have one language server per active interpreter. + // Check the setting to determine if we let pylance handle notebook intellisense or not + const middlewareType = this.configService.getSettings(notebook.uri).pylanceHandlesNotebooks + ? 'pylance' + : 'jupyter'; + // See if we already have one for this interpreter or not const id = interpreter ? getInterpreterId(interpreter) : undefined; if (id && !this.servers.has(id) && interpreter) { // We don't already have one. Create a new one for this interpreter. // The logic for whether or not const languageServerPromise = LanguageServer.createLanguageServer( + middlewareType, interpreter, this.shouldAllowIntellisense.bind(this) ).then((l) => { @@ -175,4 +185,18 @@ export class IntellisenseProvider implements IExtensionSyncActivationService { return id ? this.servers.get(id) : undefined; } + + private onDidChangeConfiguration(event: ConfigurationChangeEvent) { + if ( + event.affectsConfiguration('jupyter.pylanceHandlesNotebooks') || + event.affectsConfiguration('python.languageServer') + ) { + // Dispose all servers and start over for each open notebook + this.servers.forEach((p) => p.then((s) => s?.dispose())); + this.servers.clear(); + + // For all currently open notebooks, launch their language server + this.notebooks.notebookDocuments.forEach((n) => this.openedNotebook(n).ignoreErrors()); + } + } } diff --git a/src/client/datascience/notebook/intellisense/languageServer.ts b/src/client/datascience/notebook/intellisense/languageServer.ts index 9939ad5775a..cac5fd347cf 100644 --- a/src/client/datascience/notebook/intellisense/languageServer.ts +++ b/src/client/datascience/notebook/intellisense/languageServer.ts @@ -32,7 +32,7 @@ import * as path from 'path'; import * as fs from 'fs-extra'; import { FileBasedCancellationStrategy } from './fileBasedCancellationStrategy'; import { NOTEBOOK_SELECTOR, PYTHON_LANGUAGE } from '../../../common/constants'; -import { createNotebookMiddleware, NotebookMiddleware } from '@vscode/jupyter-lsp-middleware'; +import { createNotebookMiddleware, createPylanceMiddleware, NotebookMiddleware } from '@vscode/jupyter-lsp-middleware'; import { traceInfo } from '../../../common/logger'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { sleep } from '../../../common/utils/async'; @@ -144,6 +144,7 @@ export class LanguageServer implements Disposable { } public static async createLanguageServer( + middlewareType: 'pylance' | 'jupyter', interpreter: PythonEnvironment, shouldAllowIntellisense: (uri: Uri, interpreterId: string, interpreterPath: string) => boolean ): Promise { @@ -153,15 +154,22 @@ export class LanguageServer implements Disposable { let languageClient: LanguageClient | undefined; const outputChannel = window.createOutputChannel(`${interpreter.displayName || 'notebook'}-languageserver`); const interpreterId = getInterpreterId(interpreter); - const middleware = createNotebookMiddleware( - notebookApi, - () => languageClient, - () => noop, // Don't trace output. Slows things down too much - NOTEBOOK_SELECTOR, - /.*\.(ipynb|interactive)/m, - interpreter.path, - (uri) => shouldAllowIntellisense(uri, interpreterId, interpreter.path) - ); + const middleware = + middlewareType == 'jupyter' + ? createNotebookMiddleware( + notebookApi, + () => languageClient, + () => noop, // Don't trace output. Slows things down too much + NOTEBOOK_SELECTOR, + /.*\.(ipynb|interactive)/m, + interpreter.path, + (uri) => shouldAllowIntellisense(uri, interpreterId, interpreter.path) + ) + : createPylanceMiddleware( + () => languageClient, + interpreter.path, + (uri) => shouldAllowIntellisense(uri, interpreterId, interpreter.path) + ); // Client options should be the same for all servers we support. const clientOptions: LanguageClientOptions = {