From fb507244835bcf3a1f16e29f658b914460340dc9 Mon Sep 17 00:00:00 2001 From: rchiodo Date: Fri, 18 Sep 2020 13:05:09 -0700 Subject: [PATCH 1/9] Ideas for lang server API with python extension --- src/client/activation/types.ts | 6 +- .../common/application/customEditorService.ts | 48 +-------------- .../datascience/api/jupyterIntegration.ts | 59 +++++++++++++++---- .../intellisense/intellisenseProvider.ts | 23 +++++--- .../intellisense/notebookLanguageServer.ts | 59 +++++++++++++++++++ src/client/datascience/serviceRegistry.ts | 6 +- 6 files changed, 130 insertions(+), 71 deletions(-) create mode 100644 src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index 9d8549b9b8a1..aa6fd22ab61f 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -18,6 +18,7 @@ import { TextDocumentContentChangeEvent } from 'vscode'; import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; +import * as vscodeprotocol from 'vscode-languageserver-protocol'; import { NugetPackage } from '../common/nuget/types'; import { IDisposable, IOutputChannel, LanguageServerDownloadChannels, Resource } from '../common/types'; import { PythonEnvironment } from '../pythonEnvironments/info'; @@ -95,7 +96,10 @@ export interface ILanguageServer SignatureHelpProvider, Partial, Partial, - IDisposable {} + IDisposable { + readonly connection?: vscodeprotocol.ProtocolConnection; + readonly capabilities?: vscodeprotocol.ServerCapabilities; +} export const ILanguageServerActivator = Symbol('ILanguageServerActivator'); export interface ILanguageServerActivator extends ILanguageServer { diff --git a/src/client/common/application/customEditorService.ts b/src/client/common/application/customEditorService.ts index 4163c9af509d..34e8f3dc5fac 100644 --- a/src/client/common/application/customEditorService.ts +++ b/src/client/common/application/customEditorService.ts @@ -6,20 +6,14 @@ import * as vscode from 'vscode'; import { UseCustomEditorApi } from '../constants'; import { traceError } from '../logger'; -import { IExtensionContext } from '../types'; import { noop } from '../utils/misc'; -import { CustomEditorProvider, ICommandManager, ICustomEditorService, IWorkspaceService } from './types'; - -const EditorAssociationUpdatedKey = 'EditorAssociationUpdatedToUseCustomEditor'; -const ViewType = 'ms-python.python.notebook.ipynb'; +import { CustomEditorProvider, ICommandManager, ICustomEditorService } from './types'; @injectable() export class CustomEditorService implements ICustomEditorService { constructor( @inject(ICommandManager) private commandManager: ICommandManager, - @inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean, - @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, - @inject(IExtensionContext) private readonly extensionContext: IExtensionContext + @inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean ) { this.enableCustomEditors().catch((e) => traceError(`Error setting up custom editors: `, e)); } @@ -48,42 +42,6 @@ export class CustomEditorService implements ICustomEditorService { // tslint:disable-next-line: no-any private async enableCustomEditors() { - // This code is temporary. - const settings = this.workspace.getConfiguration('workbench', undefined); - const editorAssociations = settings.get('editorAssociations') as { - viewType: string; - filenamePattern: string; - }[]; - - // Update the settings. - if ( - this.useCustomEditorApi && - (!Array.isArray(editorAssociations) || - editorAssociations.length === 0 || - !editorAssociations.find((item) => item.viewType === ViewType)) - ) { - editorAssociations.push({ - viewType: ViewType, - filenamePattern: '*.ipynb' - }); - await Promise.all([ - this.extensionContext.globalState.update(EditorAssociationUpdatedKey, true), - settings.update('editorAssociations', editorAssociations, vscode.ConfigurationTarget.Global) - ]); - } - - // Revert the settings. - if ( - !this.useCustomEditorApi && - this.extensionContext.globalState.get(EditorAssociationUpdatedKey, false) && - Array.isArray(editorAssociations) && - editorAssociations.find((item) => item.viewType === ViewType) - ) { - const updatedSettings = editorAssociations.filter((item) => item.viewType !== ViewType); - await Promise.all([ - this.extensionContext.globalState.update(EditorAssociationUpdatedKey, false), - settings.update('editorAssociations', updatedSettings, vscode.ConfigurationTarget.Global) - ]); - } + return; } } diff --git a/src/client/datascience/api/jupyterIntegration.ts b/src/client/datascience/api/jupyterIntegration.ts index 36bb40d93ff0..723164126625 100644 --- a/src/client/datascience/api/jupyterIntegration.ts +++ b/src/client/datascience/api/jupyterIntegration.ts @@ -8,8 +8,11 @@ import { inject, injectable } from 'inversify'; import { dirname } from 'path'; import { CancellationToken, Event, Uri } from 'vscode'; +import * as vscodeprotocol from 'vscode-languageserver-protocol'; +import { ILanguageServerCache } from '../../activation/types'; import { InterpreterUri } from '../../common/installer/types'; import { IExtensions, IInstaller, InstallerResponse, Product, Resource } from '../../common/types'; +import { isResource } from '../../common/utils/misc'; import { getDebugpyPackagePath } from '../../debugger/extension/adapter/remoteLaunchers'; import { IEnvironmentActivationService } from '../../interpreter/activation/types'; import { IInterpreterQuickPickItem, IInterpreterSelector } from '../../interpreter/configuration/types'; @@ -18,6 +21,12 @@ import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; import { WindowsStoreInterpreter } from '../../pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; import { PythonEnvironment } from '../../pythonEnvironments/info'; +export type LanguageServerConnection = { + readonly connection: vscodeprotocol.ProtocolConnection; + readonly capabilities: vscodeprotocol.ServerCapabilities; + dispose(): void; +}; + type PythonApiForJupyterExtension = { /** * IInterpreterService @@ -57,9 +66,14 @@ type PythonApiForJupyterExtension = { * Returns path to where `debugpy` is. In python extension this is `/pythonFiles/lib/python`. */ getDebuggerPath(): Promise; + /** + * Returns a ProtocolConnection that can be used for communicating with a language server process. + * @param resource file that determines which connection to return + */ + getLanguageServerConnection(resource?: InterpreterUri): Promise; }; -type JupyterExtensionApi = { +export type JupyterExtensionApi = { registerPythonApi(interpreterService: PythonApiForJupyterExtension): void; }; @@ -71,19 +85,11 @@ export class JupyterExtensionIntegration { @inject(IInterpreterSelector) private readonly interpreterSelector: IInterpreterSelector, @inject(WindowsStoreInterpreter) private readonly windowsStoreInterpreter: IWindowsStoreInterpreter, @inject(IInstaller) private readonly installer: IInstaller, - @inject(IEnvironmentActivationService) private readonly envActivation: IEnvironmentActivationService + @inject(IEnvironmentActivationService) private readonly envActivation: IEnvironmentActivationService, + @inject(ILanguageServerCache) private readonly languageServerCache: ILanguageServerCache ) {} - public async integrateWithJupyterExtension(): Promise { - const jupyterExtension = this.extensions.getExtension('ms-ai-tools.jupyter'); - if (!jupyterExtension) { - return; - } - await jupyterExtension.activate(); - if (!jupyterExtension.isActive) { - return; - } - const jupyterExtensionApi = jupyterExtension.exports; + public registerApi(jupyterExtensionApi: JupyterExtensionApi) { jupyterExtensionApi.registerPythonApi({ onDidChangeInterpreter: this.interpreterService.onDidChangeInterpreter, getActiveInterpreter: async (resource?: Uri) => this.interpreterService.getActiveInterpreter(resource), @@ -104,7 +110,34 @@ export class JupyterExtensionIntegration { resource?: InterpreterUri, cancel?: CancellationToken ): Promise => this.installer.install(product, resource, cancel), - getDebuggerPath: async () => dirname(getDebugpyPackagePath()) + getDebuggerPath: async () => dirname(getDebugpyPackagePath()), + getLanguageServerConnection: async (r) => { + const resource = isResource(r) ? r : undefined; + const interpreter = !isResource(r) ? r : undefined; + const client = await this.languageServerCache.get(resource, interpreter); + + // Some langauge servers don't support the connection yet. (like Jedi until we switch to LSP) + if (client && client.connection && client.capabilities) { + return { + connection: client.connection, + capabilities: client.capabilities, + dispose: client.dispose + }; + } + return undefined; + } }); } + + public async integrateWithJupyterExtension(): Promise { + const jupyterExtension = this.extensions.getExtension('ms-ai-tools.jupyter'); + if (!jupyterExtension) { + return; + } + await jupyterExtension.activate(); + if (!jupyterExtension.isActive) { + return; + } + this.registerApi(jupyterExtension.exports); + } } diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts index 64909c7e06a5..0861c66768bc 100644 --- a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts +++ b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts @@ -22,7 +22,6 @@ import { import { CancellationToken } from 'vscode-jsonrpc'; import * as vscodeLanguageClient from 'vscode-languageclient/node'; import { concatMultilineString } from '../../../../datascience-ui/common'; -import { ILanguageServer, ILanguageServerCache } from '../../../activation/types'; import { IWorkspaceService } from '../../../common/application/types'; import { CancellationError } from '../../../common/cancellation'; import { traceError, traceWarning } from '../../../common/logger'; @@ -34,6 +33,7 @@ import { HiddenFileFormatString } from '../../../constants'; import { IInterpreterService } from '../../../interpreter/contracts'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; import { sendTelemetryWhenDone } from '../../../telemetry'; +import { JupyterExtensionIntegration } from '../../api/jupyterIntegration'; import { Identifiers, Settings, Telemetry } from '../../constants'; import { ICell, @@ -65,6 +65,7 @@ import { convertToVSCodeCompletionItem } from './conversion'; import { IntellisenseDocument } from './intellisenseDocument'; +import { NotebookLanguageServer } from './notebookLanguageServer'; // These regexes are used to get the text from jupyter output by recognizing escape charactor \x1b const DocStringRegex = /\x1b\[1;31mDocstring:\x1b\[0m\s+([\s\S]*?)\r?\n\x1b\[1;31m/; @@ -110,7 +111,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { @inject(IDataScienceFileSystem) private fs: IDataScienceFileSystem, @inject(INotebookProvider) private notebookProvider: INotebookProvider, @inject(IInterpreterService) private interpreterService: IInterpreterService, - @inject(ILanguageServerCache) private languageServerCache: ILanguageServerCache, + @inject(JupyterExtensionIntegration) private jupyterApiProvider: JupyterExtensionIntegration, @inject(IJupyterVariables) @named(Identifiers.ALL_VARIABLES) private variableProvider: IJupyterVariables ) {} @@ -198,7 +199,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { return this.documentPromise.promise; } - protected async getLanguageServer(token: CancellationToken): Promise { + protected async getLanguageServer(token: CancellationToken): Promise { // Resource should be our potential resource if its set. Otherwise workspace root const resource = this.potentialResource || @@ -225,22 +226,26 @@ export class IntellisenseProvider implements IInteractiveWindowListener { // Get an instance of the language server (so we ref count it ) try { - const languageServer = await this.languageServerCache.get(resource, interpreter); + const languageServer = await NotebookLanguageServer.create( + this.jupyterApiProvider, + resource, + interpreter + ); // Dispose of our old language service this.languageServer?.dispose(); // This new language server does not know about our document, so tell it. const document = await this.getDocument(); - if (document && languageServer.handleOpen && languageServer.handleChanges) { + if (document && languageServer) { // If we already sent an open document, that means we need to send both the open and // the new changes if (this.sentOpenDocument) { - languageServer.handleOpen(document); - languageServer.handleChanges(document, document.getFullContentChanges()); + languageServer.sendOpen(document); + languageServer.sendChanges(document, document.getFullContentChanges()); } else { this.sentOpenDocument = true; - languageServer.handleOpen(document); + languageServer.sendOpen(document); } } @@ -352,7 +357,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { token: CancellationToken ): Promise { const [languageServer, document] = await Promise.all([this.getLanguageServer(token), this.getDocument()]); - if (languageServer && languageServer.resolveCompletionItem && document) { + if (languageServer && document) { const vscodeCompItem: CompletionItem = convertToVSCodeCompletionItem(item); // Needed by Jedi in completionSource.ts to resolve the item diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts new file mode 100644 index 000000000000..1adea0892d7f --- /dev/null +++ b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts @@ -0,0 +1,59 @@ +import { Disposable } from 'vscode'; +import * as c2p from 'vscode-languageclient/lib/common/codeConverter'; +import * as p2c from 'vscode-languageclient/lib/common/protocolConverter'; +import * as vscodeprotocol from 'vscode-languageserver-protocol'; +import { Resource } from '../../../common/types'; +import { createDeferred } from '../../../common/utils/async'; +import { PythonEnvironment } from '../../../pythonEnvironments/info'; +import { JupyterExtensionIntegration, LanguageServerConnection } from '../../api/jupyterIntegration'; + +/** + * Class that wraps a language server for use by webview based notebooks + * */ +export class NotebookLanguageServer implements Disposable { + private code2p = c2p.createConverter(); + private prot2c = p2c.createConverter(); + private constructor(private connection: LanguageServerConnection) { + } + + public static async create( + jupyterApiProvider: JupyterExtensionIntegration, + resource: Resource, + interpreter: PythonEnvironment | undefined + ): Promise { + // Create a server wrapper if we can get a connection to a language server + const deferred = createDeferred(); + jupyterApiProvider.registerApi({ + registerPythonApi: (api) => { + api.getLanguageServerConnection(interpreter ? interpreter : resource) + .then((c) => { + if (c) { + deferred.resolve(new NotebookLanguageServer(c)); + } else { + deferred.resolve(undefined); + } + }) + .catch(deferred.reject); + } + }); + return deferred.promise; + } + + public dispose() { + this.connection.dispose(); + } + + public sendOpen() {} + + public sendChanged() {} + + public provideCompletionItems() {} + + public provideSignatureHelp() {} + + public provideHover() {} + + public resolveCompletionItem() { + if (this.connection.capabilities.); + } +} diff --git a/src/client/datascience/serviceRegistry.ts b/src/client/datascience/serviceRegistry.ts index 05b44495fa04..2d9a6be005d1 100644 --- a/src/client/datascience/serviceRegistry.ts +++ b/src/client/datascience/serviceRegistry.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import * as vscode from 'vscode'; +//import * as vscode from 'vscode'; import { IExtensionSingleActivationService } from '../activation/types'; import { UseCustomEditorApi, UseVSCodeNotebookEditorApi } from '../common/constants'; import { NotebookEditorSupport } from '../common/experiments/groups'; @@ -189,8 +189,8 @@ import { // tslint:disable-next-line: max-func-body-length export function registerTypes(serviceManager: IServiceManager) { const experiments = serviceManager.get(IExperimentsManager); - const inCustomEditorApiExperiment = experiments.inExperiment(NotebookEditorSupport.customEditorExperiment); - const usingCustomEditor = inCustomEditorApiExperiment && !vscode.env.appName.includes('Insider'); // Don't use app manager as it's not available yet. + //const inCustomEditorApiExperiment = experiments.inExperiment(NotebookEditorSupport.customEditorExperiment); + const usingCustomEditor = true; //inCustomEditorApiExperiment && !vscode.env.appName.includes('Insider'); // Don't use app manager as it's not available yet. const useVSCodeNotebookAPI = experiments.inExperiment(NotebookEditorSupport.nativeNotebookExperiment) && !usingCustomEditor; serviceManager.addSingletonInstance(UseCustomEditorApi, usingCustomEditor); serviceManager.addSingletonInstance(UseVSCodeNotebookEditorApi, useVSCodeNotebookAPI); From 32ce9982363eb55c9a7506df9df5657bcc55f297 Mon Sep 17 00:00:00 2001 From: rchiodo Date: Mon, 21 Sep 2020 10:54:52 -0700 Subject: [PATCH 2/9] Working with new API --- src/client/activation/common/activatorBase.ts | 34 ++++++ .../activation/refCountedLanguageServer.ts | 8 ++ .../intellisense/intellisenseProvider.ts | 8 +- .../intellisense/notebookLanguageServer.ts | 104 +++++++++++++++--- .../datascience/intellisense.unit.test.ts | 23 +++- .../mockJupyterExtensionIntegration.ts | 29 +++++ 6 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 src/test/datascience/mockJupyterExtensionIntegration.ts diff --git a/src/client/activation/common/activatorBase.ts b/src/client/activation/common/activatorBase.ts index 43ab2777cfc6..ddabddaccd9a 100644 --- a/src/client/activation/common/activatorBase.ts +++ b/src/client/activation/common/activatorBase.ts @@ -24,6 +24,7 @@ import { import * as vscodeLanguageClient from 'vscode-languageclient/node'; import { injectable } from 'inversify'; +import { noop } from 'lodash'; import { IWorkspaceService } from '../../common/application/types'; import { traceDecorators } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; @@ -72,6 +73,39 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi this.manager.disconnect(); } + public get connection() { + const languageClient = this.getLanguageClient(); + if (languageClient) { + const dummyDisposable = { + dispose: noop + }; + // Return an object that looks like a connection + return { + sendNotification: languageClient.sendNotification.bind(languageClient), + sendRequest: languageClient.sendRequest.bind(languageClient), + sendProgress: languageClient.sendProgress.bind(languageClient), + onRequest: languageClient.onRequest.bind(languageClient), + onNotification: languageClient.onNotification.bind(languageClient), + onProgress: languageClient.onProgress.bind(languageClient), + // tslint:disable-next-line: no-any + trace: (v: any) => (languageClient.trace = v), + onError: () => dummyDisposable, + onClose: () => dummyDisposable, + onDispose: () => dummyDisposable, + onUnhandledNotification: () => dummyDisposable, + dispose: this.manager.dispose.bind(this.manager), + listen: noop + }; + } + } + + public get capabilities() { + const languageClient = this.getLanguageClient(); + if (languageClient) { + return languageClient.initializeResult?.capabilities; + } + } + public handleOpen(document: TextDocument): void { const languageClient = this.getLanguageClient(); if (languageClient) { diff --git a/src/client/activation/refCountedLanguageServer.ts b/src/client/activation/refCountedLanguageServer.ts index f589d6257ac7..e19390530fd9 100644 --- a/src/client/activation/refCountedLanguageServer.ts +++ b/src/client/activation/refCountedLanguageServer.ts @@ -65,6 +65,14 @@ export class RefCountedLanguageServer implements ILanguageServerActivator { this.impl.clearAnalysisCache ? this.impl.clearAnalysisCache() : noop(); } + public get connection() { + return this.impl.connection; + } + + public get capabilities() { + return this.impl.capabilities; + } + public handleChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { this.impl.handleChanges ? this.impl.handleChanges(document, changes) : noop(); } diff --git a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts index 0861c66768bc..9124ccd5a842 100644 --- a/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts +++ b/src/client/datascience/interactive-common/intellisense/intellisenseProvider.ts @@ -102,7 +102,7 @@ export class IntellisenseProvider implements IInteractiveWindowListener { private notebookType: 'interactive' | 'native' = 'interactive'; private potentialResource: Uri | undefined; private sentOpenDocument: boolean = false; - private languageServer: ILanguageServer | undefined; + private languageServer: NotebookLanguageServer | undefined; private resource: Resource; private interpreter: PythonEnvironment | undefined; @@ -383,12 +383,12 @@ export class IntellisenseProvider implements IInteractiveWindowListener { if (document) { // Broadcast an update to the language server const languageServer = await this.getLanguageServer(CancellationToken.None); - if (languageServer && languageServer.handleChanges && languageServer.handleOpen) { + if (languageServer) { if (!this.sentOpenDocument) { this.sentOpenDocument = true; - return languageServer.handleOpen(document); + return languageServer.sendOpen(document); } else { - return languageServer.handleChanges(document, changes); + return languageServer.sendChanges(document, changes); } } } diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts index 1adea0892d7f..31d3b0e90163 100644 --- a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts +++ b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts @@ -1,6 +1,16 @@ -import { Disposable } from 'vscode'; +import { + CancellationToken, + CompletionContext, + CompletionItem, + Disposable, + Position, + SignatureHelpContext, + TextDocument, + TextDocumentContentChangeEvent +} from 'vscode'; import * as c2p from 'vscode-languageclient/lib/common/codeConverter'; import * as p2c from 'vscode-languageclient/lib/common/protocolConverter'; +import * as vscodeLanguageClient from 'vscode-languageclient/node'; import * as vscodeprotocol from 'vscode-languageserver-protocol'; import { Resource } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; @@ -9,11 +19,17 @@ import { JupyterExtensionIntegration, LanguageServerConnection } from '../../api /** * Class that wraps a language server for use by webview based notebooks - * */ + */ export class NotebookLanguageServer implements Disposable { - private code2p = c2p.createConverter(); - private prot2c = p2c.createConverter(); - private constructor(private connection: LanguageServerConnection) { + private code2ProtocolConverter = c2p.createConverter(); + private protocol2CodeConverter = p2c.createConverter(); + private connection: vscodeprotocol.ProtocolConnection; + private capabilities: vscodeprotocol.ServerCapabilities; + private disposeConnection: () => void; + private constructor(ls: LanguageServerConnection) { + this.connection = ls.connection; + this.capabilities = ls.capabilities; + this.disposeConnection = ls.dispose.bind(ls); } public static async create( @@ -40,20 +56,82 @@ export class NotebookLanguageServer implements Disposable { } public dispose() { - this.connection.dispose(); + this.disposeConnection(); } - public sendOpen() {} + public sendOpen(document: TextDocument) { + this.connection.sendNotification( + vscodeLanguageClient.DidOpenTextDocumentNotification.type, + this.code2ProtocolConverter.asOpenTextDocumentParams(document) + ); + } - public sendChanged() {} + public sendChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { + // If the language client doesn't support incremental, just send the whole document + if (this.capabilities.textDocumentSync === vscodeLanguageClient.TextDocumentSyncKind.Full) { + this.connection.sendNotification( + vscodeLanguageClient.DidChangeTextDocumentNotification.type, + this.code2ProtocolConverter.asChangeTextDocumentParams(document) + ); + } else { + this.connection.sendNotification( + vscodeLanguageClient.DidChangeTextDocumentNotification.type, + this.code2ProtocolConverter.asChangeTextDocumentParams({ + document, + contentChanges: changes + }) + ); + } + } - public provideCompletionItems() {} + public async provideCompletionItems( + document: TextDocument, + position: Position, + token: CancellationToken, + context: CompletionContext + ) { + const args = this.code2ProtocolConverter.asCompletionParams(document, position, context); + const result = await this.connection.sendRequest(vscodeLanguageClient.CompletionRequest.type, args, token); + if (result) { + return this.protocol2CodeConverter.asCompletionResult(result); + } + } - public provideSignatureHelp() {} + public async provideSignatureHelp( + document: TextDocument, + position: Position, + token: CancellationToken, + _context: SignatureHelpContext + ) { + const args: vscodeLanguageClient.TextDocumentPositionParams = { + textDocument: this.code2ProtocolConverter.asTextDocumentIdentifier(document), + position: this.code2ProtocolConverter.asPosition(position) + }; + const result = await this.connection.sendRequest(vscodeLanguageClient.SignatureHelpRequest.type, args, token); + if (result) { + return this.protocol2CodeConverter.asSignatureHelp(result); + } + } - public provideHover() {} + public async provideHover(document: TextDocument, position: Position, token: CancellationToken) { + const args: vscodeLanguageClient.TextDocumentPositionParams = { + textDocument: this.code2ProtocolConverter.asTextDocumentIdentifier(document), + position: this.code2ProtocolConverter.asPosition(position) + }; + const result = await this.connection.sendRequest(vscodeLanguageClient.HoverRequest.type, args, token); + if (result) { + return this.protocol2CodeConverter.asHover(result); + } + } - public resolveCompletionItem() { - if (this.connection.capabilities.); + public async resolveCompletionItem(item: CompletionItem, token: CancellationToken) { + const result = await this.connection.sendRequest( + vscodeLanguageClient.CompletionResolveRequest.type, + this.code2ProtocolConverter.asCompletionItem(item), + token + ); + if (result) { + return this.protocol2CodeConverter.asCompletionItem(result); + } } } diff --git a/src/test/datascience/intellisense.unit.test.ts b/src/test/datascience/intellisense.unit.test.ts index 8089af05eeb1..8e783d7a1ee0 100644 --- a/src/test/datascience/intellisense.unit.test.ts +++ b/src/test/datascience/intellisense.unit.test.ts @@ -10,7 +10,7 @@ import { Uri } from 'vscode'; import { LanguageServerType } from '../../client/activation/types'; import { IWorkspaceService } from '../../client/common/application/types'; import { PythonSettings } from '../../client/common/configSettings'; -import { IConfigurationService } from '../../client/common/types'; +import { IConfigurationService, IInstaller } from '../../client/common/types'; import { Identifiers } from '../../client/datascience/constants'; import { IntellisenseDocument } from '../../client/datascience/interactive-common/intellisense/intellisenseDocument'; import { IntellisenseProvider } from '../../client/datascience/interactive-common/intellisense/intellisenseProvider'; @@ -21,10 +21,15 @@ import { } from '../../client/datascience/interactive-common/interactiveWindowTypes'; import { JupyterVariables } from '../../client/datascience/jupyter/jupyterVariables'; import { ICell, IDataScienceFileSystem, INotebookProvider } from '../../client/datascience/types'; +import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; +import { IInterpreterSelector } from '../../client/interpreter/configuration/types'; import { IInterpreterService } from '../../client/interpreter/contracts'; +import { IWindowsStoreInterpreter } from '../../client/interpreter/locators/types'; import { createEmptyCell, generateTestCells } from '../../datascience-ui/interactive-common/mainState'; import { generateReverseChange, IMonacoTextModel } from '../../datascience-ui/react-common/monacoHelpers'; import { MockAutoSelectionService } from '../mocks/autoSelector'; +import { MockExtensions } from './mockExtensions'; +import { MockJupyterExtensionIntegration } from './mockJupyterExtensionIntegration'; import { MockLanguageServerCache } from './mockLanguageServerCache'; // tslint:disable:no-any unified-signatures @@ -76,13 +81,27 @@ suite('DataScience Intellisense Unit Tests', () => { .returns((f1: Uri, f2: Uri) => { return f1?.fsPath?.toLowerCase() === f2.fsPath?.toLowerCase(); }); + const selector = TypeMoq.Mock.ofType(); + const storeInterpreter = TypeMoq.Mock.ofType(); + const installer = TypeMoq.Mock.ofType(); + const envService = TypeMoq.Mock.ofType(); + + const extensionRegister = new MockJupyterExtensionIntegration( + new MockExtensions(), + interpreterService.object, + selector.object, + storeInterpreter.object, + installer.object, + envService.object, + languageServerCache + ); intellisenseProvider = new IntellisenseProvider( workspaceService.object, fileSystem.object, notebookProvider.object, interpreterService.object, - languageServerCache, + extensionRegister, instance(variableProvider) ); intellisenseDocument = await intellisenseProvider.getDocument(); diff --git a/src/test/datascience/mockJupyterExtensionIntegration.ts b/src/test/datascience/mockJupyterExtensionIntegration.ts new file mode 100644 index 000000000000..047bba26c0d0 --- /dev/null +++ b/src/test/datascience/mockJupyterExtensionIntegration.ts @@ -0,0 +1,29 @@ +import { ILanguageServerCache } from '../../client/activation/types'; +import { IExtensions, IInstaller } from '../../client/common/types'; +import { JupyterExtensionIntegration } from '../../client/datascience/api/jupyterIntegration'; +import { IEnvironmentActivationService } from '../../client/interpreter/activation/types'; +import { IInterpreterSelector } from '../../client/interpreter/configuration/types'; +import { IInterpreterService } from '../../client/interpreter/contracts'; +import { IWindowsStoreInterpreter } from '../../client/interpreter/locators/types'; + +export class MockJupyterExtensionIntegration extends JupyterExtensionIntegration { + constructor( + extensions: IExtensions, + interpreterService: IInterpreterService, + interpreterSelector: IInterpreterSelector, + windowsStoreInterpreter: IWindowsStoreInterpreter, + installer: IInstaller, + envActivation: IEnvironmentActivationService, + languageServerCache: ILanguageServerCache + ) { + super( + extensions, + interpreterService, + interpreterSelector, + windowsStoreInterpreter, + installer, + envActivation, + languageServerCache + ); + } +} From 4a87d94d938846af0aa453eac545a4178f70671a Mon Sep 17 00:00:00 2001 From: rchiodo Date: Mon, 21 Sep 2020 11:13:04 -0700 Subject: [PATCH 3/9] Minimize API surface --- src/client/activation/common/activatorBase.ts | 14 +- src/client/activation/types.ts | 3 +- .../datascience/api/jupyterIntegration.ts | 152 +++++++++++++++++- .../intellisense/notebookLanguageServer.ts | 8 +- 4 files changed, 152 insertions(+), 25 deletions(-) diff --git a/src/client/activation/common/activatorBase.ts b/src/client/activation/common/activatorBase.ts index ddabddaccd9a..df07c5eca200 100644 --- a/src/client/activation/common/activatorBase.ts +++ b/src/client/activation/common/activatorBase.ts @@ -24,7 +24,6 @@ import { import * as vscodeLanguageClient from 'vscode-languageclient/node'; import { injectable } from 'inversify'; -import { noop } from 'lodash'; import { IWorkspaceService } from '../../common/application/types'; import { traceDecorators } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; @@ -76,9 +75,6 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi public get connection() { const languageClient = this.getLanguageClient(); if (languageClient) { - const dummyDisposable = { - dispose: noop - }; // Return an object that looks like a connection return { sendNotification: languageClient.sendNotification.bind(languageClient), @@ -86,15 +82,7 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi sendProgress: languageClient.sendProgress.bind(languageClient), onRequest: languageClient.onRequest.bind(languageClient), onNotification: languageClient.onNotification.bind(languageClient), - onProgress: languageClient.onProgress.bind(languageClient), - // tslint:disable-next-line: no-any - trace: (v: any) => (languageClient.trace = v), - onError: () => dummyDisposable, - onClose: () => dummyDisposable, - onDispose: () => dummyDisposable, - onUnhandledNotification: () => dummyDisposable, - dispose: this.manager.dispose.bind(this.manager), - listen: noop + onProgress: languageClient.onProgress.bind(languageClient) }; } } diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index aa6fd22ab61f..86ef1dfed4cc 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -21,6 +21,7 @@ import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/nod import * as vscodeprotocol from 'vscode-languageserver-protocol'; import { NugetPackage } from '../common/nuget/types'; import { IDisposable, IOutputChannel, LanguageServerDownloadChannels, Resource } from '../common/types'; +import { ILanguageServerConnection } from '../datascience/api/jupyterIntegration'; import { PythonEnvironment } from '../pythonEnvironments/info'; export const IExtensionActivationManager = Symbol('IExtensionActivationManager'); @@ -97,7 +98,7 @@ export interface ILanguageServer Partial, Partial, IDisposable { - readonly connection?: vscodeprotocol.ProtocolConnection; + readonly connection?: ILanguageServerConnection; readonly capabilities?: vscodeprotocol.ServerCapabilities; } diff --git a/src/client/datascience/api/jupyterIntegration.ts b/src/client/datascience/api/jupyterIntegration.ts index 723164126625..1287ec30f854 100644 --- a/src/client/datascience/api/jupyterIntegration.ts +++ b/src/client/datascience/api/jupyterIntegration.ts @@ -8,6 +8,20 @@ import { inject, injectable } from 'inversify'; import { dirname } from 'path'; import { CancellationToken, Event, Uri } from 'vscode'; +import { + Disposable, + GenericNotificationHandler, + GenericRequestHandler, + NotificationHandler, + NotificationHandler0, + NotificationType, + NotificationType0, + ProgressType, + RequestHandler, + RequestHandler0, + RequestType, + RequestType0 +} from 'vscode-jsonrpc'; import * as vscodeprotocol from 'vscode-languageserver-protocol'; import { ILanguageServerCache } from '../../activation/types'; import { InterpreterUri } from '../../common/installer/types'; @@ -21,11 +35,135 @@ import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; import { WindowsStoreInterpreter } from '../../pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -export type LanguageServerConnection = { - readonly connection: vscodeprotocol.ProtocolConnection; +/** + * This interface is a subset of the vscode-protocol connection interface. + * It's the minimum set of functions needed in order to talk to a language server. + */ +export interface ILanguageServerConnection { + /** + * Sends a request and returns a promise resolving to the result of the request. + * + * @param type The type of request to sent. + * @param token An optional cancellation token. + * @returns A promise resolving to the request's result. + */ + sendRequest(type: RequestType0, token?: CancellationToken): Promise; + /** + * Sends a request and returns a promise resolving to the result of the request. + * + * @param type The type of request to sent. + * @param params The request's parameter. + * @param token An optional cancellation token. + * @returns A promise resolving to the request's result. + */ + sendRequest(type: RequestType, params: P, token?: CancellationToken): Promise; + /** + * Sends a request and returns a promise resolving to the result of the request. + * + * @param method the request's method name. + * @param token An optional cancellation token. + * @returns A promise resolving to the request's result. + */ + sendRequest(method: string, token?: CancellationToken): Promise; + /** + * Sends a request and returns a promise resolving to the result of the request. + * + * @param method the request's method name. + * @param params The request's parameter. + * @param token An optional cancellation token. + * @returns A promise resolving to the request's result. + */ + // tslint:disable-next-line: no-any + sendRequest(method: string, param: any, token?: CancellationToken): Promise; + /** + * Installs a request handler. + * + * @param type The request type to install the handler for. + * @param handler The actual handler. + */ + onRequest(type: RequestType0, handler: RequestHandler0): void; + /** + * Installs a request handler. + * + * @param type The request type to install the handler for. + * @param handler The actual handler. + */ + onRequest(type: RequestType, handler: RequestHandler): void; + /** + * Installs a request handler. + * + * @param methods The method name to install the handler for. + * @param handler The actual handler. + */ + onRequest(method: string, handler: GenericRequestHandler): void; + /** + * Sends a notification. + * + * @param type the notification's type to send. + */ + sendNotification(type: NotificationType0): void; + /** + * Sends a notification. + * + * @param type the notification's type to send. + * @param params the notification's parameters. + */ + sendNotification(type: NotificationType, params?: P): void; + /** + * Sends a notification. + * + * @param method the notification's method name. + */ + sendNotification(method: string): void; + /** + * Sends a notification. + * + * @param method the notification's method name. + * @param params the notification's parameters. + */ + // tslint:disable-next-line: unified-signatures no-any + sendNotification(method: string, params: any): void; + /** + * Installs a notification handler. + * + * @param type The notification type to install the handler for. + * @param handler The actual handler. + */ + onNotification(type: NotificationType0, handler: NotificationHandler0): void; + /** + * Installs a notification handler. + * + * @param type The notification type to install the handler for. + * @param handler The actual handler. + */ + onNotification(type: NotificationType, handler: NotificationHandler

): void; + /** + * Installs a notification handler. + * + * @param methods The method name to install the handler for. + * @param handler The actual handler. + */ + onNotification(method: string, handler: GenericNotificationHandler): void; + /** + * Installs a progress handler for a given token. + * @param type the progress type + * @param token the token + * @param handler the handler + */ + onProgress

(type: ProgressType

, token: string | number, handler: NotificationHandler

): Disposable; + /** + * Sends progress. + * @param type the progress type + * @param token the token to use + * @param value the progress value + */ + sendProgress

(type: ProgressType

, token: string | number, value: P): void; +} + +export interface ILanguageServer extends Disposable { + readonly connection: ILanguageServerConnection; readonly capabilities: vscodeprotocol.ServerCapabilities; - dispose(): void; -}; +} type PythonApiForJupyterExtension = { /** @@ -67,10 +205,10 @@ type PythonApiForJupyterExtension = { */ getDebuggerPath(): Promise; /** - * Returns a ProtocolConnection that can be used for communicating with a language server process. + * Returns a ILanguageServer that can be used for communicating with a language server process. * @param resource file that determines which connection to return */ - getLanguageServerConnection(resource?: InterpreterUri): Promise; + getLanguageServer(resource?: InterpreterUri): Promise; }; export type JupyterExtensionApi = { @@ -111,7 +249,7 @@ export class JupyterExtensionIntegration { cancel?: CancellationToken ): Promise => this.installer.install(product, resource, cancel), getDebuggerPath: async () => dirname(getDebugpyPackagePath()), - getLanguageServerConnection: async (r) => { + getLanguageServer: async (r) => { const resource = isResource(r) ? r : undefined; const interpreter = !isResource(r) ? r : undefined; const client = await this.languageServerCache.get(resource, interpreter); diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts index 31d3b0e90163..14e8269db46c 100644 --- a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts +++ b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts @@ -15,7 +15,7 @@ import * as vscodeprotocol from 'vscode-languageserver-protocol'; import { Resource } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { JupyterExtensionIntegration, LanguageServerConnection } from '../../api/jupyterIntegration'; +import { ILanguageServer, ILanguageServerConnection, JupyterExtensionIntegration } from '../../api/jupyterIntegration'; /** * Class that wraps a language server for use by webview based notebooks @@ -23,10 +23,10 @@ import { JupyterExtensionIntegration, LanguageServerConnection } from '../../api export class NotebookLanguageServer implements Disposable { private code2ProtocolConverter = c2p.createConverter(); private protocol2CodeConverter = p2c.createConverter(); - private connection: vscodeprotocol.ProtocolConnection; + private connection: ILanguageServerConnection; private capabilities: vscodeprotocol.ServerCapabilities; private disposeConnection: () => void; - private constructor(ls: LanguageServerConnection) { + private constructor(ls: ILanguageServer) { this.connection = ls.connection; this.capabilities = ls.capabilities; this.disposeConnection = ls.dispose.bind(ls); @@ -41,7 +41,7 @@ export class NotebookLanguageServer implements Disposable { const deferred = createDeferred(); jupyterApiProvider.registerApi({ registerPythonApi: (api) => { - api.getLanguageServerConnection(interpreter ? interpreter : resource) + api.getLanguageServer(interpreter ? interpreter : resource) .then((c) => { if (c) { deferred.resolve(new NotebookLanguageServer(c)); From 5dd12fff855966ea2e5837001269f34f68ae5fae Mon Sep 17 00:00:00 2001 From: rchiodo Date: Mon, 21 Sep 2020 12:36:49 -0700 Subject: [PATCH 4/9] Fix up tests for intellisense --- src/client/activation/common/activatorBase.ts | 49 ------------ .../activation/jedi/multiplexingActivator.ts | 15 ++-- .../activation/refCountedLanguageServer.ts | 9 --- src/client/activation/types.ts | 11 +-- .../intellisense/notebookLanguageServer.ts | 18 ++++- .../intellisenseProvider.ts | 2 +- .../datascience/dataScienceIocContainer.ts | 5 ++ src/test/datascience/mockLanguageServer.ts | 27 +++++-- src/test/mocks/vsc/index.ts | 77 +++++++++++++++++++ src/test/vscode-mock.ts | 4 + 10 files changed, 133 insertions(+), 84 deletions(-) diff --git a/src/client/activation/common/activatorBase.ts b/src/client/activation/common/activatorBase.ts index df07c5eca200..7a3c972ef960 100644 --- a/src/client/activation/common/activatorBase.ts +++ b/src/client/activation/common/activatorBase.ts @@ -18,7 +18,6 @@ import { SignatureHelpContext, SymbolInformation, TextDocument, - TextDocumentContentChangeEvent, WorkspaceEdit } from 'vscode'; import * as vscodeLanguageClient from 'vscode-languageclient/node'; @@ -94,37 +93,6 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi } } - public handleOpen(document: TextDocument): void { - const languageClient = this.getLanguageClient(); - if (languageClient) { - languageClient.sendNotification( - vscodeLanguageClient.DidOpenTextDocumentNotification.type, - languageClient.code2ProtocolConverter.asOpenTextDocumentParams(document) - ); - } - } - - public handleChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]): void { - const languageClient = this.getLanguageClient(); - if (languageClient) { - // If the language client doesn't support incremental, just send the whole document - if (this.textDocumentSyncKind === vscodeLanguageClient.TextDocumentSyncKind.Full) { - languageClient.sendNotification( - vscodeLanguageClient.DidChangeTextDocumentNotification.type, - languageClient.code2ProtocolConverter.asChangeTextDocumentParams(document) - ); - } else { - languageClient.sendNotification( - vscodeLanguageClient.DidChangeTextDocumentNotification.type, - languageClient.code2ProtocolConverter.asChangeTextDocumentParams({ - document, - contentChanges: changes - }) - ); - } - } - } - public provideRenameEdits( document: TextDocument, position: Position, @@ -191,23 +159,6 @@ export abstract class LanguageServerActivatorBase implements ILanguageServerActi } } - private get textDocumentSyncKind(): vscodeLanguageClient.TextDocumentSyncKind { - const languageClient = this.getLanguageClient(); - if (languageClient?.initializeResult?.capabilities?.textDocumentSync) { - const syncOptions = languageClient.initializeResult.capabilities.textDocumentSync; - const syncKind = - syncOptions !== undefined && syncOptions.hasOwnProperty('change') - ? (syncOptions as vscodeLanguageClient.TextDocumentSyncOptions).change - : syncOptions; - if (syncKind !== undefined) { - return syncKind as vscodeLanguageClient.TextDocumentSyncKind; - } - } - - // Default is full if not provided - return vscodeLanguageClient.TextDocumentSyncKind.Full; - } - private async handleProvideRenameEdits( document: TextDocument, position: Position, diff --git a/src/client/activation/jedi/multiplexingActivator.ts b/src/client/activation/jedi/multiplexingActivator.ts index 454ea7bbf898..70f5d664fc62 100644 --- a/src/client/activation/jedi/multiplexingActivator.ts +++ b/src/client/activation/jedi/multiplexingActivator.ts @@ -9,8 +9,7 @@ import { Position, ReferenceContext, SignatureHelpContext, - TextDocument, - TextDocumentContentChangeEvent + TextDocument } from 'vscode'; // tslint:disable-next-line: import-name import { IWorkspaceService } from '../../common/application/types'; @@ -73,15 +72,15 @@ export class MultiplexingJediLanguageServerActivator implements ILanguageServerA return this.onDidChangeCodeLensesEmitter.event; } - public handleChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { - if (this.realLanguageServer && this.realLanguageServer.handleChanges) { - this.realLanguageServer.handleChanges(document, changes); + public get connection() { + if (this.realLanguageServer) { + return this.realLanguageServer.connection; } } - public handleOpen(document: TextDocument) { - if (this.realLanguageServer && this.realLanguageServer.handleOpen) { - this.realLanguageServer.handleOpen(document); + public get capabilities() { + if (this.realLanguageServer) { + return this.realLanguageServer.capabilities; } } diff --git a/src/client/activation/refCountedLanguageServer.ts b/src/client/activation/refCountedLanguageServer.ts index e19390530fd9..52e76bf6a4de 100644 --- a/src/client/activation/refCountedLanguageServer.ts +++ b/src/client/activation/refCountedLanguageServer.ts @@ -17,7 +17,6 @@ import { SignatureHelpContext, SymbolInformation, TextDocument, - TextDocumentContentChangeEvent, WorkspaceEdit } from 'vscode'; @@ -73,14 +72,6 @@ export class RefCountedLanguageServer implements ILanguageServerActivator { return this.impl.capabilities; } - public handleChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { - this.impl.handleChanges ? this.impl.handleChanges(document, changes) : noop(); - } - - public handleOpen(document: TextDocument) { - this.impl.handleOpen ? this.impl.handleOpen(document) : noop(); - } - public provideRenameEdits( document: TextDocument, position: Position, diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index 86ef1dfed4cc..4ba453f7ac04 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -13,9 +13,7 @@ import { HoverProvider, ReferenceProvider, RenameProvider, - SignatureHelpProvider, - TextDocument, - TextDocumentContentChangeEvent + SignatureHelpProvider } from 'vscode'; import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; import * as vscodeprotocol from 'vscode-languageserver-protocol'; @@ -75,12 +73,6 @@ export enum LanguageServerType { export const DotNetLanguageServerFolder = 'languageServer'; export const NodeLanguageServerFolder = 'nodeLanguageServer'; -// tslint:disable-next-line: interface-name -export interface DocumentHandler { - handleOpen(document: TextDocument): void; - handleChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]): void; -} - // tslint:disable-next-line: interface-name export interface LanguageServerCommandHandler { clearAnalysisCache(): void; @@ -95,7 +87,6 @@ export interface ILanguageServer CodeLensProvider, DocumentSymbolProvider, SignatureHelpProvider, - Partial, Partial, IDisposable { readonly connection?: ILanguageServerConnection; diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts index 14e8269db46c..1c1e27d1b061 100644 --- a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts +++ b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts @@ -68,7 +68,7 @@ export class NotebookLanguageServer implements Disposable { public sendChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { // If the language client doesn't support incremental, just send the whole document - if (this.capabilities.textDocumentSync === vscodeLanguageClient.TextDocumentSyncKind.Full) { + if (this.textDocumentSyncKind === vscodeLanguageClient.TextDocumentSyncKind.Full) { this.connection.sendNotification( vscodeLanguageClient.DidChangeTextDocumentNotification.type, this.code2ProtocolConverter.asChangeTextDocumentParams(document) @@ -134,4 +134,20 @@ export class NotebookLanguageServer implements Disposable { return this.protocol2CodeConverter.asCompletionItem(result); } } + + private get textDocumentSyncKind(): vscodeLanguageClient.TextDocumentSyncKind { + if (this.capabilities.textDocumentSync) { + const syncOptions = this.capabilities.textDocumentSync; + const syncKind = + syncOptions !== undefined && syncOptions.hasOwnProperty('change') + ? (syncOptions as vscodeLanguageClient.TextDocumentSyncOptions).change + : syncOptions; + if (syncKind !== undefined) { + return syncKind as vscodeLanguageClient.TextDocumentSyncKind; + } + } + + // Default is full if not provided + return vscodeLanguageClient.TextDocumentSyncKind.Full; + } } diff --git a/src/datascience-ui/interactive-common/intellisenseProvider.ts b/src/datascience-ui/interactive-common/intellisenseProvider.ts index 035340ca480f..10636423b75c 100644 --- a/src/datascience-ui/interactive-common/intellisenseProvider.ts +++ b/src/datascience-ui/interactive-common/intellisenseProvider.ts @@ -119,7 +119,7 @@ export class IntellisenseProvider // Our code strips out _documentPosition and possibly other items that are too large to send // so instead of returning the new resolve completion item, just return the old item with documentation added in // which is what we are resolving the item to get - return Promise.resolve({ ...item, documentation: newItem.documentation }); + return Promise.resolve({ ...item, documentation: newItem?.documentation }); } else { return Promise.resolve(item); } diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index 326a6824c8ff..cf84425ee398 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -186,6 +186,7 @@ import { Architecture } from '../../client/common/utils/platform'; import { EnvironmentVariablesService } from '../../client/common/variables/environment'; import { EnvironmentVariablesProvider } from '../../client/common/variables/environmentVariablesProvider'; import { IEnvironmentVariablesProvider, IEnvironmentVariablesService } from '../../client/common/variables/types'; +import { JupyterExtensionIntegration } from '../../client/datascience/api/jupyterIntegration'; import { CodeCssGenerator } from '../../client/datascience/codeCssGenerator'; import { JupyterCommandLineSelectorCommand } from '../../client/datascience/commands/commandLineSelector'; import { CommandRegistry } from '../../client/datascience/commands/commandRegistry'; @@ -760,6 +761,10 @@ export class DataScienceIocContainer extends UnitTestIocContainer { PipEnvActivationCommandProvider, TerminalActivationProviders.pipenv ); + this.serviceManager.addSingleton( + JupyterExtensionIntegration, + JupyterExtensionIntegration + ); this.serviceManager.addSingleton(ITerminalManager, TerminalManager); this.serviceManager.addSingleton(ILanguageServerProxy, MockLanguageServerProxy); this.serviceManager.addSingleton( diff --git a/src/test/datascience/mockLanguageServer.ts b/src/test/datascience/mockLanguageServer.ts index 9f7f29086519..d432439a86ce 100644 --- a/src/test/datascience/mockLanguageServer.ts +++ b/src/test/datascience/mockLanguageServer.ts @@ -21,6 +21,7 @@ import { TextDocumentContentChangeEvent, WorkspaceEdit } from 'vscode'; +import * as vscodeLanguageClient from 'vscode-languageclient/node'; import { ILanguageServer } from '../../client/activation/types'; import { createDeferred, Deferred } from '../../client/common/utils/async'; @@ -45,14 +46,20 @@ export class MockLanguageServer implements ILanguageServer { return this.versionId; } - public handleChanges(document: TextDocument, changes: TextDocumentContentChangeEvent[]) { - this.versionId = document.version; - this.applyChanges(changes); - this.resolveNotificationPromise(); + public get connection() { + // Return an object that looks like a connection + return { + sendNotification: this.sendNotification.bind(this) as any, + sendRequest: noop as any, + sendProgress: noop as any, + onRequest: noop as any, + onNotification: noop as any, + onProgress: noop as any + }; } - public handleOpen(_document: TextDocument) { - noop(); + public get capabilities() { + return {}; } public provideRenameEdits( @@ -130,6 +137,14 @@ export class MockLanguageServer implements ILanguageServer { noop(); } + private sendNotification(method: string, params: any): void { + if (method === vscodeLanguageClient.DidChangeTextDocumentNotification.type.method) { + const changes = params.contentChanges; + this.applyChanges(changes); + this.resolveNotificationPromise(); + } + } + private applyChanges(changes: TextDocumentContentChangeEvent[]) { changes.forEach((c) => { const before = this.contents.substr(0, c.rangeOffset); diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts index 9f6fb3c2ab76..c561b9fcb54e 100644 --- a/src/test/mocks/vsc/index.ts +++ b/src/test/mocks/vsc/index.ts @@ -13,6 +13,11 @@ import * as vscode from 'vscode'; export * from './extHostedTypes'; export * from './uri'; +const escapeCodiconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi; +export function escapeCodicons(text: string): string { + return text.replace(escapeCodiconsRegex, (match, escaped) => (escaped ? match : `\\${match}`)); +} + export namespace vscMock { export enum ExtensionKind { /** @@ -165,6 +170,78 @@ export namespace vscMock { Outdent = 3 } + export enum CompletionTriggerKind { + Invoke = 0, + TriggerCharacter = 1, + TriggerForIncompleteCompletions = 2 + } + + export class MarkdownString { + public value: string; + public isTrusted?: boolean; + public readonly supportThemeIcons?: boolean; + + constructor(value?: string, supportThemeIcons: boolean = false) { + this.value = value ?? ''; + this.supportThemeIcons = supportThemeIcons; + } + + public static isMarkdownString(thing: any): thing is vscode.MarkdownString { + if (thing instanceof MarkdownString) { + return true; + } + return ( + thing && thing.appendCodeblock && thing.appendMarkdown && thing.appendText && thing.value !== undefined + ); + } + + public appendText(value: string): MarkdownString { + // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash + this.value += (this.supportThemeIcons ? escapeCodicons(value) : value) + .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&') + .replace(/\n/, '\n\n'); + + return this; + } + + public appendMarkdown(value: string): MarkdownString { + this.value += value; + + return this; + } + + public appendCodeblock(code: string, language: string = ''): MarkdownString { + this.value += '\n```'; + this.value += language; + this.value += '\n'; + this.value += code; + this.value += '\n```\n'; + return this; + } + } + + export class Hover { + public contents: vscode.MarkdownString[] | vscode.MarkedString[]; + public range: vscode.Range | undefined; + + constructor( + contents: vscode.MarkdownString | vscode.MarkedString | vscode.MarkdownString[] | vscode.MarkedString[], + range?: vscode.Range + ) { + if (!contents) { + throw new Error('Illegal argument, contents must be defined'); + } + if (Array.isArray(contents)) { + this.contents = contents; + } else if (MarkdownString.isMarkdownString(contents)) { + this.contents = [contents]; + } else { + this.contents = [contents]; + } + this.range = range; + } + } + export class CodeActionKind { public static readonly Empty: CodeActionKind = new CodeActionKind('empty'); public static readonly QuickFix: CodeActionKind = new CodeActionKind('quick.fix'); diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 2ee192f112e1..f3380316faa8 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -64,6 +64,8 @@ export function initialize() { }; } +mockedVSCode.MarkdownString = vscodeMocks.vscMock.MarkdownString; +mockedVSCode.Hover = vscodeMocks.vscMock.Hover; mockedVSCode.Disposable = vscodeMocks.vscMock.Disposable as any; mockedVSCode.ExtensionKind = vscodeMocks.vscMock.ExtensionKind; mockedVSCode.CodeAction = vscodeMocks.vscMock.CodeAction; @@ -96,6 +98,8 @@ mockedVSCode.TextEditorRevealType = vscodeMocks.vscMockExtHostedTypes.TextEditor mockedVSCode.TreeItem = vscodeMocks.vscMockExtHostedTypes.TreeItem; mockedVSCode.TreeItemCollapsibleState = vscodeMocks.vscMockExtHostedTypes.TreeItemCollapsibleState; mockedVSCode.CodeActionKind = vscodeMocks.vscMock.CodeActionKind; +mockedVSCode.CompletionItemKind = vscodeMocks.vscMock.CompletionItemKind; +mockedVSCode.CompletionTriggerKind = vscodeMocks.vscMock.CompletionTriggerKind; mockedVSCode.DebugAdapterExecutable = vscodeMocks.vscMock.DebugAdapterExecutable; mockedVSCode.DebugAdapterServer = vscodeMocks.vscMock.DebugAdapterServer; mockedVSCode.QuickInputButtons = vscodeMocks.vscMockExtHostedTypes.QuickInputButtons; From 92e5b9431e57d09fde7ed38697c88d5f0f2cfe74 Mon Sep 17 00:00:00 2001 From: rchiodo Date: Mon, 21 Sep 2020 15:44:38 -0700 Subject: [PATCH 5/9] Fix unit tests --- src/test/datascience/mockLanguageServer.ts | 26 +++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/test/datascience/mockLanguageServer.ts b/src/test/datascience/mockLanguageServer.ts index d432439a86ce..1557eba0d0e4 100644 --- a/src/test/datascience/mockLanguageServer.ts +++ b/src/test/datascience/mockLanguageServer.ts @@ -59,7 +59,9 @@ export class MockLanguageServer implements ILanguageServer { } public get capabilities() { - return {}; + return { + textDocumentSync: 2 // This is increment value. Means we support changes + } as any; } public provideRenameEdits( @@ -137,8 +139,10 @@ export class MockLanguageServer implements ILanguageServer { noop(); } - private sendNotification(method: string, params: any): void { - if (method === vscodeLanguageClient.DidChangeTextDocumentNotification.type.method) { + private sendNotification(method: any, params: any): void { + if (method === vscodeLanguageClient.DidChangeTextDocumentNotification.type) { + const doc = params.textDocument; + this.versionId = doc.version; const changes = params.contentChanges; this.applyChanges(changes); this.resolveNotificationPromise(); @@ -147,13 +151,25 @@ export class MockLanguageServer implements ILanguageServer { private applyChanges(changes: TextDocumentContentChangeEvent[]) { changes.forEach((c) => { - const before = this.contents.substr(0, c.rangeOffset); - const after = this.contents.substr(c.rangeOffset + c.rangeLength); + const offset = this.computeOffset(c); + const before = this.contents.substr(0, offset); + const after = this.contents.substr(offset + c.rangeLength); this.contents = `${before}${c.text}${after}`; }); this.versionId = this.versionId + 1; } + private computeOffset(c: TextDocumentContentChangeEvent): number { + // range offset is no longer available. Have to compute it using the contents + const lines = this.contents.splitLines({ trim: false, removeEmptyEntries: false }); + let offset = 0; + for (let i = 0; i < c.range.start.line; i += 1) { + offset += lines[i].length + 1; // + 1 for the linefeed + } + offset += c.range.start.character; + return offset; + } + private resolveNotificationPromise() { if (this.notificationPromise) { this.notificationPromise.resolve(); From ec075715bfb6de3d0ed135d7b363e27e297ba9c8 Mon Sep 17 00:00:00 2001 From: rchiodo Date: Mon, 21 Sep 2020 15:53:51 -0700 Subject: [PATCH 6/9] Put back custom editor service --- .../common/application/customEditorService.ts | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/client/common/application/customEditorService.ts b/src/client/common/application/customEditorService.ts index 34e8f3dc5fac..4163c9af509d 100644 --- a/src/client/common/application/customEditorService.ts +++ b/src/client/common/application/customEditorService.ts @@ -6,14 +6,20 @@ import * as vscode from 'vscode'; import { UseCustomEditorApi } from '../constants'; import { traceError } from '../logger'; +import { IExtensionContext } from '../types'; import { noop } from '../utils/misc'; -import { CustomEditorProvider, ICommandManager, ICustomEditorService } from './types'; +import { CustomEditorProvider, ICommandManager, ICustomEditorService, IWorkspaceService } from './types'; + +const EditorAssociationUpdatedKey = 'EditorAssociationUpdatedToUseCustomEditor'; +const ViewType = 'ms-python.python.notebook.ipynb'; @injectable() export class CustomEditorService implements ICustomEditorService { constructor( @inject(ICommandManager) private commandManager: ICommandManager, - @inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean + @inject(UseCustomEditorApi) private readonly useCustomEditorApi: boolean, + @inject(IWorkspaceService) private readonly workspace: IWorkspaceService, + @inject(IExtensionContext) private readonly extensionContext: IExtensionContext ) { this.enableCustomEditors().catch((e) => traceError(`Error setting up custom editors: `, e)); } @@ -42,6 +48,42 @@ export class CustomEditorService implements ICustomEditorService { // tslint:disable-next-line: no-any private async enableCustomEditors() { - return; + // This code is temporary. + const settings = this.workspace.getConfiguration('workbench', undefined); + const editorAssociations = settings.get('editorAssociations') as { + viewType: string; + filenamePattern: string; + }[]; + + // Update the settings. + if ( + this.useCustomEditorApi && + (!Array.isArray(editorAssociations) || + editorAssociations.length === 0 || + !editorAssociations.find((item) => item.viewType === ViewType)) + ) { + editorAssociations.push({ + viewType: ViewType, + filenamePattern: '*.ipynb' + }); + await Promise.all([ + this.extensionContext.globalState.update(EditorAssociationUpdatedKey, true), + settings.update('editorAssociations', editorAssociations, vscode.ConfigurationTarget.Global) + ]); + } + + // Revert the settings. + if ( + !this.useCustomEditorApi && + this.extensionContext.globalState.get(EditorAssociationUpdatedKey, false) && + Array.isArray(editorAssociations) && + editorAssociations.find((item) => item.viewType === ViewType) + ) { + const updatedSettings = editorAssociations.filter((item) => item.viewType !== ViewType); + await Promise.all([ + this.extensionContext.globalState.update(EditorAssociationUpdatedKey, false), + settings.update('editorAssociations', updatedSettings, vscode.ConfigurationTarget.Global) + ]); + } } } From 93c21cf8e61efa3cb9362bd7dd279493bbc0770e Mon Sep 17 00:00:00 2001 From: rchiodo Date: Mon, 21 Sep 2020 16:06:29 -0700 Subject: [PATCH 7/9] Remove unnecessary changes for service registry --- src/client/datascience/serviceRegistry.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/client/datascience/serviceRegistry.ts b/src/client/datascience/serviceRegistry.ts index 2d9a6be005d1..05b44495fa04 100644 --- a/src/client/datascience/serviceRegistry.ts +++ b/src/client/datascience/serviceRegistry.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -//import * as vscode from 'vscode'; +import * as vscode from 'vscode'; import { IExtensionSingleActivationService } from '../activation/types'; import { UseCustomEditorApi, UseVSCodeNotebookEditorApi } from '../common/constants'; import { NotebookEditorSupport } from '../common/experiments/groups'; @@ -189,8 +189,8 @@ import { // tslint:disable-next-line: max-func-body-length export function registerTypes(serviceManager: IServiceManager) { const experiments = serviceManager.get(IExperimentsManager); - //const inCustomEditorApiExperiment = experiments.inExperiment(NotebookEditorSupport.customEditorExperiment); - const usingCustomEditor = true; //inCustomEditorApiExperiment && !vscode.env.appName.includes('Insider'); // Don't use app manager as it's not available yet. + const inCustomEditorApiExperiment = experiments.inExperiment(NotebookEditorSupport.customEditorExperiment); + const usingCustomEditor = inCustomEditorApiExperiment && !vscode.env.appName.includes('Insider'); // Don't use app manager as it's not available yet. const useVSCodeNotebookAPI = experiments.inExperiment(NotebookEditorSupport.nativeNotebookExperiment) && !usingCustomEditor; serviceManager.addSingletonInstance(UseCustomEditorApi, usingCustomEditor); serviceManager.addSingletonInstance(UseVSCodeNotebookEditorApi, useVSCodeNotebookAPI); From 1633264b9c38b6be42c93fb765035bf2e5ce39e3 Mon Sep 17 00:00:00 2001 From: rchiodo Date: Tue, 22 Sep 2020 09:05:48 -0700 Subject: [PATCH 8/9] Code review feedback --- src/client/activation/types.ts | 4 +- .../datascience/api/jupyterIntegration.ts | 144 +----------------- .../intellisense/notebookLanguageServer.ts | 4 +- 3 files changed, 11 insertions(+), 141 deletions(-) diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index 4ba453f7ac04..370853b390cc 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -16,7 +16,7 @@ import { SignatureHelpProvider } from 'vscode'; import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/node'; -import * as vscodeprotocol from 'vscode-languageserver-protocol'; +import * as lsp from 'vscode-languageserver-protocol'; import { NugetPackage } from '../common/nuget/types'; import { IDisposable, IOutputChannel, LanguageServerDownloadChannels, Resource } from '../common/types'; import { ILanguageServerConnection } from '../datascience/api/jupyterIntegration'; @@ -90,7 +90,7 @@ export interface ILanguageServer Partial, IDisposable { readonly connection?: ILanguageServerConnection; - readonly capabilities?: vscodeprotocol.ServerCapabilities; + readonly capabilities?: lsp.ServerCapabilities; } export const ILanguageServerActivator = Symbol('ILanguageServerActivator'); diff --git a/src/client/datascience/api/jupyterIntegration.ts b/src/client/datascience/api/jupyterIntegration.ts index 1287ec30f854..af7784838785 100644 --- a/src/client/datascience/api/jupyterIntegration.ts +++ b/src/client/datascience/api/jupyterIntegration.ts @@ -7,22 +7,8 @@ import { inject, injectable } from 'inversify'; import { dirname } from 'path'; -import { CancellationToken, Event, Uri } from 'vscode'; -import { - Disposable, - GenericNotificationHandler, - GenericRequestHandler, - NotificationHandler, - NotificationHandler0, - NotificationType, - NotificationType0, - ProgressType, - RequestHandler, - RequestHandler0, - RequestType, - RequestType0 -} from 'vscode-jsonrpc'; -import * as vscodeprotocol from 'vscode-languageserver-protocol'; +import { CancellationToken, Disposable, Event, Uri } from 'vscode'; +import * as lsp from 'vscode-languageserver-protocol'; import { ILanguageServerCache } from '../../activation/types'; import { InterpreterUri } from '../../common/installer/types'; import { IExtensions, IInstaller, InstallerResponse, Product, Resource } from '../../common/types'; @@ -39,130 +25,14 @@ import { PythonEnvironment } from '../../pythonEnvironments/info'; * This interface is a subset of the vscode-protocol connection interface. * It's the minimum set of functions needed in order to talk to a language server. */ -export interface ILanguageServerConnection { - /** - * Sends a request and returns a promise resolving to the result of the request. - * - * @param type The type of request to sent. - * @param token An optional cancellation token. - * @returns A promise resolving to the request's result. - */ - sendRequest(type: RequestType0, token?: CancellationToken): Promise; - /** - * Sends a request and returns a promise resolving to the result of the request. - * - * @param type The type of request to sent. - * @param params The request's parameter. - * @param token An optional cancellation token. - * @returns A promise resolving to the request's result. - */ - sendRequest(type: RequestType, params: P, token?: CancellationToken): Promise; - /** - * Sends a request and returns a promise resolving to the result of the request. - * - * @param method the request's method name. - * @param token An optional cancellation token. - * @returns A promise resolving to the request's result. - */ - sendRequest(method: string, token?: CancellationToken): Promise; - /** - * Sends a request and returns a promise resolving to the result of the request. - * - * @param method the request's method name. - * @param params The request's parameter. - * @param token An optional cancellation token. - * @returns A promise resolving to the request's result. - */ - // tslint:disable-next-line: no-any - sendRequest(method: string, param: any, token?: CancellationToken): Promise; - /** - * Installs a request handler. - * - * @param type The request type to install the handler for. - * @param handler The actual handler. - */ - onRequest(type: RequestType0, handler: RequestHandler0): void; - /** - * Installs a request handler. - * - * @param type The request type to install the handler for. - * @param handler The actual handler. - */ - onRequest(type: RequestType, handler: RequestHandler): void; - /** - * Installs a request handler. - * - * @param methods The method name to install the handler for. - * @param handler The actual handler. - */ - onRequest(method: string, handler: GenericRequestHandler): void; - /** - * Sends a notification. - * - * @param type the notification's type to send. - */ - sendNotification(type: NotificationType0): void; - /** - * Sends a notification. - * - * @param type the notification's type to send. - * @param params the notification's parameters. - */ - sendNotification(type: NotificationType, params?: P): void; - /** - * Sends a notification. - * - * @param method the notification's method name. - */ - sendNotification(method: string): void; - /** - * Sends a notification. - * - * @param method the notification's method name. - * @param params the notification's parameters. - */ - // tslint:disable-next-line: unified-signatures no-any - sendNotification(method: string, params: any): void; - /** - * Installs a notification handler. - * - * @param type The notification type to install the handler for. - * @param handler The actual handler. - */ - onNotification(type: NotificationType0, handler: NotificationHandler0): void; - /** - * Installs a notification handler. - * - * @param type The notification type to install the handler for. - * @param handler The actual handler. - */ - onNotification(type: NotificationType, handler: NotificationHandler

): void; - /** - * Installs a notification handler. - * - * @param methods The method name to install the handler for. - * @param handler The actual handler. - */ - onNotification(method: string, handler: GenericNotificationHandler): void; - /** - * Installs a progress handler for a given token. - * @param type the progress type - * @param token the token - * @param handler the handler - */ - onProgress

(type: ProgressType

, token: string | number, handler: NotificationHandler

): Disposable; - /** - * Sends progress. - * @param type the progress type - * @param token the token to use - * @param value the progress value - */ - sendProgress

(type: ProgressType

, token: string | number, value: P): void; -} +export type ILanguageServerConnection = Pick< + lsp.ProtocolConnection, + 'sendRequest' | 'sendNotification' | 'onProgress' | 'sendProgress' | 'onNotification' | 'onRequest' +>; export interface ILanguageServer extends Disposable { readonly connection: ILanguageServerConnection; - readonly capabilities: vscodeprotocol.ServerCapabilities; + readonly capabilities: lsp.ServerCapabilities; } type PythonApiForJupyterExtension = { diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts index 1c1e27d1b061..478686903381 100644 --- a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts +++ b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts @@ -11,7 +11,7 @@ import { import * as c2p from 'vscode-languageclient/lib/common/codeConverter'; import * as p2c from 'vscode-languageclient/lib/common/protocolConverter'; import * as vscodeLanguageClient from 'vscode-languageclient/node'; -import * as vscodeprotocol from 'vscode-languageserver-protocol'; +import * as lsp from 'vscode-languageserver-protocol'; import { Resource } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; @@ -24,7 +24,7 @@ export class NotebookLanguageServer implements Disposable { private code2ProtocolConverter = c2p.createConverter(); private protocol2CodeConverter = p2c.createConverter(); private connection: ILanguageServerConnection; - private capabilities: vscodeprotocol.ServerCapabilities; + private capabilities: lsp.ServerCapabilities; private disposeConnection: () => void; private constructor(ls: ILanguageServer) { this.connection = ls.connection; From 3103ee9e353f6df81cd4b4c46c5d0e2b856273ab Mon Sep 17 00:00:00 2001 From: rchiodo Date: Tue, 22 Sep 2020 11:54:47 -0700 Subject: [PATCH 9/9] Move connection type to types file --- src/client/activation/types.ts | 10 +++++++++- src/client/datascience/api/jupyterIntegration.ts | 11 +---------- .../intellisense/notebookLanguageServer.ts | 3 ++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index 370853b390cc..68316191360d 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -19,7 +19,6 @@ import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient/nod import * as lsp from 'vscode-languageserver-protocol'; import { NugetPackage } from '../common/nuget/types'; import { IDisposable, IOutputChannel, LanguageServerDownloadChannels, Resource } from '../common/types'; -import { ILanguageServerConnection } from '../datascience/api/jupyterIntegration'; import { PythonEnvironment } from '../pythonEnvironments/info'; export const IExtensionActivationManager = Symbol('IExtensionActivationManager'); @@ -78,6 +77,15 @@ export interface LanguageServerCommandHandler { clearAnalysisCache(): void; } +/** + * This interface is a subset of the vscode-protocol connection interface. + * It's the minimum set of functions needed in order to talk to a language server. + */ +export type ILanguageServerConnection = Pick< + lsp.ProtocolConnection, + 'sendRequest' | 'sendNotification' | 'onProgress' | 'sendProgress' | 'onNotification' | 'onRequest' +>; + export interface ILanguageServer extends RenameProvider, DefinitionProvider, diff --git a/src/client/datascience/api/jupyterIntegration.ts b/src/client/datascience/api/jupyterIntegration.ts index af7784838785..98c154fe1706 100644 --- a/src/client/datascience/api/jupyterIntegration.ts +++ b/src/client/datascience/api/jupyterIntegration.ts @@ -9,7 +9,7 @@ import { inject, injectable } from 'inversify'; import { dirname } from 'path'; import { CancellationToken, Disposable, Event, Uri } from 'vscode'; import * as lsp from 'vscode-languageserver-protocol'; -import { ILanguageServerCache } from '../../activation/types'; +import { ILanguageServerCache, ILanguageServerConnection } from '../../activation/types'; import { InterpreterUri } from '../../common/installer/types'; import { IExtensions, IInstaller, InstallerResponse, Product, Resource } from '../../common/types'; import { isResource } from '../../common/utils/misc'; @@ -21,15 +21,6 @@ import { IWindowsStoreInterpreter } from '../../interpreter/locators/types'; import { WindowsStoreInterpreter } from '../../pythonEnvironments/discovery/locators/services/windowsStoreInterpreter'; import { PythonEnvironment } from '../../pythonEnvironments/info'; -/** - * This interface is a subset of the vscode-protocol connection interface. - * It's the minimum set of functions needed in order to talk to a language server. - */ -export type ILanguageServerConnection = Pick< - lsp.ProtocolConnection, - 'sendRequest' | 'sendNotification' | 'onProgress' | 'sendProgress' | 'onNotification' | 'onRequest' ->; - export interface ILanguageServer extends Disposable { readonly connection: ILanguageServerConnection; readonly capabilities: lsp.ServerCapabilities; diff --git a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts index 478686903381..33d635b17e21 100644 --- a/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts +++ b/src/client/datascience/interactive-common/intellisense/notebookLanguageServer.ts @@ -12,10 +12,11 @@ import * as c2p from 'vscode-languageclient/lib/common/codeConverter'; import * as p2c from 'vscode-languageclient/lib/common/protocolConverter'; import * as vscodeLanguageClient from 'vscode-languageclient/node'; import * as lsp from 'vscode-languageserver-protocol'; +import { ILanguageServerConnection } from '../../../activation/types'; import { Resource } from '../../../common/types'; import { createDeferred } from '../../../common/utils/async'; import { PythonEnvironment } from '../../../pythonEnvironments/info'; -import { ILanguageServer, ILanguageServerConnection, JupyterExtensionIntegration } from '../../api/jupyterIntegration'; +import { ILanguageServer, JupyterExtensionIntegration } from '../../api/jupyterIntegration'; /** * Class that wraps a language server for use by webview based notebooks