From a3a5ac5bc38898425fcfb88fa88e65d1bb6a17ed Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 16 Jan 2020 08:37:28 -0500 Subject: [PATCH 01/63] (wip) only create one connection manager per app, one socket per language --- .../src/adapters/codemirror/feature.ts | 2 +- .../codemirror/features/highlights.ts | 13 +- .../src/adapters/codemirror/features/hover.ts | 5 +- .../adapters/codemirror/features/jump_to.ts | 11 +- .../adapters/codemirror/features/rename.ts | 2 +- .../adapters/codemirror/features/signature.ts | 5 +- .../src/adapters/codemirror/testutils.ts | 7 +- .../jupyterlab/components/completion.ts | 1 + .../src/adapters/jupyterlab/file_editor.ts | 7 +- .../src/adapters/jupyterlab/jl_adapter.ts | 14 +- .../src/adapters/jupyterlab/notebook.ts | 7 +- packages/jupyterlab-lsp/src/connection.ts | 32 +++-- .../jupyterlab-lsp/src/connection_manager.ts | 67 +++++++--- packages/jupyterlab-lsp/src/index.ts | 8 +- .../jupyterlab-lsp/src/virtual/document.ts | 31 +++++ packages/lsp-ws-connection/src/types.ts | 32 +++-- .../lsp-ws-connection/src/ws-connection.ts | 122 ++++++++++-------- 17 files changed, 237 insertions(+), 129 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index a301b8057..b5c0ed375 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -278,7 +278,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { protected async apply_edit( workspaceEdit: lsProtocol.WorkspaceEdit ): Promise { - let current_uri = this.connection.getDocumentUri(); + let current_uri = this.virtual_document.document_info.uri; // Specs: documentChanges are preferred over changes let changes = workspaceEdit.documentChanges ? workspaceEdit.documentChanges.map( diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 629c283e2..4cb7596c8 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -12,15 +12,15 @@ export class Highlights extends CodeMirrorLSPFeature { static commands: Array = [ { id: 'highlight-references', - execute: ({ connection, virtual_position }) => - connection.getReferences(virtual_position), + execute: ({ connection, virtual_position, document }) => + connection.getReferences(virtual_position, document.document_info), is_enabled: ({ connection }) => connection.isReferencesSupported(), label: 'Highlight references' }, { id: 'highlight-type-definition', - execute: ({ connection, virtual_position }) => - connection.getTypeDefinition(virtual_position), + execute: ({ connection, virtual_position, document }) => + connection.getTypeDefinition(virtual_position, document.document_info), is_enabled: ({ connection }) => connection.isTypeDefinitionSupported(), label: 'Highlight type definition' } @@ -88,7 +88,10 @@ export class Highlights extends CodeMirrorLSPFeature { let virtual_position = this.virtual_editor.root_position_to_virtual_position( root_position ); - this.connection.getDocumentHighlights(virtual_position); + this.connection.getDocumentHighlights( + virtual_position, + this.virtual_document.document_info + ); } catch (e) { console.warn('Could not get highlights:', e); } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index e02bccfd1..2a2d7cd92 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -44,7 +44,10 @@ export class Hover extends CodeMirrorLSPFeature { this.connection_handlers.set('hover', this.handleHover.bind(this)); // TODO: make the debounce rate configurable this.debounced_get_hover = new Debouncer(() => { - this.connection.getHoverTooltip(this.virtual_position); + this.connection.getHoverTooltip( + this.virtual_position, + this.virtual_document.document_info + ); }, 50); super.register(); } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts index d774d791f..f0c1be381 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts @@ -3,14 +3,15 @@ import * as lsProtocol from 'vscode-languageserver-protocol'; import { PositionConverter } from '../../../converter'; import { IVirtualPosition } from '../../../positioning'; import { uri_to_contents_path, uris_equal } from '../../../utils'; +import { IDocumentInfo } from 'lsp-ws-connection/src'; export class JumpToDefinition extends CodeMirrorLSPFeature { name = 'JumpToDefinition'; static commands: Array = [ { id: 'jump-to-definition', - execute: ({ connection, virtual_position }) => - connection.getDefinition(virtual_position), + execute: ({ connection, virtual_position, document }) => + connection.getDefinition(virtual_position, document.document_info), is_enabled: ({ connection }) => connection.isDefinitionSupported(), label: 'Jump to definition' } @@ -26,9 +27,9 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { } async handle_jump( - location_or_locations: lsProtocol.Location | lsProtocol.Location[] + location_or_locations: lsProtocol.Location | lsProtocol.Location[], + document_info: IDocumentInfo ) { - let connection = this.connection; // some language servers appear to return a single object const locations = Array.isArray(location_or_locations) ? location_or_locations @@ -46,7 +47,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { let location = locations[0]; let uri: string = decodeURI(location.uri); - let current_uri = connection.getDocumentUri(); + let current_uri = document_info.uri; let virtual_position = PositionConverter.lsp_to_cm( location.range.start diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts index b4bc53532..33b063de5 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts @@ -45,7 +45,7 @@ export class Rename extends CodeMirrorLSPFeature { }) .then(value => { connection - .rename(virtual_position, value.value) + .rename(virtual_position, document.document_info, value.value) .catch(handle_failure); }) .catch(handle_failure); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts index 173da15c2..9093c0a00 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts @@ -128,7 +128,10 @@ export class Signature extends CodeMirrorLSPFeature { 'Signature will be requested for', virtual_position ); - this.connection.getSignatureHelp(virtual_position); + this.connection.getSignatureHelp( + virtual_position, + this.virtual_document.document_info + ); } } } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index ad25ade42..25984ecf0 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -85,12 +85,7 @@ export abstract class FeatureTestEnvironment return new LSPConnection({ languageId: this.language(), serverUri: '', - documentUri: 'file://' + this.path(), - rootUri: '/', - documentText: () => { - this.virtual_editor.update_documents().catch(console.log); - return this.virtual_editor.virtual_document.value; - } + rootUri: '/' }); } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index 4a111286b..9a4127904 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -201,6 +201,7 @@ export class LSPConnector extends DataConnector< end, text: token.value }, + document.document_info, typed_character, this.trigger_kind ) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts index d3976ee6b..011659d9a 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts @@ -10,6 +10,7 @@ import { ICompletionManager } from '@jupyterlab/completer'; import { LSPConnector } from './components/completion'; import { CodeEditor } from '@jupyterlab/codeeditor'; import { VirtualFileEditor } from '../../virtual/editors/file_editor'; +import { DocumentConnectionManager } from '../../connection_manager'; export class FileEditorAdapter extends JupyterLabWidgetAdapter { editor: FileEditor; @@ -48,14 +49,16 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { app: JupyterFrontEnd, protected completion_manager: ICompletionManager, rendermime_registry: IRenderMimeRegistry, - server_root: string + server_root: string, + connection_manager: DocumentConnectionManager ) { super( app, editor_widget, rendermime_registry, 'completer:invoke-file', - server_root + server_root, + connection_manager ); this.jumper = jumper; this.editor = editor_widget.content; diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 40851d9f0..bc672e8f1 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -121,7 +121,8 @@ export abstract class JupyterLabWidgetAdapter protected widget: IDocumentWidget, protected rendermime_registry: IRenderMimeRegistry, invoke: string, - private server_root: string + private server_root: string, + connection_manager: DocumentConnectionManager ) { this.status_message = new StatusMessage(); this.widget.context.pathChanged.connect(this.reload_connection.bind(this)); @@ -129,7 +130,7 @@ export abstract class JupyterLabWidgetAdapter this.invoke_command = invoke; this.document_connected = new Signal(this); this.adapters = new Map(); - this.connection_manager = new DocumentConnectionManager(); + this.connection_manager = connection_manager; this.connection_manager.closed.connect((manger, { virtual_document }) => { console.log( 'LSP: connection closed, disconnecting adapter', @@ -203,7 +204,9 @@ export abstract class JupyterLabWidgetAdapter if (state === 'completed') { for (let connection of this.connection_manager.connections.values()) { - connection.sendSaved(); + connection.sendSaved( + this.virtual_editor.virtual_document.document_info + ); } } } @@ -268,7 +271,10 @@ export abstract class JupyterLabWidgetAdapter virtual_document.id_path, 'has changed sending update' ); - connection.sendFullTextChange(virtual_document.value); + connection.sendFullTextChange( + virtual_document.value, + virtual_document.document_info + ); // the first change (initial) is not propagated to features, // as it has no associated CodeMirrorChange object if (!is_init) { diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index dca9a53d1..564b52748 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -14,6 +14,7 @@ import { foreign_code_extractors } from '../../extractors/defaults'; import { Cell } from '@jupyterlab/cells'; import { nbformat } from '@jupyterlab/coreutils'; import ILanguageInfoMetadata = nbformat.ILanguageInfoMetadata; +import { DocumentConnectionManager } from '../../connection_manager'; export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; @@ -30,14 +31,16 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { app: JupyterFrontEnd, completion_manager: ICompletionManager, rendermime_registry: IRenderMimeRegistry, - server_root: string + server_root: string, + connection_manager: DocumentConnectionManager ) { super( app, editor_widget, rendermime_registry, 'completer:invoke-notebook', - server_root + server_root, + connection_manager ); this.editor = editor_widget.content; this.completion_manager = completion_manager; diff --git a/packages/jupyterlab-lsp/src/connection.ts b/packages/jupyterlab-lsp/src/connection.ts index 41330470b..046c90f89 100644 --- a/packages/jupyterlab-lsp/src/connection.ts +++ b/packages/jupyterlab-lsp/src/connection.ts @@ -7,7 +7,8 @@ import { ILspOptions, IPosition, ITokenInfo, - LspWsConnection + LspWsConnection, + IDocumentInfo } from 'lsp-ws-connection'; import { CompletionTriggerKind } from './lsp'; import { until_ready } from './utils'; @@ -20,13 +21,14 @@ export class LSPConnection extends LspWsConnection { } public sendSelectiveChange( - changeEvent: lsProtocol.TextDocumentContentChangeEvent + changeEvent: lsProtocol.TextDocumentContentChangeEvent, + documentInfo: IDocumentInfo ) { - this._sendChange([changeEvent]); + this._sendChange([changeEvent], documentInfo); } - public sendFullTextChange(text: string): void { - this._sendChange([{ text }]); + public sendFullTextChange(text: string, documentInfo: IDocumentInfo): void { + this._sendChange([{ text }], documentInfo); } public isRenameSupported() { @@ -35,7 +37,11 @@ export class LSPConnection extends LspWsConnection { ); } - async rename(location: IPosition, newName: string): Promise { + async rename( + location: IPosition, + documentInfo: IDocumentInfo, + newName: string + ): Promise { if (!this.isConnected || !this.isRenameSupported()) { return; } @@ -44,7 +50,7 @@ export class LSPConnection extends LspWsConnection { this.connection .sendRequest('textDocument/rename', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -95,15 +101,16 @@ export class LSPConnection extends LspWsConnection { } private _sendChange( - changeEvents: lsProtocol.TextDocumentContentChangeEvent[] + changeEvents: lsProtocol.TextDocumentContentChangeEvent[], + documentInfo: IDocumentInfo ) { if (!this.isConnected || !this.isInitialized) { return; } const textDocumentChange: lsProtocol.DidChangeTextDocumentParams = { textDocument: { - uri: this.documentInfo.documentUri, - version: this.documentVersion + uri: documentInfo.uri, + version: documentInfo.version } as lsProtocol.VersionedTextDocumentIdentifier, contentChanges: changeEvents }; @@ -111,12 +118,13 @@ export class LSPConnection extends LspWsConnection { 'textDocument/didChange', textDocumentChange ); - this.documentVersion++; + documentInfo.version++; } public async getCompletion( location: IPosition, token: ITokenInfo, + documentInfo: IDocumentInfo, triggerCharacter: string, triggerKind: CompletionTriggerKind ): Promise { @@ -133,7 +141,7 @@ export class LSPConnection extends LspWsConnection { this.connection .sendRequest('textDocument/completion', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index f3086e589..3902980bf 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -1,4 +1,4 @@ -import { VirtualDocument } from './virtual/document'; +import { VirtualDocument, VirtualDocumentInfo } from './virtual/document'; import { LSPConnection } from './connection'; import { Signal } from '@phosphor/signaling'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; @@ -94,23 +94,12 @@ export class DocumentConnectionManager { language ); - let socket = new WebSocket(uris.socket); - - let connection = new LSPConnection({ - languageId: language, - serverUri: uris.server, - rootUri: uris.base, - documentUri: uris.document, - documentText: () => { - // NOTE: Update is async now and this is not really used, as an alternative method - // which is compatible with async is used. - // This should be only used in the initialization step. - // if (main_connection.isConnected) { - // console.warn('documentText is deprecated for use in JupyterLab LSP'); - // } - return virtual_document.value; - } - }).connect(socket); + virtual_document.document_info = new VirtualDocumentInfo( + virtual_document, + uris.document + ); + + const connection = Private.connection(language, uris); connection.on('error', e => { console.warn(e); @@ -137,7 +126,16 @@ export class DocumentConnectionManager { } }); + if (connection.isReady) { + connection.sendOpen(virtual_document.document_info); + } else { + connection.on('serverInitialized', () => { + connection.sendOpen(virtual_document.document_info); + }); + } + this.connections.set(virtual_document.id_path, connection); + return connection; } @@ -224,7 +222,7 @@ export namespace DocumentConnectionManager { export function solve_uris( virtual_document: VirtualDocument, language: string - ) { + ): IURIs { const wsBase = PageConfig.getBaseUrl().replace(/^http/, 'ws'); const rootUri = PageConfig.getOption('rootUri'); const virtualDocumentsUri = PageConfig.getOption('virtualDocumentsUri'); @@ -240,4 +238,35 @@ export namespace DocumentConnectionManager { socket: URLExt.join(wsBase, 'lsp', language) }; } + + export interface IURIs { + base: string; + document: string; + server: string; + socket: string; + } +} + +namespace Private { + type TConnectionKey = [string, string, string]; + + const _connections = new Map<[string, string, string], LSPConnection>(); + + export function connection( + language: string, + uris: DocumentConnectionManager.IURIs + ) { + const key: TConnectionKey = [language, uris.server, uris.base]; + if (!_connections.has(key)) { + console.log('opening new connection for', ...key); + const socket = new WebSocket(uris.socket); + const connection = new LSPConnection({ + languageId: language, + serverUri: uris.server, + rootUri: uris.base + }).connect(socket); + _connections.set(key, connection); + } + return _connections.get(key); + } } diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index 72119f1c5..c5013835f 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -34,6 +34,7 @@ import IPaths = JupyterFrontEnd.IPaths; import { IStatusBar } from '@jupyterlab/statusbar'; import { LSPStatus } from './adapters/jupyterlab/components/statusbar'; import { IDocumentWidget } from '@jupyterlab/docregistry/lib/registry'; +import { DocumentConnectionManager } from './connection_manager'; const lsp_commands: Array = [].concat( ...lsp_features.map(feature => feature.commands) @@ -69,6 +70,7 @@ const plugin: JupyterFrontEndPlugin = { labShell: ILabShell, status_bar: IStatusBar ) => { + const connection_manager = new DocumentConnectionManager(); // temporary workaround for getting the absolute path let server_root = paths.directories.serverRoot; if (server_root.startsWith('~')) { @@ -153,7 +155,8 @@ const plugin: JupyterFrontEndPlugin = { app, completion_manager, rendermime_registry, - server_root + server_root, + connection_manager ); file_editor_adapters.set(fileEditor.id, adapter); } @@ -176,7 +179,8 @@ const plugin: JupyterFrontEndPlugin = { app, completion_manager, rendermime_registry, - server_root + server_root, + connection_manager ); notebook_adapters.set(widget.id, adapter); }); diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index a407c695d..bc8693b29 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -14,6 +14,7 @@ import { IVirtualPosition } from '../positioning'; import IRange = CodeEditor.IRange; +import { IDocumentInfo } from 'lsp-ws-connection/src'; type language = string; @@ -83,6 +84,32 @@ export function is_within_range( ); } +/** + * a virtual implementation of IDocumentInfo + */ +export class VirtualDocumentInfo implements IDocumentInfo { + private _document: VirtualDocument; + private _uri: string; + version = 0; + + constructor(document: VirtualDocument, uri: string) { + this._document = document; + this._uri = uri; + } + + get text() { + return this._document.value; + } + + get uri() { + return this._uri; + } + + get languageId() { + return this._document.language; + } +} + /** * A notebook can hold one or more virtual documents; there is always one, * "root" document, corresponding to the language of the kernel. All other @@ -107,6 +134,10 @@ export class VirtualDocument { public foreign_document_opened: Signal; public readonly instance_id: number; public standalone: boolean; + /** + * the remote document uri, version and other server-related info + */ + public document_info: IDocumentInfo; /** * Virtual lines keep all the lines present in the document AND extracted to the foreign document. */ diff --git a/packages/lsp-ws-connection/src/types.ts b/packages/lsp-ws-connection/src/types.ts index a609b3e57..60d264c75 100644 --- a/packages/lsp-ws-connection/src/types.ts +++ b/packages/lsp-ws-connection/src/types.ts @@ -12,6 +12,13 @@ export interface ITokenInfo { text: string; } +export interface IDocumentInfo { + uri: string; + version: number; + text: string; + languageId: string; +} + type ConnectionEvent = | 'completion' | 'completionResolved' @@ -64,20 +71,25 @@ export interface ILspConnection { * The initialize request tells the server which options the client supports */ sendInitialize(): void; + /** + * Inform the server that the document was opened + */ + sendOpen(documentInfo: IDocumentInfo): void; /** * Sends the full text of the document to the server */ - sendChange(): void; + sendChange(documentInfo: IDocumentInfo): void; /** * Requests additional information for a particular character */ - getHoverTooltip(position: IPosition): void; + getHoverTooltip(position: IPosition, documentInfo: IDocumentInfo): void; /** * Request possible completions from the server */ getCompletion( location: IPosition, token: ITokenInfo, + documentInfo: IDocumentInfo, triggerCharacter?: string, triggerKind?: lsProtocol.CompletionTriggerKind ): void; @@ -88,31 +100,31 @@ export interface ILspConnection { /** * Request possible signatures for the current method */ - getSignatureHelp(position: IPosition): void; + getSignatureHelp(position: IPosition, documentInfo: IDocumentInfo): void; /** * Request all matching symbols in the document scope */ - getDocumentHighlights(position: IPosition): void; + getDocumentHighlights(position: IPosition, documentInfo: IDocumentInfo): void; /** * Request a link to the definition of the current symbol. The results will not be displayed * unless they are within the same file URI */ - getDefinition(position: IPosition): void; + getDefinition(position: IPosition, documentInfo: IDocumentInfo): void; /** * Request a link to the type definition of the current symbol. The results will not be displayed * unless they are within the same file URI */ - getTypeDefinition(position: IPosition): void; + getTypeDefinition(position: IPosition, documentInfo: IDocumentInfo): void; /** * Request a link to the implementation of the current symbol. The results will not be displayed * unless they are within the same file URI */ - getImplementation(position: IPosition): void; + getImplementation(position: IPosition, documentInfo: IDocumentInfo): void; /** * Request a link to all references to the current symbol. The results will not be displayed * unless they are within the same file URI */ - getReferences(position: IPosition): void; + getReferences(position: IPosition, documentInfo: IDocumentInfo): void; // TODO: // Workspaces: Not in scope @@ -140,8 +152,6 @@ export interface ILspConnection { getLanguageCompletionCharacters(): string[]; getLanguageSignatureCharacters(): string[]; - getDocumentUri(): string; - /** * Does the server support go to definition? */ @@ -276,8 +286,6 @@ export interface ITextEditorOptions { export interface ILspOptions { serverUri: string; languageId: string; - documentUri: string; - documentText: () => string; rootUri: string; } diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 422307002..bebbbe355 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -6,7 +6,13 @@ import { registerServerCapability, unregisterServerCapability } from './server-capability-registration'; -import { ILspConnection, ILspOptions, IPosition, ITokenInfo } from './types'; +import { + ILspConnection, + ILspOptions, + IPosition, + ITokenInfo, + IDocumentInfo +} from './types'; /** * Changes as compared to upstream: @@ -23,12 +29,16 @@ export class LspWsConnection extends events.EventEmitter public documentInfo: ILspOptions; public serverCapabilities: protocol.ServerCapabilities; protected socket: WebSocket; - protected documentVersion = 0; protected connection: MessageConnection; + private rootUri: string; constructor(options: ILspOptions) { super(); - this.documentInfo = options; + this.rootUri = options.rootUri; + } + + get isReady() { + return this.isConnected && this.isInitialized; } /** @@ -120,10 +130,6 @@ export class LspWsConnection extends events.EventEmitter this.socket.close(); } - public getDocumentUri() { - return this.documentInfo.documentUri; - } - /** * Initialization parameters to be sent to the language server. * Subclasses can overload this when adding more features. @@ -184,7 +190,7 @@ export class LspWsConnection extends events.EventEmitter } as protocol.ClientCapabilities, initializationOptions: null, processId: null, - rootUri: this.documentInfo.rootUri, + rootUri: this.rootUri, workspaceFolders: null }; } @@ -208,38 +214,51 @@ export class LspWsConnection extends events.EventEmitter ); } - public sendChange() { - if (!this.isConnected || !this.isInitialized) { + public sendOpen(documentInfo: IDocumentInfo) { + const textDocumentMessage: protocol.DidOpenTextDocumentParams = { + textDocument: { + uri: documentInfo.uri, + languageId: documentInfo.languageId, + text: documentInfo.text, + version: documentInfo.version + } as protocol.TextDocumentItem + }; + this.connection.sendNotification( + 'textDocument/didOpen', + textDocumentMessage + ); + this.sendChange(documentInfo); + } + + public sendChange(documentInfo: IDocumentInfo) { + if (!this.isReady) { return; } const textDocumentChange: protocol.DidChangeTextDocumentParams = { textDocument: { - uri: this.documentInfo.documentUri, - version: this.documentVersion + uri: documentInfo.uri, + version: documentInfo.version } as protocol.VersionedTextDocumentIdentifier, - contentChanges: [ - { - text: this.documentInfo.documentText() - } - ] + contentChanges: [{ text: documentInfo.text }] }; this.connection.sendNotification( 'textDocument/didChange', textDocumentChange ); - this.documentVersion++; + documentInfo.version++; } - public sendSaved() { - if (!this.isConnected || !this.isInitialized) { + public sendSaved(documentInfo: IDocumentInfo) { + if (!this.isReady) { return; } + const textDocumentChange: protocol.DidSaveTextDocumentParams = { textDocument: { - uri: this.documentInfo.documentUri, - version: this.documentVersion + uri: documentInfo.uri, + version: documentInfo.version } as protocol.VersionedTextDocumentIdentifier, - text: this.documentInfo.documentText() + text: documentInfo.text }; this.connection.sendNotification( 'textDocument/didSave', @@ -247,14 +266,14 @@ export class LspWsConnection extends events.EventEmitter ); } - public getHoverTooltip(location: IPosition) { + public getHoverTooltip(location: IPosition, documentInfo: IDocumentInfo) { if (!this.isInitialized) { return; } this.connection .sendRequest('textDocument/hover', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -269,6 +288,7 @@ export class LspWsConnection extends events.EventEmitter public getCompletion( location: IPosition, token: ITokenInfo, + documentInfo: IDocumentInfo, triggerCharacter?: string, triggerKind?: protocol.CompletionTriggerKind ) { @@ -281,7 +301,7 @@ export class LspWsConnection extends events.EventEmitter 'textDocument/completion', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -303,7 +323,7 @@ export class LspWsConnection extends events.EventEmitter } public getDetailedCompletion(completionItem: protocol.CompletionItem) { - if (!this.isConnected) { + if (!this.isReady) { return; } this.connection @@ -316,12 +336,12 @@ export class LspWsConnection extends events.EventEmitter }); } - public getSignatureHelp(location: IPosition) { - if (!this.isConnected || !this.serverCapabilities?.signatureHelpProvider) { + public getSignatureHelp(location: IPosition, documentInfo: IDocumentInfo) { + if (!this.isReady || !this.serverCapabilities?.signatureHelpProvider) { return; } - const code = this.documentInfo.documentText(); + const code = documentInfo.text; const lines = code.split('\n'); const typedCharacter = lines[location.line][location.ch - 1]; @@ -335,7 +355,7 @@ export class LspWsConnection extends events.EventEmitter this.connection .sendRequest('textDocument/signatureHelp', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -350,7 +370,10 @@ export class LspWsConnection extends events.EventEmitter /** * Request the locations of all matching document symbols */ - public getDocumentHighlights(location: IPosition) { + public getDocumentHighlights( + location: IPosition, + documentInfo: IDocumentInfo + ) { if ( !this.isConnected || !this.serverCapabilities?.documentHighlightProvider @@ -363,7 +386,7 @@ export class LspWsConnection extends events.EventEmitter 'textDocument/documentHighlight', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -380,8 +403,8 @@ export class LspWsConnection extends events.EventEmitter * Request a link to the definition of the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getDefinition(location: IPosition) { - if (!this.isConnected || !this.isDefinitionSupported()) { + public getDefinition(location: IPosition, documentInfo: IDocumentInfo) { + if (!this.isReady || !this.isDefinitionSupported()) { return; } @@ -390,7 +413,7 @@ export class LspWsConnection extends events.EventEmitter 'textDocument/definition', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -407,8 +430,8 @@ export class LspWsConnection extends events.EventEmitter * Request a link to the type definition of the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getTypeDefinition(location: IPosition) { - if (!this.isConnected || !this.isTypeDefinitionSupported()) { + public getTypeDefinition(location: IPosition, documentInfo: IDocumentInfo) { + if (!this.isReady || !this.isTypeDefinitionSupported()) { return; } @@ -417,7 +440,7 @@ export class LspWsConnection extends events.EventEmitter 'textDocument/typeDefinition', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -434,7 +457,7 @@ export class LspWsConnection extends events.EventEmitter * Request a link to the implementation of the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getImplementation(location: IPosition) { + public getImplementation(location: IPosition, documentInfo: IDocumentInfo) { if (!this.isConnected || !this.isImplementationSupported()) { return; } @@ -444,7 +467,7 @@ export class LspWsConnection extends events.EventEmitter 'textDocument/implementation', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -461,7 +484,7 @@ export class LspWsConnection extends events.EventEmitter * Request a link to all references to the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getReferences(location: IPosition) { + public getReferences(location: IPosition, documentInfo: IDocumentInfo) { if (!this.isConnected || !this.isReferencesSupported()) { return; } @@ -469,7 +492,7 @@ export class LspWsConnection extends events.EventEmitter this.connection .sendRequest('textDocument/references', { textDocument: { - uri: this.documentInfo.documentUri + uri: documentInfo.uri }, position: { line: location.line, @@ -528,23 +551,10 @@ export class LspWsConnection extends events.EventEmitter protected onServerInitialized(params: protocol.InitializeResult) { this.isInitialized = true; this.serverCapabilities = params.capabilities; - const textDocumentMessage: protocol.DidOpenTextDocumentParams = { - textDocument: { - uri: this.documentInfo.documentUri, - languageId: this.documentInfo.languageId, - text: this.documentInfo.documentText(), - version: this.documentVersion - } as protocol.TextDocumentItem - }; this.connection.sendNotification('initialized'); this.connection.sendNotification('workspace/didChangeConfiguration', { settings: {} }); - this.connection.sendNotification( - 'textDocument/didOpen', - textDocumentMessage - ); - this.sendChange(); this.emit('serverInitialized', this.serverCapabilities); } } From afc45acd9c12d98e002c016b02fff5ac0958caaf Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 16 Jan 2020 09:49:30 -0500 Subject: [PATCH 02/63] more work on lazy loading --- .../src/adapters/jupyterlab/jl_adapter.ts | 6 +--- .../jupyterlab-lsp/src/connection_manager.ts | 33 ++++++++++--------- .../lsp-ws-connection/src/ws-connection.ts | 9 ++--- packages/lsp-ws-connection/tsconfig.json | 2 +- 4 files changed, 23 insertions(+), 27 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index bc672e8f1..9e8751a55 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -328,11 +328,7 @@ export abstract class JupyterLabWidgetAdapter document_path: this.document_path }; - let connection = this.connection_manager.connect(options).catch(() => { - this.connection_manager - .retry_to_connect(options, 0.5) - .catch(console.warn); - }); + let connection = await this.connection_manager.connect(options); return { connection, diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 3902980bf..82f19344c 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -86,7 +86,9 @@ export class DocumentConnectionManager { this.documents_changed.emit(this.documents); } - private connect_socket(options: ISocketConnectionOptions): LSPConnection { + private async connect_socket( + options: ISocketConnectionOptions + ): Promise { let { virtual_document, language } = options; const uris = DocumentConnectionManager.solve_uris( @@ -99,7 +101,7 @@ export class DocumentConnectionManager { uris.document ); - const connection = Private.connection(language, uris); + const connection = await Private.connection(language, uris); connection.on('error', e => { console.warn(e); @@ -172,7 +174,7 @@ export class DocumentConnectionManager { } async connect(options: ISocketConnectionOptions) { - let connection = this.connect_socket(options); + let connection = await this.connect_socket(options); connection.on('serverInitialized', capabilities => { this.initialized.emit({ connection, virtual_document }); @@ -248,25 +250,26 @@ export namespace DocumentConnectionManager { } namespace Private { - type TConnectionKey = [string, string, string]; + const _connections = new Map(); - const _connections = new Map<[string, string, string], LSPConnection>(); - - export function connection( + export async function connection( language: string, uris: DocumentConnectionManager.IURIs - ) { - const key: TConnectionKey = [language, uris.server, uris.base]; - if (!_connections.has(key)) { - console.log('opening new connection for', ...key); + ): Promise { + const connection_module = await import( + /* webpackChunkName: "jupyter-lsp-connection" */ './connection' + ); + if (!_connections.has(language)) { const socket = new WebSocket(uris.socket); - const connection = new LSPConnection({ + const connection = new connection_module.LSPConnection({ languageId: language, serverUri: uris.server, rootUri: uris.base - }).connect(socket); - _connections.set(key, connection); + }); + _connections.set(language, connection); + connection.connect(socket); } - return _connections.get(key); + const connection = _connections.get(language); + return connection; } } diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index bebbbe355..c86ad167c 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -374,10 +374,7 @@ export class LspWsConnection extends events.EventEmitter location: IPosition, documentInfo: IDocumentInfo ) { - if ( - !this.isConnected || - !this.serverCapabilities?.documentHighlightProvider - ) { + if (!this.isReady || !this.serverCapabilities?.documentHighlightProvider) { return; } @@ -458,7 +455,7 @@ export class LspWsConnection extends events.EventEmitter * unless they are within the same file URI */ public getImplementation(location: IPosition, documentInfo: IDocumentInfo) { - if (!this.isConnected || !this.isImplementationSupported()) { + if (!this.isReady || !this.isImplementationSupported()) { return; } @@ -485,7 +482,7 @@ export class LspWsConnection extends events.EventEmitter * unless they are within the same file URI */ public getReferences(location: IPosition, documentInfo: IDocumentInfo) { - if (!this.isConnected || !this.isReferencesSupported()) { + if (!this.isReady || !this.isReferencesSupported()) { return; } diff --git a/packages/lsp-ws-connection/tsconfig.json b/packages/lsp-ws-connection/tsconfig.json index 44d902963..e3bfc61c2 100644 --- a/packages/lsp-ws-connection/tsconfig.json +++ b/packages/lsp-ws-connection/tsconfig.json @@ -9,5 +9,5 @@ "target": "es5" }, "include": ["src"], - "exclude": ["node_modules"] + "exclude": ["node_modules", "src/test"] } From 312c9e5ea906a041d3ba53018f610a8697c5b350 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 16 Jan 2020 09:56:03 -0500 Subject: [PATCH 03/63] make highlight uri-aware --- .../src/adapters/codemirror/features/highlights.ts | 5 ++++- packages/lsp-ws-connection/src/types.ts | 2 +- packages/lsp-ws-connection/src/ws-connection.ts | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 4cb7596c8..ec9de0579 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -47,7 +47,10 @@ export class Highlights extends CodeMirrorLSPFeature { this.highlight_markers = []; } - protected handleHighlight(items: lsProtocol.DocumentHighlight[]) { + protected handleHighlight(items: lsProtocol.DocumentHighlight[], documentUri: string) { + if (documentUri !== this.virtual_document.document_info.uri) { + return + } this.clear_markers(); if (!items) { diff --git a/packages/lsp-ws-connection/src/types.ts b/packages/lsp-ws-connection/src/types.ts index 60d264c75..26ea79131 100644 --- a/packages/lsp-ws-connection/src/types.ts +++ b/packages/lsp-ws-connection/src/types.ts @@ -46,7 +46,7 @@ export interface ILspConnection { ): void; on( event: 'highlight', - callback: (highlights: lsProtocol.DocumentHighlight[]) => void + callback: (highlights: lsProtocol.DocumentHighlight[], documentUri: string) => void ): void; on( event: 'signature', diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index c86ad167c..97375f1dd 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -391,8 +391,8 @@ export class LspWsConnection extends events.EventEmitter } } as protocol.TextDocumentPositionParams ) - .then(params => { - this.emit('highlight', params); + .then(highlights => { + this.emit('highlight', highlights, documentInfo.uri); }); } From 568234c44a8dcaee76e47f8c1c4d25c614c305fc Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 16 Jan 2020 10:18:46 -0500 Subject: [PATCH 04/63] make hover uri-aware --- .../adapters/codemirror/features/highlights.ts | 7 +++++-- .../src/adapters/codemirror/features/hover.ts | 10 ++++++++-- .../src/adapters/codemirror/features/jump_to.ts | 5 ++--- packages/lsp-ws-connection/src/types.ts | 15 ++++++++++++--- packages/lsp-ws-connection/src/ws-connection.ts | 4 ++-- 5 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index ec9de0579..2a28d51f5 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -47,9 +47,12 @@ export class Highlights extends CodeMirrorLSPFeature { this.highlight_markers = []; } - protected handleHighlight(items: lsProtocol.DocumentHighlight[], documentUri: string) { + protected handleHighlight( + items: lsProtocol.DocumentHighlight[], + documentUri: string + ) { if (documentUri !== this.virtual_document.document_info.uri) { - return + return; } this.clear_markers(); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index 2a2d7cd92..198399731 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -38,7 +38,10 @@ export class Hover extends CodeMirrorLSPFeature { this.hover_character === this.last_hover_character ) { this.show_next_tooltip = true; - this.handleHover(this.last_hover_response); + this.handleHover( + this.last_hover_response, + this.virtual_document.document_info.uri + ); } }); this.connection_handlers.set('hover', this.handleHover.bind(this)); @@ -86,7 +89,10 @@ export class Hover extends CodeMirrorLSPFeature { } } - public handleHover(response: lsProtocol.Hover) { + public handleHover(response: lsProtocol.Hover, documentUri: string) { + if (documentUri !== this.virtual_document.document_info.uri) { + return; + } this.hide_hover(); this.last_hover_character = null; this.last_hover_response = null; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts index f0c1be381..55fcfb6cb 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts @@ -3,7 +3,6 @@ import * as lsProtocol from 'vscode-languageserver-protocol'; import { PositionConverter } from '../../../converter'; import { IVirtualPosition } from '../../../positioning'; import { uri_to_contents_path, uris_equal } from '../../../utils'; -import { IDocumentInfo } from 'lsp-ws-connection/src'; export class JumpToDefinition extends CodeMirrorLSPFeature { name = 'JumpToDefinition'; @@ -28,7 +27,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { async handle_jump( location_or_locations: lsProtocol.Location | lsProtocol.Location[], - document_info: IDocumentInfo + document_uri: string ) { // some language servers appear to return a single object const locations = Array.isArray(location_or_locations) @@ -47,7 +46,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { let location = locations[0]; let uri: string = decodeURI(location.uri); - let current_uri = document_info.uri; + let current_uri = document_uri; let virtual_position = PositionConverter.lsp_to_cm( location.range.start diff --git a/packages/lsp-ws-connection/src/types.ts b/packages/lsp-ws-connection/src/types.ts index 26ea79131..a1719ff23 100644 --- a/packages/lsp-ws-connection/src/types.ts +++ b/packages/lsp-ws-connection/src/types.ts @@ -39,14 +39,20 @@ export interface ILspConnection { event: 'completionResolved', callback: (item: lsProtocol.CompletionItem) => void ): void; - on(event: 'hover', callback: (hover: lsProtocol.Hover) => void): void; + on( + event: 'hover', + callback: (hover: lsProtocol.Hover, documentUri: string) => void + ): void; on( event: 'diagnostic', callback: (diagnostic: lsProtocol.PublishDiagnosticsParams) => void ): void; on( event: 'highlight', - callback: (highlights: lsProtocol.DocumentHighlight[], documentUri: string) => void + callback: ( + highlights: lsProtocol.DocumentHighlight[], + documentUri: string + ) => void ): void; on( event: 'signature', @@ -54,7 +60,10 @@ export interface ILspConnection { ): void; on( event: 'goTo', - callback: (location: Location | Location[] | LocationLink[] | null) => void + callback: ( + location: Location | Location[] | LocationLink[] | null, + documentUri: string + ) => void ): void; on(event: 'error', callback: (error: any) => void): void; on(event: 'logging', callback: (log: any) => void): void; diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 97375f1dd..a7f561747 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -281,7 +281,7 @@ export class LspWsConnection extends events.EventEmitter } } as protocol.TextDocumentPositionParams) .then(params => { - this.emit('hover', params); + this.emit('hover', params, documentInfo.uri); }); } @@ -419,7 +419,7 @@ export class LspWsConnection extends events.EventEmitter } as protocol.TextDocumentPositionParams ) .then(result => { - this.emit('goTo', result); + this.emit('goTo', result, documentInfo.uri); }); } From 48e0a94769830ea4d5af7edbb5ec12749efdca3e Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sat, 18 Jan 2020 17:19:14 -0500 Subject: [PATCH 05/63] mostly get highlighting to not duplicate in wrong editors --- .gitignore | 1 + .../codemirror/features/highlights.ts | 8 +- .../lsp-ws-connection/src/ws-connection.ts | 80 +++++++++++-------- tslint.json | 2 +- 4 files changed, 55 insertions(+), 36 deletions(-) diff --git a/.gitignore b/.gitignore index 68dc8297a..07dfcc7a0 100644 --- a/.gitignore +++ b/.gitignore @@ -114,3 +114,4 @@ junit.xml coverage/ .vscode/ _schema.d.ts +.virtual_documents/ diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 2a28d51f5..e6bb89715 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -73,7 +73,7 @@ export class Highlights extends CodeMirrorLSPFeature { } } - protected onCursorActivity() { + protected async onCursorActivity() { let root_position = this.virtual_editor .getDoc() .getCursor('start') as IRootPosition; @@ -94,10 +94,12 @@ export class Highlights extends CodeMirrorLSPFeature { let virtual_position = this.virtual_editor.root_position_to_virtual_position( root_position ); - this.connection.getDocumentHighlights( + const highlights = await this.connection.getDocumentHighlights( virtual_position, - this.virtual_document.document_info + this.virtual_document.document_info, + false ); + this.handleHighlight(highlights, this.virtual_document.document_info.uri); } catch (e) { console.warn('Could not get highlights:', e); } diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index a7f561747..b17519b33 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -370,30 +370,32 @@ export class LspWsConnection extends events.EventEmitter /** * Request the locations of all matching document symbols */ - public getDocumentHighlights( + public async getDocumentHighlights( location: IPosition, - documentInfo: IDocumentInfo + documentInfo: IDocumentInfo, + emit = true ) { if (!this.isReady || !this.serverCapabilities?.documentHighlightProvider) { return; } - this.connection - .sendRequest( - 'textDocument/documentHighlight', - { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - } - } as protocol.TextDocumentPositionParams - ) - .then(highlights => { - this.emit('highlight', highlights, documentInfo.uri); - }); + const highlights = await this.connection.sendRequest< + protocol.DocumentHighlight[] + >('textDocument/documentHighlight', { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + } + } as protocol.TextDocumentPositionParams); + + if (emit) { + this.emit('highlight', highlights, documentInfo.uri); + } + + return highlights; } /** @@ -481,24 +483,38 @@ export class LspWsConnection extends events.EventEmitter * Request a link to all references to the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getReferences(location: IPosition, documentInfo: IDocumentInfo) { + public async getReferences( + location: IPosition, + documentInfo: IDocumentInfo, + emit = false + ) { if (!this.isReady || !this.isReferencesSupported()) { return; } - this.connection - .sendRequest('textDocument/references', { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - } - } as protocol.ReferenceParams) - .then(result => { - this.emit('goTo', result); - }); + const params: protocol.ReferenceParams = { + context: { + includeDeclaration: true + }, + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + } + }; + + const locations = await this.connection.sendRequest( + 'textDocument/references', + params + ); + + if (emit) { + this.emit('goTo', locations, documentInfo.uri); + } + + return locations; } /** diff --git a/tslint.json b/tslint.json index 92feecb0f..a5a4e6a2d 100644 --- a/tslint.json +++ b/tslint.json @@ -3,7 +3,7 @@ "rules": { "prettier": [true, { "singleQuote": true }], "align": [true, "parameters", "statements"], - "await-promise": true, + "await-promise": [true, "Thenable"], "ban": [ true, ["_", "forEach"], From d8fb97fcecd4aa3214607137ee946dcb649ab734 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 10:16:02 -0500 Subject: [PATCH 06/63] start working on getting the unit tests back up --- .../src/test/connection.test.ts | 27 ++++++++++++------- .../src/test/mock-connection.ts | 5 ++-- packages/lsp-ws-connection/tsconfig.json | 2 +- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/packages/lsp-ws-connection/src/test/connection.test.ts b/packages/lsp-ws-connection/src/test/connection.test.ts index 168796f2f..2b7736dfa 100644 --- a/packages/lsp-ws-connection/src/test/connection.test.ts +++ b/packages/lsp-ws-connection/src/test/connection.test.ts @@ -11,6 +11,13 @@ interface IListeners { [type: string]: Listener[]; } +const mockInfo = { + uri: 'file://' + __dirname, + version: 0, + languageId: 'plaintext', + text: '' +}; + // There is a library that can be used to mock WebSockets, but the API surface tested here is small // enough that it is not necessary to use the library. This mock is a simple EventEmitter class MockSocket implements EventTarget { @@ -94,9 +101,7 @@ describe('LspWsConnection', () => { connection = new LspWsConnection({ languageId: 'plaintext', rootUri: 'file://' + __dirname, - documentUri: 'file://' + __dirname, - serverUri, - documentText: () => '' + serverUri }); mockSocket = new MockSocket('ws://localhost:8080'); }); @@ -285,10 +290,13 @@ describe('LspWsConnection', () => { // 2. After receiving capabilities from the server, we will send a hover mockSocket.send.onSecondCall().callsFake(str => { - connection.getHoverTooltip({ - line: 1, - ch: 0 - }); + connection.getHoverTooltip( + { + line: 1, + ch: 0 + }, + mockInfo + ); }); }); @@ -392,7 +400,8 @@ describe('LspWsConnection', () => { ch: 9 }, text: '.' - } + }, + mockInfo ); }); }); @@ -492,7 +501,7 @@ describe('LspWsConnection', () => { connection.connect(mockSocket); connection.close(); - connection.sendChange(); + connection.sendChange(mockInfo); expect(mockSocket.send.callCount).equal(0); }); }); diff --git a/packages/lsp-ws-connection/src/test/mock-connection.ts b/packages/lsp-ws-connection/src/test/mock-connection.ts index 6eff810c7..8ade623ea 100644 --- a/packages/lsp-ws-connection/src/test/mock-connection.ts +++ b/packages/lsp-ws-connection/src/test/mock-connection.ts @@ -23,6 +23,7 @@ export class MockConnection implements ILspConnection { public sendInitialize = sinon.stub(); public sendChange = sinon.stub(); + public sendOpen = sinon.stub(); public getHoverTooltip = sinon.stub(); public getCompletion = sinon.stub(); public getDetailedCompletion = sinon.stub(); @@ -47,7 +48,7 @@ export class MockConnection implements ILspConnection { this.signatureCharacters = ['(']; } - public on(type: string, listener: (arg: any) => void) { + public on(type: string, listener: (...args: any) => void) { const listeners = this.listeners[type]; if (!listeners) { this.listeners[type] = []; @@ -55,7 +56,7 @@ export class MockConnection implements ILspConnection { this.listeners[type].push(listener); } - public off(type: string, listener: (arg: any) => void) { + public off(type: string, listener: (...args: any) => void) { const listeners = this.listeners[type]; if (!listeners) { return; diff --git a/packages/lsp-ws-connection/tsconfig.json b/packages/lsp-ws-connection/tsconfig.json index e3bfc61c2..44d902963 100644 --- a/packages/lsp-ws-connection/tsconfig.json +++ b/packages/lsp-ws-connection/tsconfig.json @@ -9,5 +9,5 @@ "target": "es5" }, "include": ["src"], - "exclude": ["node_modules", "src/test"] + "exclude": ["node_modules"] } From 8b39f9d9b1b876f5eb52c63025e3bebafd885eb4 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 10:19:47 -0500 Subject: [PATCH 07/63] fix connection test --- packages/lsp-ws-connection/src/test/connection.test.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/lsp-ws-connection/src/test/connection.test.ts b/packages/lsp-ws-connection/src/test/connection.test.ts index 2b7736dfa..9f12634ed 100644 --- a/packages/lsp-ws-connection/src/test/connection.test.ts +++ b/packages/lsp-ws-connection/src/test/connection.test.ts @@ -162,18 +162,12 @@ describe('LspWsConnection', () => { setTimeout(() => { const mock = mockSocket.send; - expect(mock.callCount).equal(5); + expect(mock.callCount).equal(3); - // 3, 4, 5 are sent after initialization + // 3 is sent after initialization expect(JSON.parse(mock.getCall(2).args[0]).method).equal( 'workspace/didChangeConfiguration' ); - expect(JSON.parse(mock.getCall(3).args[0]).method).equal( - 'textDocument/didOpen' - ); - expect(JSON.parse(mock.getCall(4).args[0]).method).equal( - 'textDocument/didChange' - ); done(); }, 0); From e11a63fecbc11a6965279db3ee3571d69683be49 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 11:58:49 -0500 Subject: [PATCH 08/63] add rename to lsp interface --- packages/lsp-ws-connection/src/types.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/lsp-ws-connection/src/types.ts b/packages/lsp-ws-connection/src/types.ts index a1719ff23..0987d11cb 100644 --- a/packages/lsp-ws-connection/src/types.ts +++ b/packages/lsp-ws-connection/src/types.ts @@ -65,6 +65,10 @@ export interface ILspConnection { documentUri: string ) => void ): void; + on( + event: 'rename', + callback: (edit: lsProtocol.WorkspaceEdit | null) => void + ): void; on(event: 'error', callback: (error: any) => void): void; on(event: 'logging', callback: (log: any) => void): void; From ac82ff0301dad521bcc0918a08a70f4bddbff8a0 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:01:40 -0500 Subject: [PATCH 09/63] hide non-essential logging behind DEBUG --- .../src/adapters/codemirror/cm_adapter.ts | 19 ++++--- .../codemirror/features/diagnostics.ts | 17 +++---- .../features/diagnostics_listing.tsx | 1 + .../src/adapters/jupyterlab/jl_adapter.ts | 50 +++++++++++-------- .../src/adapters/jupyterlab/notebook.ts | 18 ++++--- .../jupyterlab-lsp/src/connection_manager.ts | 30 +++++++---- .../jupyterlab-lsp/src/virtual/console.ts | 6 ++- .../src/virtual/editors/notebook.ts | 4 +- 8 files changed, 88 insertions(+), 57 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 292f87cd3..5c3c7db90 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -6,6 +6,8 @@ import { IRootPosition } from '../../positioning'; import { ILSPFeature } from './feature'; import { IJupyterLabComponentsManager } from '../jupyterlab/jl_adapter'; +const DEBUG = false; + export class CodeMirrorAdapter { features: Map; @@ -38,12 +40,17 @@ export class CodeMirrorAdapter { public async updateAfterChange() { this.jupyterlab_components.remove_tooltip(); - await until_ready(() => this.last_change != null, 30, 22).catch(() => { - this.invalidateLastChange(); - throw Error( - 'No change obtained from CodeMirror editor within the expected time of 0.66s' - ); - }); + + try { + await until_ready(() => this.last_change != null, 30, 22); + } catch (err) { + DEBUG && + console.log( + 'No change obtained from CodeMirror editor within the expected time of 0.66s' + ); + return; + } + let change: CodeMirror.EditorChange = this.last_change; try { diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index 543f94fd5..bcf222282 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -14,9 +14,10 @@ import { IEditorDiagnostic } from './diagnostics_listing'; import { VirtualDocument } from '../../../virtual/document'; -import { DocumentConnectionManager } from '../../../connection_manager'; import { VirtualEditor } from '../../../virtual/editor'; +const DEBUG = false; + // TODO: settings const default_severity = 2; @@ -223,19 +224,13 @@ export class Diagnostics extends CodeMirrorLSPFeature { } public handleDiagnostic(response: lsProtocol.PublishDiagnosticsParams) { + if (response.uri !== this.virtual_document.document_info.uri) { + return; + } /* TODO: gutters */ try { let diagnostics_list: IEditorDiagnostic[] = []; - let my_uri = DocumentConnectionManager.solve_uris( - this.virtual_document, - '' - ).document; - - if (response.uri !== my_uri) { - return; - } - // Note: no deep equal for Sets or Maps in JS const markers_to_retain: Set = new Set(); @@ -273,7 +268,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { start_in_root ); } catch (e) { - console.log(e, diagnostics); + DEBUG && console.log(e, diagnostics); return; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx index 323ef7feb..b91d7ba7f 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx @@ -163,6 +163,7 @@ function SortableTH(props: { name: string; listing: DiagnosticsListing }): any { const is_sort_key = props.name === props.listing.sort_key; return ( props.listing.sort(props.name)} className={ is_sort_key diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index faa3778d3..8f7e1aeb8 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -33,6 +33,8 @@ import { } from '../../connection_manager'; import { Rename } from '../codemirror/features/rename'; +const DEBUG = false; + export const lsp_features: Array = [ Completion, Diagnostics, @@ -132,10 +134,11 @@ export abstract class JupyterLabWidgetAdapter this.adapters = new Map(); this.connection_manager = connection_manager; this.connection_manager.closed.connect((manger, { virtual_document }) => { - console.log( - 'LSP: connection closed, disconnecting adapter', - virtual_document.id_path - ); + DEBUG && + console.log( + 'LSP: connection closed, disconnecting adapter', + virtual_document.id_path + ); this.disconnect_adapter(virtual_document); }); this.connection_manager.connected.connect((manager, data) => { @@ -232,11 +235,12 @@ export abstract class JupyterLabWidgetAdapter await this.virtual_editor.update_documents().then(() => { // refresh the document on the LSP server this.document_changed(virtual_document, virtual_document, true); - console.log( - 'LSP: virtual document(s) for', - this.document_path, - 'have been initialized' - ); + DEBUG && + console.log( + 'LSP: virtual document(s) for', + this.document_path, + 'have been initialized' + ); }); } @@ -262,19 +266,24 @@ export abstract class JupyterLabWidgetAdapter let adapter = this.adapters.get(virtual_document.id_path); if (typeof connection === 'undefined') { - console.log('LSP: Skipping document update signal: connection not ready'); + DEBUG && + console.log( + 'LSP: Skipping document update signal: connection not ready' + ); return; } if (typeof adapter === 'undefined') { - console.log('LSP: Skipping document update signal: adapter not ready'); + DEBUG && + console.log('LSP: Skipping document update signal: adapter not ready'); return; } - console.log( - 'LSP: virtual document', - virtual_document.id_path, - 'has changed sending update' - ); + DEBUG && + console.log( + 'LSP: virtual document', + virtual_document.id_path, + 'has changed sending update' + ); connection.sendFullTextChange( virtual_document.value, virtual_document.document_info @@ -320,9 +329,10 @@ export abstract class JupyterLabWidgetAdapter private async connect(virtual_document: VirtualDocument) { let language = virtual_document.language; - console.log( - `LSP: will connect using root path: ${this.root_path} and language: ${language}` - ); + DEBUG && + console.log( + `LSP: will connect using root path: ${this.root_path} and language: ${language}` + ); let options = { virtual_document, @@ -378,7 +388,7 @@ export abstract class JupyterLabWidgetAdapter this, adapter_features ); - console.log('LSP: Adapter for', this.document_path, 'is ready.'); + DEBUG && console.log('LSP: Adapter for', this.document_path, 'is ready.'); return adapter; } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 564b52748..7d4a555c3 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -16,6 +16,8 @@ import { nbformat } from '@jupyterlab/coreutils'; import ILanguageInfoMetadata = nbformat.ILanguageInfoMetadata; import { DocumentConnectionManager } from '../../connection_manager'; +const DEBUG = false; + export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; widget: NotebookPanel; @@ -56,11 +58,12 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { } change.newValue.ready .then(async spec => { - console.log( - 'LSP: Changed to ' + - change.newValue.info.language_info.name + - ' kernel, reconnecting' - ); + DEBUG && + console.log( + 'LSP: Changed to ' + + change.newValue.info.language_info.name + + ' kernel, reconnecting' + ); await until_ready(this.is_ready.bind(this), -1); this.reload_connection(); }) @@ -117,9 +120,10 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { } async init_once_ready() { - console.log('LSP: waiting for', this.document_path, 'to fully load'); + DEBUG && + console.log('LSP: waiting for', this.document_path, 'to fully load'); await until_ready(this.is_ready.bind(this), -1); - console.log('LSP:', this.document_path, 'ready for connection'); + DEBUG && console.log('LSP:', this.document_path, 'ready for connection'); this.virtual_editor = new VirtualEditorForNotebook( this.widget.content, diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 82f19344c..0b452d358 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -4,6 +4,8 @@ import { Signal } from '@phosphor/signaling'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { sleep, until_ready } from './utils'; +const DEBUG = false; + export interface IDocumentConnectionData { virtual_document: VirtualDocument; connection: LSPConnection; @@ -68,10 +70,11 @@ export class DocumentConnectionManager { connect_document_signals(virtual_document: VirtualDocument) { virtual_document.foreign_document_opened.connect((host, context) => { - console.log( - 'LSP: Connecting foreign document: ', - context.foreign_document.id_path - ); + DEBUG && + console.log( + 'LSP: Connecting foreign document: ', + context.foreign_document.id_path + ); this.connect_document_signals(context.foreign_document); }); virtual_document.foreign_document_closed.connect( @@ -110,7 +113,7 @@ export class DocumentConnectionManager { // TODO: those codes may be specific to my proxy client, need to investigate if (error.message.indexOf('code = 1005') !== -1) { console.warn('LSP: Connection failed for ' + virtual_document.id_path); - console.log('LSP: disconnecting ' + virtual_document.id_path); + console.warn('LSP: disconnecting ' + virtual_document.id_path); this.closed.emit({ connection, virtual_document }); this.ignored_languages.add(virtual_document.language); console.warn( @@ -161,11 +164,12 @@ export class DocumentConnectionManager { success = true; }) .catch(e => { - console.log(e); + DEBUG && console.warn(e); }); - console.log( - 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' - ); + DEBUG && + console.log( + 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' + ); await sleep(interval); // gradually increase the time delay, up to 5 sec @@ -192,7 +196,13 @@ export class DocumentConnectionManager { ).catch(() => { throw Error('LSP: Connect timed out for ' + virtual_document.id_path); }); - console.log('LSP:', document_path, virtual_document.id_path, 'connected.'); + DEBUG && + console.log( + 'LSP:', + document_path, + virtual_document.id_path, + 'connected.' + ); connection.on('close', closed_manually => { if (!closed_manually) { diff --git a/packages/jupyterlab-lsp/src/virtual/console.ts b/packages/jupyterlab-lsp/src/virtual/console.ts index 0f2f884ac..0d3c3d157 100644 --- a/packages/jupyterlab-lsp/src/virtual/console.ts +++ b/packages/jupyterlab-lsp/src/virtual/console.ts @@ -1,5 +1,7 @@ import '../../style/console.css'; +const DEBUG = false; + export abstract class EditorLogConsole { abstract log(...args: any[]): void; abstract warn(...args: any[]): void; @@ -31,7 +33,7 @@ export class FloatingConsole extends EditorLogConsole { } log(...args: any[]) { - this.append(this.to_string(args), 'log'); + DEBUG && this.append(this.to_string(args), 'log'); } warn(...args: any[]) { this.append(this.to_string(args), 'warn'); @@ -43,7 +45,7 @@ export class FloatingConsole extends EditorLogConsole { export class BrowserConsole extends EditorLogConsole { log(...args: any[]) { - console.log('LSP: ', ...args); + DEBUG && console.log('LSP: ', ...args); } warn(...args: any[]) { console.warn('LSP: ', ...args); diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index d28f83736..c6f22d1c9 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -11,6 +11,8 @@ import { IVirtualPosition } from '../../positioning'; +const DEBUG = false; + // @ts-ignore class DocDispatcher implements CodeMirror.Doc { virtual_editor: VirtualEditorForNotebook; @@ -180,7 +182,7 @@ export class VirtualEditorForNotebook extends VirtualEditor { let editor = this.get_editor_at_root_line(pos); return editor.charCoords(pos, mode); } catch (e) { - console.log(e); + DEBUG && console.log(e); return { bottom: 0, left: 0, right: 0, top: 0 }; } } From 41c5b0cda0306722b69f5b4f09ca269d6a993fcc Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:03:20 -0500 Subject: [PATCH 10/63] wait for kernel before initializing notebook adapter to reduce log output --- .../jupyterlab-lsp/src/command_manager.ts | 6 +++- packages/jupyterlab-lsp/src/connection.ts | 4 +-- packages/jupyterlab-lsp/src/index.ts | 33 +++++++++++-------- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/jupyterlab-lsp/src/command_manager.ts b/packages/jupyterlab-lsp/src/command_manager.ts index 7740ac9ed..a33524d60 100644 --- a/packages/jupyterlab-lsp/src/command_manager.ts +++ b/packages/jupyterlab-lsp/src/command_manager.ts @@ -217,7 +217,11 @@ export class NotebookCommandManager extends ContextCommandManager { let ce_cursor = editor.getCursorPosition(); let cm_cursor = PositionConverter.ce_to_cm(ce_cursor) as IEditorPosition; - let virtual_editor = this.current_adapter.virtual_editor; + let virtual_editor = this.current_adapter?.virtual_editor; + + if (!virtual_editor) { + return null; + } let root_position = virtual_editor.transform_from_notebook_to_root( cell, diff --git a/packages/jupyterlab-lsp/src/connection.ts b/packages/jupyterlab-lsp/src/connection.ts index 046c90f89..4c9ccad50 100644 --- a/packages/jupyterlab-lsp/src/connection.ts +++ b/packages/jupyterlab-lsp/src/connection.ts @@ -42,7 +42,7 @@ export class LSPConnection extends LspWsConnection { documentInfo: IDocumentInfo, newName: string ): Promise { - if (!this.isConnected || !this.isRenameSupported()) { + if (!this.isReady || !this.isRenameSupported()) { return; } @@ -56,7 +56,7 @@ export class LSPConnection extends LspWsConnection { line: location.line, character: location.ch }, - newName: newName + newName } as lsProtocol.RenameParams) .then( (result: lsProtocol.WorkspaceEdit | null) => { diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index c5013835f..d9c82e83f 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -170,19 +170,26 @@ const plugin: JupyterFrontEndPlugin = { ); command_manager.add(lsp_commands); - notebookTracker.widgetAdded.connect((sender, widget) => { - // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) - let jumper = new NotebookJumper(widget, documentManager); - let adapter = new NotebookAdapter( - widget, - jumper, - app, - completion_manager, - rendermime_registry, - server_root, - connection_manager - ); - notebook_adapters.set(widget.id, adapter); + notebookTracker.widgetAdded.connect(async (sender, widget) => { + // Don't try to do anything until we have a kernel + widget.context.session.kernelChanged.connect(async () => { + // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) + await widget.context.session.kernel.ready; + if (notebook_adapters.get(widget.id)) { + return; + } + let jumper = new NotebookJumper(widget, documentManager); + let adapter = new NotebookAdapter( + widget, + jumper, + app, + completion_manager, + rendermime_registry, + server_root, + connection_manager + ); + notebook_adapters.set(widget.id, adapter); + }); }); // position context menu entries after 10th but before 11th default entry From e5ec5368311cc3012b995c269234268f29079e84 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:04:43 -0500 Subject: [PATCH 11/63] hide more logging --- .../src/adapters/codemirror/features/diagnostics.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index bcf222282..0c68ed966 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -119,7 +119,6 @@ export class Diagnostics extends CodeMirrorLSPFeature { } if (!panel_widget.isAttached) { - console.warn(adapter.widget_id); app.shell.add(panel_widget, 'main', { ref: adapter.widget_id, mode: 'split-bottom' From 1ff5b32e9b51ff78c8906e4f88bd9aa15684e2cf Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:11:59 -0500 Subject: [PATCH 12/63] squash react log errors --- .../features/diagnostics_listing.tsx | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx index b91d7ba7f..ff2af897a 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx @@ -155,7 +155,7 @@ class Column { } render_header(listing: DiagnosticsListing): ReactElement { - return ; + return ; } } @@ -186,7 +186,7 @@ export class DiagnosticsListing extends VDomRenderer { new Column({ name: 'Virtual Document', render_cell: (row, context) => ( - + ), @@ -197,14 +197,14 @@ export class DiagnosticsListing extends VDomRenderer { name: 'Message', render_cell: row => { let message = message_without_code(row.data.diagnostic); - return {message}; + return {message}; }, sort: (a, b) => a.data.diagnostic.message.localeCompare(b.data.diagnostic.message) }), new Column({ name: 'Code', - render_cell: row => {row.data.diagnostic.code}, + render_cell: row => {row.data.diagnostic.code}, sort: (a, b) => (a.data.diagnostic.code + '').localeCompare( b.data.diagnostic.source + '' @@ -214,32 +214,34 @@ export class DiagnosticsListing extends VDomRenderer { name: 'Severity', // TODO: use default diagnostic severity render_cell: row => ( - {diagnosticSeverityNames[row.data.diagnostic.severity || 1]} + + {diagnosticSeverityNames[row.data.diagnostic.severity || 1]} + ), sort: (a, b) => a.data.diagnostic.severity > b.data.diagnostic.severity ? 1 : -1 }), new Column({ name: 'Source', - render_cell: row => {row.data.diagnostic.source}, + render_cell: row => {row.data.diagnostic.source}, sort: (a, b) => a.data.diagnostic.source.localeCompare(b.data.diagnostic.source) }), new Column({ name: 'Cell', - render_cell: row => {row.cell_number}, + render_cell: row => {row.cell_number}, sort: (a, b) => (a.cell_number > b.cell_number ? 1 : -1), is_available: context => context.editor.has_cells }), new Column({ name: 'Line', - render_cell: row => {row.data.range.start.line}, + render_cell: row => {row.data.range.start.line}, sort: (a, b) => a.data.range.start.line > b.data.range.start.line ? 1 : -1 }), new Column({ name: 'Ch', - render_cell: row => {row.data.range.start.line}, + render_cell: row => {row.data.range.start.line}, sort: (a, b) => (a.data.range.start.ch > b.data.range.start.ch ? 1 : -1) }) ]; From 83c1dd8b37b91965ea1dafd65c8b573af890b5fd Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:31:14 -0500 Subject: [PATCH 13/63] squash more errors with checks on widget lifecycle --- .../src/adapters/codemirror/cm_adapter.ts | 11 ++++++--- .../codemirror/features/highlights.ts | 16 ++++++++++--- .../jupyterlab/components/statusbar.tsx | 23 +++++++++++-------- .../src/adapters/jupyterlab/notebook.ts | 5 +++- packages/jupyterlab-lsp/src/index.ts | 10 ++++++-- .../src/virtual/editors/notebook.ts | 3 +++ 6 files changed, 49 insertions(+), 19 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 5c3c7db90..6d427fa07 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -53,11 +53,16 @@ export class CodeMirrorAdapter { let change: CodeMirror.EditorChange = this.last_change; + let root_position: IRootPosition; + try { - const root_position = this.editor - .getDoc() - .getCursor('end') as IRootPosition; + root_position = this.editor.getDoc().getCursor('end') as IRootPosition; + } catch (err) { + DEBUG && console.log('LSP: Root positon not found'); + return; + } + try { let document = this.editor.document_at_root_position(root_position); if (this.virtual_document !== document) { diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index e6bb89715..f39b15159 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -5,6 +5,8 @@ import { VirtualDocument } from '../../../virtual/document'; import { IRootPosition } from '../../../positioning'; import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; +const DEBUG = false; + export class Highlights extends CodeMirrorLSPFeature { name = 'Highlights'; protected highlight_markers: CodeMirror.TextMarker[] = []; @@ -74,9 +76,17 @@ export class Highlights extends CodeMirrorLSPFeature { } protected async onCursorActivity() { - let root_position = this.virtual_editor - .getDoc() - .getCursor('start') as IRootPosition; + let root_position: IRootPosition; + + try { + root_position = this.virtual_editor + .getDoc() + .getCursor('start') as IRootPosition; + } catch (err) { + DEBUG && console.warn('LSP: no root position available'); + return; + } + let document: VirtualDocument; try { document = this.virtual_editor.document_at_root_position(root_position); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx index a4abe1abd..4e6ed3e37 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx @@ -539,7 +539,8 @@ export namespace LSPStatus { set adapter(adapter: JupyterLabWidgetAdapter | null) { const oldAdapter = this._adapter; - if (oldAdapter !== null) { + + if (oldAdapter != null) { oldAdapter.connection_manager.connected.disconnect(this._onChange); oldAdapter.connection_manager.initialized.connect(this._onChange); oldAdapter.connection_manager.disconnected.disconnect(this._onChange); @@ -550,19 +551,21 @@ export namespace LSPStatus { oldAdapter.status_message.changed.connect(this._onChange); } - let onChange = this._onChange.bind(this); - adapter.connection_manager.connected.connect(onChange); - adapter.connection_manager.initialized.connect(onChange); - adapter.connection_manager.disconnected.connect(onChange); - adapter.connection_manager.closed.connect(onChange); - adapter.connection_manager.documents_changed.connect(onChange); - adapter.status_message.changed.connect(onChange); + if (adapter != null) { + adapter.connection_manager.connected.connect(this._onChange); + adapter.connection_manager.initialized.connect(this._onChange); + adapter.connection_manager.disconnected.connect(this._onChange); + adapter.connection_manager.closed.connect(this._onChange); + adapter.connection_manager.documents_changed.connect(this._onChange); + adapter.status_message.changed.connect(this._onChange); + } + this._adapter = adapter; } - private _onChange() { + private _onChange = () => { this.stateChanged.emit(void 0); - } + }; private _adapter: JupyterLabWidgetAdapter | null = null; } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 7d4a555c3..f2eec0834 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -53,7 +53,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { this.widget.context.session.kernelChanged.connect((_session, change) => { if (!change.newValue) { - console.warn('LSP: kernel was shut down'); + DEBUG && console.log('LSP: kernel was shut down'); return; } change.newValue.ready @@ -156,6 +156,9 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { connect_completion() { // see https://github.com/jupyterlab/jupyterlab/blob/c0e9eb94668832d1208ad3b00a9791ef181eca4c/packages/completer-extension/src/index.ts#L198-L213 const cell = this.widget.content.activeCell; + if (cell == null) { + return; + } this.set_completion_connector(cell); const handler = this.completion_manager.register({ connector: this.current_completion_connector, diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index d9c82e83f..0192a58a7 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -173,11 +173,17 @@ const plugin: JupyterFrontEndPlugin = { notebookTracker.widgetAdded.connect(async (sender, widget) => { // Don't try to do anything until we have a kernel widget.context.session.kernelChanged.connect(async () => { - // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) - await widget.context.session.kernel.ready; if (notebook_adapters.get(widget.id)) { return; } + + if (!widget.context.session.kernel) { + return; + } + + await widget.context.session.kernel.ready; + + // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) let jumper = new NotebookJumper(widget, documentManager); let adapter = new NotebookAdapter( widget, diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index c6f22d1c9..de6406327 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -342,6 +342,9 @@ export class VirtualEditorForNotebook extends VirtualEditor { callback: (cm_editor: CodeMirror.Editor) => any, monitor_for_new_blocks = true ) { + if (this.notebook.isDisposed) { + return; + } const cells_with_handlers = new Set(); for (let cell of this.notebook.widgets) { From 95eacdfa83495b2aeffab7ad93084864a38432f2 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:37:26 -0500 Subject: [PATCH 14/63] more log squashing --- .../src/adapters/codemirror/features/diagnostics.ts | 10 +++++----- .../src/adapters/codemirror/features/highlights.ts | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index 0c68ed966..d2c4ede4b 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -250,7 +250,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { range.end ) as IVirtualPosition; if (start.line > this.virtual_document.last_virtual_line) { - console.log( + DEBUG && console.log( 'Malformed diagnostic was skipped (out of lines) ', diagnostics ); @@ -275,7 +275,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { // and the user already changed the document so // that now this regions is in another virtual document! if (this.virtual_document !== document) { - console.log( + DEBUG && console.log( `Ignoring inspections from ${response.uri}`, ` (this region is covered by a another virtual document: ${document.uri})`, ` inspections: `, @@ -289,7 +289,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { .get(start.line) .skip_inspect.indexOf(document.id_path) !== -1 ) { - console.log( + DEBUG && console.log( 'Ignoring inspections silenced for this document:', diagnostics ); @@ -359,8 +359,8 @@ export class Diagnostics extends CodeMirrorLSPFeature { console.warn( 'Marking inspection (diagnostic text) failed, see following logs (2):' ); - console.log(diagnostics); - console.log(e); + DEBUG && console.log(diagnostics); + DEBUG && console.log(e); return; } this.marked_diagnostics.set(diagnostic_hash, marker); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index f39b15159..e1c5673c6 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -91,8 +91,8 @@ export class Highlights extends CodeMirrorLSPFeature { try { document = this.virtual_editor.document_at_root_position(root_position); } catch (e) { - console.warn( - 'Could not obtain virtual document from position', + DEBUG && console.warn( + 'LSP: Could not obtain virtual document from position', root_position ); return; From 291e5250e11341220e3e3d9dc88348e4c4e46951 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 12:56:44 -0500 Subject: [PATCH 15/63] walk back expectation of kernel readiness, change log squashing approach --- .../codemirror/features/diagnostics.ts | 31 +++++++++------- .../codemirror/features/highlights.ts | 9 +++-- .../src/adapters/jupyterlab/notebook.ts | 3 +- packages/jupyterlab-lsp/src/index.ts | 37 ++++++------------- 4 files changed, 36 insertions(+), 44 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index d2c4ede4b..d48a31b39 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -250,10 +250,11 @@ export class Diagnostics extends CodeMirrorLSPFeature { range.end ) as IVirtualPosition; if (start.line > this.virtual_document.last_virtual_line) { - DEBUG && console.log( - 'Malformed diagnostic was skipped (out of lines) ', - diagnostics - ); + DEBUG && + console.log( + 'Malformed diagnostic was skipped (out of lines) ', + diagnostics + ); return; } @@ -275,12 +276,13 @@ export class Diagnostics extends CodeMirrorLSPFeature { // and the user already changed the document so // that now this regions is in another virtual document! if (this.virtual_document !== document) { - DEBUG && console.log( - `Ignoring inspections from ${response.uri}`, - ` (this region is covered by a another virtual document: ${document.uri})`, - ` inspections: `, - diagnostics - ); + DEBUG && + console.log( + `Ignoring inspections from ${response.uri}`, + ` (this region is covered by a another virtual document: ${document.uri})`, + ` inspections: `, + diagnostics + ); return; } @@ -289,10 +291,11 @@ export class Diagnostics extends CodeMirrorLSPFeature { .get(start.line) .skip_inspect.indexOf(document.id_path) !== -1 ) { - DEBUG && console.log( - 'Ignoring inspections silenced for this document:', - diagnostics - ); + DEBUG && + console.log( + 'Ignoring inspections silenced for this document:', + diagnostics + ); return; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index e1c5673c6..33a3cef04 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -91,10 +91,11 @@ export class Highlights extends CodeMirrorLSPFeature { try { document = this.virtual_editor.document_at_root_position(root_position); } catch (e) { - DEBUG && console.warn( - 'LSP: Could not obtain virtual document from position', - root_position - ); + DEBUG && + console.warn( + 'LSP: Could not obtain virtual document from position', + root_position + ); return; } if (document !== this.virtual_document) { diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index f2eec0834..4c097e670 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -77,6 +77,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { is_ready() { return ( + !this.widget.isDisposed && this.widget.context.isReady && this.widget.content.isVisible && this.widget.content.widgets.length > 0 && @@ -93,7 +94,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { try { return this.widget.context.session.kernel.info.language_info; } catch (e) { - console.warn('Could not get kernel metadata'); + DEBUG && console.log('LSP: Could not get kernel metadata'); return null; } } diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index 0192a58a7..e3f2433a6 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -171,31 +171,18 @@ const plugin: JupyterFrontEndPlugin = { command_manager.add(lsp_commands); notebookTracker.widgetAdded.connect(async (sender, widget) => { - // Don't try to do anything until we have a kernel - widget.context.session.kernelChanged.connect(async () => { - if (notebook_adapters.get(widget.id)) { - return; - } - - if (!widget.context.session.kernel) { - return; - } - - await widget.context.session.kernel.ready; - - // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) - let jumper = new NotebookJumper(widget, documentManager); - let adapter = new NotebookAdapter( - widget, - jumper, - app, - completion_manager, - rendermime_registry, - server_root, - connection_manager - ); - notebook_adapters.set(widget.id, adapter); - }); + // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) + let jumper = new NotebookJumper(widget, documentManager); + let adapter = new NotebookAdapter( + widget, + jumper, + app, + completion_manager, + rendermime_registry, + server_root, + connection_manager + ); + notebook_adapters.set(widget.id, adapter); }); // position context menu entries after 10th but before 11th default entry From bd6442beb6060b39de13cfa85ebdc2b4990b6679 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 13:16:06 -0500 Subject: [PATCH 16/63] use solve_uris for document_info --- packages/jupyterlab-lsp/src/virtual/document.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index bc8693b29..a60318ed1 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -16,6 +16,8 @@ import { import IRange = CodeEditor.IRange; import { IDocumentInfo } from 'lsp-ws-connection/src'; +import { DocumentConnectionManager } from '../connection_manager'; + type language = string; interface IVirtualLine { @@ -102,7 +104,8 @@ export class VirtualDocumentInfo implements IDocumentInfo { } get uri() { - return this._uri; + const uris = DocumentConnectionManager.solve_uris(this._document, this.languageId); + return uris.document; } get languageId() { From 32b6a6412147f4660b64a9ca8624f99f31b7742b Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 13:56:43 -0500 Subject: [PATCH 17/63] rework document closing behavior --- .../src/adapters/jupyterlab/jl_adapter.ts | 2 +- .../jupyterlab-lsp/src/connection_manager.ts | 25 ++++++++----------- .../jupyterlab-lsp/src/virtual/document.ts | 9 ++++--- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 8f7e1aeb8..7232ab929 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -194,7 +194,7 @@ export abstract class JupyterLabWidgetAdapter } // disconnect all existing connections (and dispose adapters) - this.connection_manager.close_all(); + this.connection_manager.unregister_document(this.virtual_editor.virtual_document); // recreate virtual document using current path and language this.virtual_editor.create_virtual_document(); // reconnect diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 0b452d358..1adbefec0 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -99,10 +99,7 @@ export class DocumentConnectionManager { language ); - virtual_document.document_info = new VirtualDocumentInfo( - virtual_document, - uris.document - ); + virtual_document.document_info = new VirtualDocumentInfo(virtual_document); const connection = await Private.connection(language, uris); @@ -219,14 +216,10 @@ export class DocumentConnectionManager { return connection; } - public close_all() { - for (let [id_path, connection] of this.connections.entries()) { - let virtual_document = this.documents.get(id_path); - connection.close(); - // TODO: close() should trigger the closed event, but it does not seem to work, hence manual trigger below: - this.closed.emit({ connection, virtual_document }); - } - this.connections.clear(); + public unregister_document(virtual_document: VirtualDocument) { + const connection = this.connections.get(virtual_document.id_path); + this.connections.delete(virtual_document.id_path); + this.closed.emit({ connection, virtual_document }); } } @@ -269,7 +262,9 @@ namespace Private { const connection_module = await import( /* webpackChunkName: "jupyter-lsp-connection" */ './connection' ); - if (!_connections.has(language)) { + let connection = _connections.get(language); + + if (connection == null) { const socket = new WebSocket(uris.socket); const connection = new connection_module.LSPConnection({ languageId: language, @@ -279,7 +274,9 @@ namespace Private { _connections.set(language, connection); connection.connect(socket); } - const connection = _connections.get(language); + + connection = _connections.get(language); + return connection; } } diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index a60318ed1..2d20f3427 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -91,12 +91,10 @@ export function is_within_range( */ export class VirtualDocumentInfo implements IDocumentInfo { private _document: VirtualDocument; - private _uri: string; version = 0; - constructor(document: VirtualDocument, uri: string) { + constructor(document: VirtualDocument) { this._document = document; - this._uri = uri; } get text() { @@ -104,7 +102,10 @@ export class VirtualDocumentInfo implements IDocumentInfo { } get uri() { - const uris = DocumentConnectionManager.solve_uris(this._document, this.languageId); + const uris = DocumentConnectionManager.solve_uris( + this._document, + this.languageId + ); return uris.document; } From 93c50673f485426fdc6112e8627c3c86a2691298 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 13:57:46 -0500 Subject: [PATCH 18/63] linting --- packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 7232ab929..fdacd0c63 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -194,7 +194,9 @@ export abstract class JupyterLabWidgetAdapter } // disconnect all existing connections (and dispose adapters) - this.connection_manager.unregister_document(this.virtual_editor.virtual_document); + this.connection_manager.unregister_document( + this.virtual_editor.virtual_document + ); // recreate virtual document using current path and language this.virtual_editor.create_virtual_document(); // reconnect From 4ac60a6fa4599e6664b91baed2b20fc18233cd2b Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 14:23:51 -0500 Subject: [PATCH 19/63] handle some open doc issues when closing tabs --- atest/01_Editor.robot | 2 +- atest/03_Notebook.robot | 3 +-- atest/Keywords.robot | 14 +++++++++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/atest/01_Editor.robot b/atest/01_Editor.robot index 6fd46b8a7..9050c9d6e 100644 --- a/atest/01_Editor.robot +++ b/atest/01_Editor.robot @@ -67,7 +67,7 @@ Editor Shows Features for Language Set Tags language:${Language.lower()} Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}editor${/}${Language.lower()} Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} - Lab Command Close All Tabs + Try to Close All Tabs Open ${file} in ${MENU EDITOR} Capture Page Screenshot 00-opened.png FOR ${f} IN @{features} diff --git a/atest/03_Notebook.robot b/atest/03_Notebook.robot index 5393f15f3..ebdfb755f 100644 --- a/atest/03_Notebook.robot +++ b/atest/03_Notebook.robot @@ -1,10 +1,10 @@ *** Settings *** Suite Setup Setup Suite For Screenshots notebook Resource Keywords.robot +Test Setup Try to Close All Tabs *** Test Cases *** Python - [Setup] Gently Reset Workspace Setup Notebook Python Python.ipynb Capture Page Screenshot 01-python.png ${diagnostic} = Set Variable W291 trailing whitespace (pycodestyle) @@ -13,7 +13,6 @@ Python Clean Up After Working With File Python.ipynb Foregin Extractors - [Setup] Gently Reset Workspace Setup Notebook Python Foreign extractors.ipynb @{diagnostics} = Create List Failed to parse expression undefined name 'valid' (pyflakes) Trailing whitespace is superfluous. (lintr) FOR ${diagnostic} IN @{diagnostics} diff --git a/atest/Keywords.robot b/atest/Keywords.robot index c664057ca..d952f8c37 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -66,9 +66,17 @@ Open JupyterLab Close JupyterLab Close All Browsers -Reset Application State +Close All Tabs + Accept Default Dialog Option Lab Command Close All Tabs Accept Default Dialog Option + +Try to Close All Tabs + Wait Until Keyword Succeeds 5x 50ms Close All Tabs + +Reset Application State + Try to Close All Tabs + Accept Default Dialog Option Ensure All Kernels Are Shut Down Lab Command Reset Application State Wait For Splash @@ -174,7 +182,7 @@ Setup Notebook Set Tags language:${Language.lower()} Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${file.lower()} Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} - Lab Command Close All Tabs + Try to Close All Tabs Open ${file} in ${MENU NOTEBOOK} Capture Page Screenshot 00-opened.png @@ -194,7 +202,7 @@ Wait For Dialog Wait Until Page Contains Element ${DIALOG WINDOW} timeout=180s Gently Reset Workspace - Lab Command Close All Tabs + Try to Close All Tabs Enter Cell Editor [Arguments] ${cell_nr} ${line}=1 From 548998b4c0b10e22525f5b75ebb0e187154ca9d3 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 14:35:45 -0500 Subject: [PATCH 20/63] change connection constructor for searchability --- packages/jupyterlab-lsp/src/connection_manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 1adbefec0..c40dd869d 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -259,14 +259,14 @@ namespace Private { language: string, uris: DocumentConnectionManager.IURIs ): Promise { - const connection_module = await import( + const { LSPConnection } = await import( /* webpackChunkName: "jupyter-lsp-connection" */ './connection' ); let connection = _connections.get(language); if (connection == null) { const socket = new WebSocket(uris.socket); - const connection = new connection_module.LSPConnection({ + const connection = new LSPConnection({ languageId: language, serverUri: uris.server, rootUri: uris.base From 273c9b6af29018eb03737542810d83a15df48425 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 16:16:23 -0500 Subject: [PATCH 21/63] remove todo on bash, since they are randomized now --- atest/01_Editor.robot | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/atest/01_Editor.robot b/atest/01_Editor.robot index 9050c9d6e..75f47ac51 100644 --- a/atest/01_Editor.robot +++ b/atest/01_Editor.robot @@ -11,9 +11,7 @@ ${CM CURSORS} css:.CodeMirror-cursors:not([style='visibility: hidden']) *** Test Cases *** Bash - [Documentation] TODO: figure out why the first server is extra flaky - Wait Until Keyword Succeeds 6x 5s Editor Shows Features for Language Bash example.sh Diagnostics=Failed to parse expression - ... Jump to Definition=fib + Editor Shows Features for Language Bash example.sh Diagnostics=Failed to parse expression Jump to Definition=fib CSS ${def} = Set Variable xpath:(//span[contains(@class, 'cm-variable-2')][contains(text(), '--some-var')])[last()] From 74ebb52a92ca5a6008bb074ef16ec8c76dbf0fdb Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 16:47:06 -0500 Subject: [PATCH 22/63] work on behavior of foreign docs, diagnostics and singleton connection_manager --- .../src/adapters/codemirror/cm_adapter.ts | 2 +- .../codemirror/features/diagnostics.ts | 2 +- .../codemirror/features/highlights.ts | 2 +- .../jupyterlab/components/statusbar.tsx | 10 +++---- .../src/adapters/jupyterlab/file_editor.ts | 4 ++- .../src/adapters/jupyterlab/jl_adapter.ts | 28 ++++++++++--------- .../src/adapters/jupyterlab/notebook.ts | 2 +- .../jupyterlab-lsp/src/connection_manager.ts | 22 +++++++++++---- packages/jupyterlab-lsp/src/index.ts | 6 ++-- .../jupyterlab-lsp/src/virtual/console.ts | 2 +- .../jupyterlab-lsp/src/virtual/document.ts | 9 ++++-- .../src/virtual/editors/notebook.ts | 2 +- 12 files changed, 54 insertions(+), 37 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 6d427fa07..4a79166a5 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -6,7 +6,7 @@ import { IRootPosition } from '../../positioning'; import { ILSPFeature } from './feature'; import { IJupyterLabComponentsManager } from '../jupyterlab/jl_adapter'; -const DEBUG = false; +const DEBUG = 0; export class CodeMirrorAdapter { features: Map; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index d48a31b39..dfcbec0d1 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -16,7 +16,7 @@ import { import { VirtualDocument } from '../../../virtual/document'; import { VirtualEditor } from '../../../virtual/editor'; -const DEBUG = false; +const DEBUG = 0; // TODO: settings const default_severity = 2; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 33a3cef04..2bb0453b1 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -5,7 +5,7 @@ import { VirtualDocument } from '../../../virtual/document'; import { IRootPosition } from '../../../positioning'; import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; -const DEBUG = false; +const DEBUG = 0; export class Highlights extends CodeMirrorLSPFeature { name = 'Highlights'; diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx index 4e6ed3e37..1e0fb696a 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx @@ -123,9 +123,9 @@ class LSPPopup extends VDomRenderer { ); let status = ''; - if (connection.isInitialized) { + if (connection?.isInitialized) { status = 'initialized'; - } else if (connection.isConnected) { + } else if (connection?.isConnected) { status = 'connected'; } else { status = 'not connected'; @@ -366,8 +366,8 @@ export namespace LSPStatus { Map > { let data = new Map(); - if (!this.adapter) { - return new Map(); + if (!this.adapter?.virtual_editor) { + return data; } let main_document = this.adapter.virtual_editor.virtual_document; @@ -409,7 +409,7 @@ export namespace LSPStatus { } get detected_languages(): Set { - if (!this.adapter) { + if (!this.adapter?.virtual_editor) { return new Set(); } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts index 011659d9a..376b9270c 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts @@ -12,6 +12,8 @@ import { CodeEditor } from '@jupyterlab/codeeditor'; import { VirtualFileEditor } from '../../virtual/editors/file_editor'; import { DocumentConnectionManager } from '../../connection_manager'; +const DEBUG = 0; + export class FileEditorAdapter extends JupyterLabWidgetAdapter { editor: FileEditor; jumper: FileEditorJumper; @@ -71,7 +73,7 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { ); this.connect_contentChanged_signal(); - console.log('LSP: file ready for connection:', this.path); + DEBUG && console.log('LSP: file ready for connection:', this.path); this.connect_document(this.virtual_editor.virtual_document).catch( console.warn ); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index fdacd0c63..706c6a297 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -33,7 +33,7 @@ import { } from '../../connection_manager'; import { Rename } from '../codemirror/features/rename'; -const DEBUG = false; +const DEBUG = 0; export const lsp_features: Array = [ Completion, @@ -77,7 +77,7 @@ export class StatusMessage { set(message: string, timeout?: number) { this.message = message; this.changed.emit(''); - if (typeof timeout !== 'undefined' && timeout !== -1) { + if (timeout == null && timeout !== -1) { setTimeout(this.cleanup.bind(this), timeout); } } @@ -139,11 +139,11 @@ export abstract class JupyterLabWidgetAdapter 'LSP: connection closed, disconnecting adapter', virtual_document.id_path ); + if (virtual_document !== this.virtual_editor?.virtual_document) { + return; + } this.disconnect_adapter(virtual_document); }); - this.connection_manager.connected.connect((manager, data) => { - this.on_connected(data).catch(console.warn); - }); // register completion connectors this.document_connected.connect(() => this.connect_completion()); @@ -189,7 +189,7 @@ export abstract class JupyterLabWidgetAdapter // but also reloads the connection; used during file rename (or when it was moved) protected reload_connection() { // ignore premature calls (before the editor was initialized) - if (typeof this.virtual_editor === 'undefined') { + if (this.virtual_editor == null) { return; } @@ -207,7 +207,7 @@ export abstract class JupyterLabWidgetAdapter protected on_save_state(context: any, state: DocumentRegistry.SaveState) { // ignore premature calls (before the editor was initialized) - if (typeof this.virtual_editor === 'undefined') { + if (this.virtual_editor == null) { return; } @@ -247,13 +247,13 @@ export abstract class JupyterLabWidgetAdapter } protected async connect_document(virtual_document: VirtualDocument) { - this.connection_manager.connect_document_signals(virtual_document); virtual_document.changed.connect(this.document_changed.bind(this)); - await this.connect(virtual_document).catch(console.warn); - virtual_document.foreign_document_opened.connect((host, context) => { + virtual_document.foreign_document_opened.connect(async (host, context) => { this.connect_document(context.foreign_document).catch(console.warn); }); + + await this.connect(virtual_document).catch(console.warn); } document_changed( @@ -267,14 +267,14 @@ export abstract class JupyterLabWidgetAdapter ); let adapter = this.adapters.get(virtual_document.id_path); - if (typeof connection === 'undefined') { + if (connection == null) { DEBUG && console.log( 'LSP: Skipping document update signal: connection not ready' ); return; } - if (typeof adapter === 'undefined') { + if (adapter == null) { DEBUG && console.log('LSP: Skipping document update signal: adapter not ready'); return; @@ -319,7 +319,7 @@ export abstract class JupyterLabWidgetAdapter private disconnect_adapter(virtual_document: VirtualDocument) { let adapter = this.adapters.get(virtual_document.id_path); this.adapters.delete(virtual_document.id_path); - if (typeof adapter !== 'undefined') { + if (adapter != null) { adapter.remove(); } } @@ -346,6 +346,8 @@ export abstract class JupyterLabWidgetAdapter let connection = await this.connection_manager.connect(options); + await this.on_connected({ virtual_document, connection }); + return { connection, virtual_document diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 4c097e670..27be9da6f 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -16,7 +16,7 @@ import { nbformat } from '@jupyterlab/coreutils'; import ILanguageInfoMetadata = nbformat.ILanguageInfoMetadata; import { DocumentConnectionManager } from '../../connection_manager'; -const DEBUG = false; +const DEBUG = 0; export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index c40dd869d..b597fa2c7 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -4,7 +4,7 @@ import { Signal } from '@phosphor/signaling'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { sleep, until_ready } from './utils'; -const DEBUG = false; +const DEBUG = 0; export interface IDocumentConnectionData { virtual_document: VirtualDocument; @@ -72,19 +72,25 @@ export class DocumentConnectionManager { virtual_document.foreign_document_opened.connect((host, context) => { DEBUG && console.log( - 'LSP: Connecting foreign document: ', + 'LSP: ConnectionManager received foreign document: ', context.foreign_document.id_path ); - this.connect_document_signals(context.foreign_document); }); + virtual_document.foreign_document_closed.connect( (host, { foreign_document }) => { - this.connections.get(foreign_document.id_path).close(); - this.connections.delete(foreign_document.id_path); + const connection = this.connections.get(foreign_document.id_path); + if (connection) { + connection.close(); + this.connections.delete(foreign_document.id_path); + } else { + console.warn('LSP: no connection for', foreign_document); + } this.documents.delete(foreign_document.id_path); this.documents_changed.emit(this.documents); } ); + this.documents.set(virtual_document.id_path, virtual_document); this.documents_changed.emit(this.documents); } @@ -92,8 +98,11 @@ export class DocumentConnectionManager { private async connect_socket( options: ISocketConnectionOptions ): Promise { + DEBUG && console.log('LSP: Connection Socket', options); let { virtual_document, language } = options; + this.connect_document_signals(virtual_document); + const uris = DocumentConnectionManager.solve_uris( virtual_document, language @@ -175,6 +184,7 @@ export class DocumentConnectionManager { } async connect(options: ISocketConnectionOptions) { + DEBUG && console.log('LSP: connection requested', options); let connection = await this.connect_socket(options); connection.on('serverInitialized', capabilities => { @@ -186,7 +196,7 @@ export class DocumentConnectionManager { await until_ready( () => { // @ts-ignore - return connection.isConnected; + return connection.isReady; }, 50, 50 diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index e3f2433a6..fd537699e 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -136,9 +136,9 @@ const plugin: JupyterFrontEndPlugin = { } ); - fileEditorTracker.widgetUpdated.connect((sender, widget) => { - console.log(sender); - console.log(widget); + fileEditorTracker.widgetUpdated.connect((_sender, _widget) => { + // console.log(sender); + // console.log(widget); // TODO? // adapter.remove(); // connection.close(); diff --git a/packages/jupyterlab-lsp/src/virtual/console.ts b/packages/jupyterlab-lsp/src/virtual/console.ts index 0d3c3d157..658de0f03 100644 --- a/packages/jupyterlab-lsp/src/virtual/console.ts +++ b/packages/jupyterlab-lsp/src/virtual/console.ts @@ -1,6 +1,6 @@ import '../../style/console.css'; -const DEBUG = false; +const DEBUG = 0; export abstract class EditorLogConsole { abstract log(...args: any[]): void; diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index 2d20f3427..cf2af7262 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -18,6 +18,8 @@ import { IDocumentInfo } from 'lsp-ws-connection/src'; import { DocumentConnectionManager } from '../connection_manager'; +const DEBUG = 0; + type language = string; interface IVirtualLine { @@ -287,10 +289,11 @@ export class VirtualDocument { false, this ); - this.foreign_document_opened.emit({ + const context: IForeignContext = { foreign_document: document, parent_host: this - }); + }; + this.foreign_document_opened.emit(context); // pass through any future signals document.foreign_document_closed.connect(this.forward_closed_signal); document.foreign_document_opened.connect(this.forward_opened_signal); @@ -578,7 +581,7 @@ export class VirtualDocument { } close_foreign(document: VirtualDocument) { - console.log('LSP: closing document', document); + DEBUG && console.log('LSP: closing document', document); this.foreign_document_closed.emit({ foreign_document: document, parent_host: this diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index de6406327..97ee03e84 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -11,7 +11,7 @@ import { IVirtualPosition } from '../../positioning'; -const DEBUG = false; +const DEBUG = 0; // @ts-ignore class DocDispatcher implements CodeMirror.Doc { From b6df0b7f01c8dc1405f6ca494ad0bf1b66c5e244 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 17:05:10 -0500 Subject: [PATCH 23/63] pyflakes and mypy can both claim the error --- atest/03_Notebook.robot | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/atest/03_Notebook.robot b/atest/03_Notebook.robot index ebdfb755f..6c9ab8dd7 100644 --- a/atest/03_Notebook.robot +++ b/atest/03_Notebook.robot @@ -14,9 +14,10 @@ Python Foregin Extractors Setup Notebook Python Foreign extractors.ipynb - @{diagnostics} = Create List Failed to parse expression undefined name 'valid' (pyflakes) Trailing whitespace is superfluous. (lintr) + # if mypy and pyflakes will fight over `(N|n)ame 'valid'`, just hope for the best + @{diagnostics} = Create List Failed to parse expression ame 'valid' Trailing whitespace is superfluous. (lintr) FOR ${diagnostic} IN @{diagnostics} - Wait Until Page Contains Element css:.cm-lsp-diagnostic[title="${diagnostic}"] timeout=35s + Wait Until Page Contains Element css:.cm-lsp-diagnostic[title*\="${diagnostic}"] timeout=35s Capture Page Screenshot 0x-${diagnostic}.png END Clean Up After Working With File Foreign Extractors.ipynb From bbb50d4c4236b16bcb5e50d7d868bc93b038cbeb Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 17:22:54 -0500 Subject: [PATCH 24/63] start working on unit tests --- packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index 25984ecf0..d2d006191 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -21,7 +21,7 @@ import { nbformat } from '@jupyterlab/coreutils'; import { ICellModel } from '@jupyterlab/cells'; import createNotebook = NBTestUtils.createNotebook; import { CodeMirrorAdapter } from './cm_adapter'; -import { VirtualDocument } from '../../virtual/document'; +import { VirtualDocument, VirtualDocumentInfo } from '../../virtual/document'; interface IFeatureTestEnvironment { host: HTMLElement; @@ -48,6 +48,9 @@ export abstract class FeatureTestEnvironment init() { this.virtual_editor = this.create_virtual_editor(); + this.virtual_editor.virtual_document.document_info = new VirtualDocumentInfo( + this.virtual_editor.virtual_document + ); } abstract create_virtual_editor(): VirtualEditor; From 505c8a6032ef106de2ee9e00deb889a23b70f0e3 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 20:41:29 -0500 Subject: [PATCH 25/63] more work on unit tests (down to 5 failing) --- .../adapters/codemirror/features/rename.ts | 31 ++++++------ .../src/adapters/codemirror/testutils.ts | 5 +- packages/jupyterlab-lsp/src/connection.ts | 49 +++++++++---------- .../jupyterlab-lsp/src/connection_manager.ts | 4 +- .../jupyterlab-lsp/src/virtual/document.ts | 1 + 5 files changed, 43 insertions(+), 47 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts index 33b063de5..393b67e3e 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts @@ -14,7 +14,7 @@ export class Rename extends CodeMirrorLSPFeature { static commands: Array = [ { id: 'rename-symbol', - execute: ({ + execute: async ({ editor, connection, virtual_position, @@ -22,8 +22,8 @@ export class Rename extends CodeMirrorLSPFeature { features }) => { let old_value = document.getTokenAt(virtual_position).string; + const rename_feature = features.get('Rename') as Rename; let handle_failure = (error: any) => { - let rename_feature = features.get('Rename') as Rename; let diagnostics_feature = features.get('Diagnostics') as Diagnostics; let status = ux_workaround_for_rope_limitation( @@ -38,28 +38,29 @@ export class Rename extends CodeMirrorLSPFeature { rename_feature.status_message.set(status, 7.5 * 1000); }; - InputDialog.getText({ + const dialog_value = await InputDialog.getText({ title: 'Rename to', text: old_value, okLabel: 'Rename' - }) - .then(value => { - connection - .rename(virtual_position, document.document_info, value.value) - .catch(handle_failure); - }) - .catch(handle_failure); + }); + + try { + const edit = await connection.rename( + virtual_position, + document.document_info, + dialog_value.value, + false + ); + rename_feature.handleRename(edit); + } catch (err) { + handle_failure(err); + } }, is_enabled: ({ connection }) => connection.isRenameSupported(), label: 'Rename symbol' } ]; - register(): void { - this.connection_handlers.set('renamed', this.handleRename.bind(this)); - super.register(); - } - handleRename(workspaceEdit: lsProtocol.WorkspaceEdit) { this.apply_edit(workspaceEdit) .catch(error => { diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index d2d006191..25984ecf0 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -21,7 +21,7 @@ import { nbformat } from '@jupyterlab/coreutils'; import { ICellModel } from '@jupyterlab/cells'; import createNotebook = NBTestUtils.createNotebook; import { CodeMirrorAdapter } from './cm_adapter'; -import { VirtualDocument, VirtualDocumentInfo } from '../../virtual/document'; +import { VirtualDocument } from '../../virtual/document'; interface IFeatureTestEnvironment { host: HTMLElement; @@ -48,9 +48,6 @@ export abstract class FeatureTestEnvironment init() { this.virtual_editor = this.create_virtual_editor(); - this.virtual_editor.virtual_document.document_info = new VirtualDocumentInfo( - this.virtual_editor.virtual_document - ); } abstract create_virtual_editor(): VirtualEditor; diff --git a/packages/jupyterlab-lsp/src/connection.ts b/packages/jupyterlab-lsp/src/connection.ts index 4c9ccad50..74faa9141 100644 --- a/packages/jupyterlab-lsp/src/connection.ts +++ b/packages/jupyterlab-lsp/src/connection.ts @@ -40,35 +40,34 @@ export class LSPConnection extends LspWsConnection { async rename( location: IPosition, documentInfo: IDocumentInfo, - newName: string - ): Promise { + newName: string, + emit = true + ): Promise { if (!this.isReady || !this.isRenameSupported()) { return; } - return new Promise((resolve, reject) => { - this.connection - .sendRequest('textDocument/rename', { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - }, - newName - } as lsProtocol.RenameParams) - .then( - (result: lsProtocol.WorkspaceEdit | null) => { - this.emit('renamed', result); - resolve(true); - }, - error => { - console.warn(error); - reject(error); - } - ); - }); + const params: lsProtocol.RenameParams = { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + }, + newName + }; + + const edit: lsProtocol.WorkspaceEdit = await this.connection.sendRequest( + 'textDocument/rename', + params + ); + + if (emit) { + this.emit('renamed', edit); + } + + return edit; } public connect(socket: WebSocket): this { diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index b597fa2c7..95afb6eb5 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -1,4 +1,4 @@ -import { VirtualDocument, VirtualDocumentInfo } from './virtual/document'; +import { VirtualDocument } from './virtual/document'; import { LSPConnection } from './connection'; import { Signal } from '@phosphor/signaling'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; @@ -108,8 +108,6 @@ export class DocumentConnectionManager { language ); - virtual_document.document_info = new VirtualDocumentInfo(virtual_document); - const connection = await Private.connection(language, uris); connection.on('error', e => { diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index cf2af7262..cc7a3d19b 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -213,6 +213,7 @@ export class VirtualDocument { this.foreign_document_opened = new Signal(this); this.changed = new Signal(this); this.unused_documents = new Set(); + this.document_info = new VirtualDocumentInfo(this); this.clear(); } From c6f9f3692a109923198d3cf80a7d924498575f5e Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 20:53:53 -0500 Subject: [PATCH 26/63] temporarily ignore jest test fails on azure --- ci/job.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/job.test.yml b/ci/job.test.yml index 72f675319..4e044ed2a 100644 --- a/ci/job.test.yml +++ b/ci/job.test.yml @@ -57,7 +57,7 @@ jobs: - script: ${{ platform.activate }} jupyterlab-lsp && cd dist && python -m pip install jupyter_lsp-$(PY_JLSP_VERSION)-py3-none-any.whl --no-deps displayName: install python wheel - - script: ${{ platform.activate }} jupyterlab-lsp && jlpm test + - script: ${{ platform.activate }} jupyterlab-lsp && jlpm test || echo 'TODO UNCOMMENT THIS' displayName: run frontend unit tests - task: PublishTestResults@2 From 69559b14bc277a33db32ebad37584db65b0185d0 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 21:42:40 -0500 Subject: [PATCH 27/63] try linking in hot lsp-ws --- azure-pipelines.yml | 3 +++ ci/job.test.yml | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c81433ae2..69221a921 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,6 +22,9 @@ variables: THIRD_PARTY_LABEXTENSIONS: >- @krassowski/jupyterlab_go_to_definition + LINKED_EXTENSIONS: >- + packages/lsp-ws-connection + jobs: - template: ci/job.lint.yml - template: ci/job.test.yml diff --git a/ci/job.test.yml b/ci/job.test.yml index 4e044ed2a..9ed8fe12d 100644 --- a/ci/job.test.yml +++ b/ci/job.test.yml @@ -20,6 +20,7 @@ parameters: spec: '>=3.8,<3.9.0a0' lab: '>=1.2.4,<1.3.0a0' env_update: conda env update -n jupyterlab-lsp --file env-test.yml --quiet + lab_link: jupyter labextension link --no-build $(LINKED_EXTENSIONS) lab_ext: jupyter labextension install --no-build $(THIRD_PARTY_LABEXTENSIONS) $(FIRST_PARTY_LABEXTENSIONS) jobs: @@ -79,6 +80,9 @@ jobs: - script: ${{ platform.activate }} jupyterlab-lsp && python scripts/utest.py --test-run-title="Pytest ${{ platform.name }}${{ python.name }}" displayName: run python tests + - script: ${{ platform.activate }} jupyterlab-lsp && ${{ parameters.lab_link }} || ${{ parameters.lab_link }} || ${{ parameters.lab_link }} + displayName: install support packages + - script: ${{ platform.activate }} jupyterlab-lsp && ${{ parameters.lab_ext }} || ${{ parameters.lab_ext }} || ${{ parameters.lab_ext }} displayName: install labextensions From 79095e673d3f4e7cbaedbeb8c09eaf112bf6c857 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 20 Jan 2020 22:30:19 -0500 Subject: [PATCH 28/63] try entrypoints instead of pkg_resources --- atest/03_Notebook.robot | 2 +- atest/Keywords.robot | 2 +- ci/env-test.yml.in | 2 ++ environment.yml | 2 ++ py_src/jupyter_lsp/manager.py | 16 +++++++++------- setup.cfg | 3 +++ 6 files changed, 18 insertions(+), 9 deletions(-) diff --git a/atest/03_Notebook.robot b/atest/03_Notebook.robot index 6c9ab8dd7..ce4ca47eb 100644 --- a/atest/03_Notebook.robot +++ b/atest/03_Notebook.robot @@ -1,7 +1,7 @@ *** Settings *** Suite Setup Setup Suite For Screenshots notebook -Resource Keywords.robot Test Setup Try to Close All Tabs +Resource Keywords.robot *** Test Cases *** Python diff --git a/atest/Keywords.robot b/atest/Keywords.robot index d952f8c37..e7a928ee3 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -72,7 +72,7 @@ Close All Tabs Accept Default Dialog Option Try to Close All Tabs - Wait Until Keyword Succeeds 5x 50ms Close All Tabs + Wait Until Keyword Succeeds 5x 50ms Close All Tabs Reset Application State Try to Close All Tabs diff --git a/ci/env-test.yml.in b/ci/env-test.yml.in index 202f25f06..512850bd3 100644 --- a/ci/env-test.yml.in +++ b/ci/env-test.yml.in @@ -8,11 +8,13 @@ dependencies: # runtime dependencies - python {python} - jupyterlab {lab} + - importlib_metadata # build dependencies - nodejs # for python language server (and development) - flake8 >=3.5 - python-language-server + - zipp # for R language server and kernel - r - r-irkernel diff --git a/environment.yml b/environment.yml index 4f2313397..3c4377b40 100644 --- a/environment.yml +++ b/environment.yml @@ -20,6 +20,8 @@ dependencies: - pylint - python-language-server - ruamel_yaml + - zipp + - importlib_metadata - pip: # not-yet-appearing-in-conda-forge - pyls-black - pyls-isort diff --git a/py_src/jupyter_lsp/manager.py b/py_src/jupyter_lsp/manager.py index 273c15946..320401043 100644 --- a/py_src/jupyter_lsp/manager.py +++ b/py_src/jupyter_lsp/manager.py @@ -2,7 +2,7 @@ """ from typing import Dict, Text, Tuple -import pkg_resources +import entrypoints from notebook.transutils import _ from traitlets import Bool, Dict as Dict_, Instance, List as List_, default @@ -107,11 +107,13 @@ def init_listeners(self): for scope, trt_ep in scopes.items(): listeners, entry_point = trt_ep - for ept in pkg_resources.iter_entry_points(entry_point): # pragma: no cover + for ep_name, ept in entrypoints.get_group_named( + entry_point + ).items(): # pragma: no cover try: - listeners.append(entry_point.load()) + listeners.append(ept.load()) except Exception as err: - self.log.warning("Failed to load entry point %s: %s", ept, err) + self.log.warning("Failed to load entry point %s: %s", ep_name, err) for listener in listeners: self.__class__.register_message_listener(scope=scope.value)(listener) @@ -155,17 +157,17 @@ def _autodetect_language_servers(self): entry_points = [] try: - entry_points = list(pkg_resources.iter_entry_points(EP_SPEC_V1)) + entry_points = entrypoints.get_group_named(EP_SPEC_V1) except Exception: # pragma: no cover self.log.exception("Failed to load entry_points") - for ep in entry_points: + for ep_name, ep in entry_points.items(): try: spec_finder = ep.load() # type: SpecMaker except Exception as err: # pragma: no cover self.log.warn( _("Failed to load language server spec finder `{}`: \n{}").format( - ep.name, err + ep_name, err ) ) continue diff --git a/setup.cfg b/setup.cfg index 29ca453a5..66cfa2bcf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -116,3 +116,6 @@ ignore_errors = True [mypy-pytest_azurepipelines] ignore_missing_imports = True + +[mypy-entrypoints] +ignore_missing_imports = True From d9f2605b50647acaf8bed23f73d06e0af8845558 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 19:58:38 -0500 Subject: [PATCH 29/63] roll back zipp stuff --- ci/env-test.yml.in | 2 -- environment.yml | 2 -- 2 files changed, 4 deletions(-) diff --git a/ci/env-test.yml.in b/ci/env-test.yml.in index 512850bd3..202f25f06 100644 --- a/ci/env-test.yml.in +++ b/ci/env-test.yml.in @@ -8,13 +8,11 @@ dependencies: # runtime dependencies - python {python} - jupyterlab {lab} - - importlib_metadata # build dependencies - nodejs # for python language server (and development) - flake8 >=3.5 - python-language-server - - zipp # for R language server and kernel - r - r-irkernel diff --git a/environment.yml b/environment.yml index 3c4377b40..4f2313397 100644 --- a/environment.yml +++ b/environment.yml @@ -20,8 +20,6 @@ dependencies: - pylint - python-language-server - ruamel_yaml - - zipp - - importlib_metadata - pip: # not-yet-appearing-in-conda-forge - pyls-black - pyls-isort From 9c225e69e0a3150517b1c933158e4048e655a514 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 20:48:08 -0500 Subject: [PATCH 30/63] activeCell can be null --- packages/jupyterlab-lsp/src/virtual/editors/notebook.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index 97ee03e84..189cd76d8 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -42,6 +42,9 @@ class DocDispatcher implements CodeMirror.Doc { getCursor(start?: string): CodeMirror.Position { let cell = this.virtual_editor.notebook.activeCell; + if (cell == null) { + return; + } let active_editor = cell.editor as CodeMirrorEditor; let cursor = active_editor.editor .getDoc() @@ -356,6 +359,9 @@ export class VirtualEditorForNotebook extends VirtualEditor { } if (monitor_for_new_blocks) { this.notebook.activeCellChanged.connect((notebook, cell) => { + if (cell == null) { + return; + } let cm_editor = (cell.editor as CodeMirrorEditor).editor; if (!cells_with_handlers.has(cell) && cell.model.type === 'code') { callback(cm_editor); From a995e11516009802e59e6c142f70a3bf22272a6e Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 21:20:15 -0500 Subject: [PATCH 31/63] fix up unit tests through PageConfig, other means --- .../src/adapters/codemirror/feature.spec.ts | 16 +++--- .../src/adapters/codemirror/feature.ts | 2 + .../codemirror/features/rename.spec.ts | 8 ++- .../adapters/codemirror/features/rename.ts | 54 +++++++++++-------- .../src/adapters/codemirror/testutils.ts | 4 +- 5 files changed, 51 insertions(+), 33 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.spec.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.spec.ts index f7f464aef..5808518bc 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.spec.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.spec.ts @@ -21,6 +21,7 @@ import { nbformat } from '@jupyterlab/coreutils'; import { language_specific_overrides } from '../../magics/defaults'; import { foreign_code_extractors } from '../../extractors/defaults'; import { NotebookModel } from '@jupyterlab/notebook'; +import { PageConfig } from '@jupyterlab/coreutils'; const js_fib_code = `function fib(n) { return n<2?n:fib(n-1)+fib(n-2); @@ -94,6 +95,8 @@ const js_partial_edits = [ ]; describe('Feature', () => { + PageConfig.setOption('rootUri', 'file://'); + describe('apply_edit()', () => { class EditApplyingFeature extends CodeMirrorLSPFeature { name = 'EditApplyingFeature'; @@ -248,9 +251,9 @@ describe('Feature', () => { 'def a_function_2():\n pass\n\n\nx = a_function_2()\n'; expect(main_document.value).to.be.equal(old_virtual_source); - let result = await feature.do_apply_edit({ + let outcome = await feature.do_apply_edit({ changes: { - ['file://' + environment.path()]: [ + [main_document.document_info.uri]: [ { range: { start: { line: 0, character: 0 }, @@ -261,6 +264,7 @@ describe('Feature', () => { ] } }); + await synchronizeContent(); let value = main_document.value; @@ -274,9 +278,9 @@ describe('Feature', () => { ); expect(code_cells[1]).to.have.property('source', 'x = a_function_2()'); - expect(result.appliedChanges).to.be.equal(null); - expect(result.wasGranular).to.be.equal(false); - expect(result.modifiedCells).to.be.equal(2); + expect(outcome.appliedChanges).to.be.equal(null); + expect(outcome.wasGranular).to.be.equal(false); + expect(outcome.modifiedCells).to.be.equal(2); }); it('handles IPython magics', async () => { @@ -317,7 +321,7 @@ print(x)""") await feature.do_apply_edit({ changes: { - ['file://' + environment.path()]: [ + [main_document.document_info.uri]: [ { range: { start: { line: 0, character: 0 }, diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index b5c0ed375..a0f914721 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -279,6 +279,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { workspaceEdit: lsProtocol.WorkspaceEdit ): Promise { let current_uri = this.virtual_document.document_info.uri; + // Specs: documentChanges are preferred over changes let changes = workspaceEdit.documentChanges ? workspaceEdit.documentChanges.map( @@ -292,6 +293,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { for (let change of changes) { let uri = change.textDocument.uri; + if ( decodeURI(uri) !== decodeURI(current_uri) && decodeURI(uri) !== '/' + decodeURI(current_uri) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.spec.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.spec.ts index 9ad7dcf28..8e86c25e0 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.spec.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.spec.ts @@ -2,6 +2,7 @@ import { expect } from 'chai'; import { Rename } from './rename'; import { FileEditorFeatureTestEnvironment } from '../testutils'; import * as lsProtocol from 'vscode-languageserver-protocol'; +import { PageConfig } from '@jupyterlab/coreutils'; describe('Rename', () => { let env: FileEditorFeatureTestEnvironment; @@ -15,12 +16,14 @@ describe('Rename', () => { beforeEach(() => (feature = env.init_feature(Rename))); afterEach(() => env.dispose_feature(feature)); + PageConfig.setOption('rootUri', 'file://'); + it('renders inspections', async () => { env.ce_editor.model.value.text = 'x = 1\n'; await env.virtual_editor.update_documents(); let main_document = env.virtual_editor.virtual_document; - feature.handleRename({ + await feature.handleRename({ changes: { ['file://' + env.path()]: [ { @@ -33,10 +36,11 @@ describe('Rename', () => { ] } }); + await env.virtual_editor.update_documents(); - expect(main_document.value).to.be.equal('y = 1\n'); expect(feature.status_message.message).to.be.equal('Renamed a variable'); + expect(main_document.value).to.be.equal('y = 1\n'); }); }); }); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts index 393b67e3e..e0af38dff 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts @@ -51,9 +51,9 @@ export class Rename extends CodeMirrorLSPFeature { dialog_value.value, false ); - rename_feature.handleRename(edit); - } catch (err) { - handle_failure(err); + await rename_feature.handleRename(edit); + } catch (error) { + handle_failure(error); } }, is_enabled: ({ connection }) => connection.isRenameSupported(), @@ -61,29 +61,37 @@ export class Rename extends CodeMirrorLSPFeature { } ]; - handleRename(workspaceEdit: lsProtocol.WorkspaceEdit) { - this.apply_edit(workspaceEdit) - .catch(error => { - this.status_message.set(`Rename failed: ${error}`); - }) - .then((outcome: IEditOutcome) => { - let status: string; + async handleRename(workspaceEdit: lsProtocol.WorkspaceEdit) { + let outcome: IEditOutcome; - if (outcome.wasGranular) { - status = `Renamed a variable in ${outcome.appliedChanges} places`; - } else if (this.virtual_editor.has_cells) { - status = `Renamed a variable in ${outcome.modifiedCells} cells`; - } else { - status = `Renamed a variable`; - } + try { + outcome = await this.apply_edit(workspaceEdit); + } catch (error) { + this.status_message.set(`Rename failed: ${error}`); + return outcome; + } - if (outcome.errors.length !== 0) { - status += ` with errors: ${outcome.errors}`; - } + try { + let status: string; - this.status_message.set(status, 5 * 1000); - }) - .catch(console.warn); + if (outcome.wasGranular) { + status = `Renamed a variable in ${outcome.appliedChanges} places`; + } else if (this.virtual_editor.has_cells) { + status = `Renamed a variable in ${outcome.modifiedCells} cells`; + } else { + status = `Renamed a variable`; + } + + if (outcome.errors.length !== 0) { + status += ` with errors: ${outcome.errors}`; + } + + this.status_message.set(status, 5 * 1000); + } catch (error) { + console.warn(error); + } + + return outcome; } } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index 25984ecf0..a891f7dbd 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -84,8 +84,8 @@ export abstract class FeatureTestEnvironment public create_dummy_connection() { return new LSPConnection({ languageId: this.language(), - serverUri: '', - rootUri: '/' + serverUri: 'ws://localhost:8080', + rootUri: 'file:///unit-test' }); } From 71fc0e700652406124c048c4e876413ce391dccd Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 21:21:16 -0500 Subject: [PATCH 32/63] uncomment unit tests --- ci/job.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/job.test.yml b/ci/job.test.yml index 9ed8fe12d..57f71ac1a 100644 --- a/ci/job.test.yml +++ b/ci/job.test.yml @@ -58,7 +58,7 @@ jobs: - script: ${{ platform.activate }} jupyterlab-lsp && cd dist && python -m pip install jupyter_lsp-$(PY_JLSP_VERSION)-py3-none-any.whl --no-deps displayName: install python wheel - - script: ${{ platform.activate }} jupyterlab-lsp && jlpm test || echo 'TODO UNCOMMENT THIS' + - script: ${{ platform.activate }} jupyterlab-lsp && jlpm test' displayName: run frontend unit tests - task: PublishTestResults@2 From 4d5b1124eadd6512a2d0443df0cf2a019e3d85a7 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 21:33:56 -0500 Subject: [PATCH 33/63] trailing quote --- ci/job.test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ci/job.test.yml b/ci/job.test.yml index 57f71ac1a..0084853a9 100644 --- a/ci/job.test.yml +++ b/ci/job.test.yml @@ -58,7 +58,7 @@ jobs: - script: ${{ platform.activate }} jupyterlab-lsp && cd dist && python -m pip install jupyter_lsp-$(PY_JLSP_VERSION)-py3-none-any.whl --no-deps displayName: install python wheel - - script: ${{ platform.activate }} jupyterlab-lsp && jlpm test' + - script: ${{ platform.activate }} jupyterlab-lsp && jlpm test displayName: run frontend unit tests - task: PublishTestResults@2 From 70d2d397c5333df6e27eb7a87f8042c32491edb4 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 21:50:55 -0500 Subject: [PATCH 34/63] hide some more warnings from unready connections --- .../jupyterlab-lsp/src/command_manager.ts | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/jupyterlab-lsp/src/command_manager.ts b/packages/jupyterlab-lsp/src/command_manager.ts index a33524d60..a1612b26f 100644 --- a/packages/jupyterlab-lsp/src/command_manager.ts +++ b/packages/jupyterlab-lsp/src/command_manager.ts @@ -70,7 +70,7 @@ abstract class LSPCommandManager { } protected should_attach(command: IFeatureCommand) { - if (typeof command.attach_to === 'undefined') { + if (command.attach_to == null) { return true; } return command.attach_to.has(this.entry_point); @@ -166,9 +166,9 @@ export abstract class ContextCommandManager extends LSPCommandManager { try { let context = this.get_context(); return ( - context !== null && + context != null && this.current_adapter && - context.connection && + context.connection?.isReady && command.is_enabled(context) ); } catch (e) { @@ -179,18 +179,12 @@ export abstract class ContextCommandManager extends LSPCommandManager { protected get_rank(command: IFeatureCommand): number { let is_relative = - typeof command.is_rank_relative === 'undefined' - ? true - : command.is_rank_relative; - if ( - is_relative && - typeof this.rank_group !== 'undefined' && - this.rank_group_size - ) { - let relative = typeof command.rank !== 'undefined' ? command.rank : 0; + command.is_rank_relative == null ? true : command.is_rank_relative; + if (is_relative && this.rank_group != null && this.rank_group_size) { + let relative = command.rank != null ? command.rank : 0; return this.rank_group + relative / this.rank_group_size; } else { - return typeof command.rank !== 'undefined' ? command.rank : Infinity; + return command.rank != null ? command.rank : Infinity; } } From 5ee14272b93a27720fe6eafef46764e55ce34c38 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 23:04:23 -0500 Subject: [PATCH 35/63] refactor goto to use promises --- .../adapters/codemirror/features/jump_to.ts | 74 +++++++++----- .../jupyterlab/components/completion.ts | 11 +-- packages/jupyterlab-lsp/src/connection.ts | 49 ---------- packages/lsp-ws-connection/src/types.ts | 13 ++- .../lsp-ws-connection/src/ws-connection.ts | 96 ++++++++++--------- 5 files changed, 115 insertions(+), 128 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts index 55fcfb6cb..57af652dd 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts @@ -1,34 +1,41 @@ import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; -import * as lsProtocol from 'vscode-languageserver-protocol'; import { PositionConverter } from '../../../converter'; import { IVirtualPosition } from '../../../positioning'; import { uri_to_contents_path, uris_equal } from '../../../utils'; +import { AnyLocation } from 'lsp-ws-connection/src/types'; + +const DEBUG = 0; export class JumpToDefinition extends CodeMirrorLSPFeature { name = 'JumpToDefinition'; static commands: Array = [ { id: 'jump-to-definition', - execute: ({ connection, virtual_position, document }) => - connection.getDefinition(virtual_position, document.document_info), + execute: async ({ connection, virtual_position, document, features }) => { + const jump_feature = features.get( + 'JumpToDefinition' + ) as JumpToDefinition; + const targets = await connection.getDefinition( + virtual_position, + document.document_info, + false + ); + await jump_feature.handle_jump(targets, document.document_info.uri); + }, is_enabled: ({ connection }) => connection.isDefinitionSupported(), label: 'Jump to definition' } ]; - register(): void { - this.connection_handlers.set('goTo', this.handle_jump.bind(this)); - super.register(); - } - get jumper() { return this.jupyterlab_components.jumper; } - async handle_jump( - location_or_locations: lsProtocol.Location | lsProtocol.Location[], - document_uri: string - ) { + get_uri_and_range(location_or_locations: AnyLocation) { + if (location_or_locations == null) { + DEBUG && console.log('No jump targets found'); + return; + } // some language servers appear to return a single object const locations = Array.isArray(location_or_locations) ? location_or_locations @@ -38,29 +45,49 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { // (like when there are multiple definitions or usages) // could use the showHints() or completion frontend as a reference if (locations.length === 0) { - console.log('No jump targets found'); return; } - console.log('Will jump to the first of suggested locations:', locations); - let location = locations[0]; + DEBUG && + console.log('Will jump to the first of suggested locations:', locations); + + const location_or_link = locations[0]; + + if ('targetUri' in location_or_link) { + return { + uri: decodeURI(location_or_link.targetUri), + range: location_or_link.targetRange + }; + } else if ('uri' in location_or_link) { + return { + uri: decodeURI(location_or_link.uri), + range: location_or_link.range + }; + } + } + + async handle_jump(location_or_locations: AnyLocation, document_uri: string) { + const target_info = this.get_uri_and_range(location_or_locations); + + if (target_info == null) { + DEBUG && console.log('No jump targets found'); + } - let uri: string = decodeURI(location.uri); - let current_uri = document_uri; + let { uri, range } = target_info; let virtual_position = PositionConverter.lsp_to_cm( - location.range.start + range.start ) as IVirtualPosition; - if (uris_equal(uri, current_uri)) { + if (uris_equal(uri, document_uri)) { let editor_index = this.virtual_editor.get_editor_index(virtual_position); // if in current file, transform from the position within virtual document to the editor position: let editor_position = this.virtual_editor.transform_virtual_to_editor( virtual_position ); let editor_position_ce = PositionConverter.cm_to_ce(editor_position); - console.log(`Jumping to ${editor_index}th editor of ${uri}`); - console.log('Jump target within editor:', editor_position_ce); + DEBUG && console.log(`Jumping to ${editor_index}th editor of ${uri}`); + DEBUG && console.log('Jump target within editor:', editor_position_ce); this.jumper.jump({ token: { offset: this.jumper.getOffset(editor_position_ce, editor_index), @@ -71,8 +98,9 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { } else { // otherwise there is no virtual document and we expect the returned position to be source position: let source_position_ce = PositionConverter.cm_to_ce(virtual_position); - console.log(`Jumping to external file: ${uri}`); - console.log('Jump target (source location):', source_position_ce); + DEBUG && console.log(`Jumping to external file: ${uri}`); + DEBUG && + console.log('Jump target (source location):', source_position_ce); // can it be resolved vs our guessed server root? const contents_path = uri_to_contents_path(uri); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index c036eed4d..20f7d2d2d 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -196,9 +196,8 @@ export class LSPConnector extends DataConnector< console.log('[LSP][Completer] Token:', token); - let completion_items: lsProtocol.CompletionItem[] = []; - await connection - .getCompletion( + let completion_items = + ((await connection.getCompletion( cursor, { start, @@ -206,12 +205,10 @@ export class LSPConnector extends DataConnector< text: token.value }, document.document_info, + false, typed_character, this.trigger_kind - ) - .then(items => { - completion_items = items || []; - }); + )) || []) as lsProtocol.CompletionItem[]; let prefix = token.value.slice(0, position_in_token + 1); diff --git a/packages/jupyterlab-lsp/src/connection.ts b/packages/jupyterlab-lsp/src/connection.ts index 74faa9141..6e7fbe5ee 100644 --- a/packages/jupyterlab-lsp/src/connection.ts +++ b/packages/jupyterlab-lsp/src/connection.ts @@ -6,11 +6,9 @@ import * as lsProtocol from 'vscode-languageserver-protocol'; import { ILspOptions, IPosition, - ITokenInfo, LspWsConnection, IDocumentInfo } from 'lsp-ws-connection'; -import { CompletionTriggerKind } from './lsp'; import { until_ready } from './utils'; interface ILSPOptions extends ILspOptions {} @@ -119,51 +117,4 @@ export class LSPConnection extends LspWsConnection { ); documentInfo.version++; } - - public async getCompletion( - location: IPosition, - token: ITokenInfo, - documentInfo: IDocumentInfo, - triggerCharacter: string, - triggerKind: CompletionTriggerKind - ): Promise { - if (!this.isConnected) { - return; - } - if ( - !(this.serverCapabilities && this.serverCapabilities.completionProvider) - ) { - return; - } - - return new Promise(resolve => { - this.connection - .sendRequest('textDocument/completion', { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - }, - context: { - triggerKind: triggerKind, - triggerCharacter - } - } as lsProtocol.CompletionParams) - .then( - ( - params: - | lsProtocol.CompletionList - | lsProtocol.CompletionItem[] - | null - ) => { - if (params) { - params = 'items' in params ? params.items : params; - } - resolve(params as lsProtocol.CompletionItem[]); - } - ); - }); - } } diff --git a/packages/lsp-ws-connection/src/types.ts b/packages/lsp-ws-connection/src/types.ts index 0987d11cb..7bee973be 100644 --- a/packages/lsp-ws-connection/src/types.ts +++ b/packages/lsp-ws-connection/src/types.ts @@ -1,5 +1,4 @@ import * as lsProtocol from 'vscode-languageserver-protocol'; -import { Location, LocationLink } from 'vscode-languageserver-protocol'; export interface IPosition { line: number; @@ -19,6 +18,12 @@ export interface IDocumentInfo { languageId: string; } +export type AnyLocation = + | lsProtocol.Location + | lsProtocol.Location[] + | lsProtocol.LocationLink[] + | null; + type ConnectionEvent = | 'completion' | 'completionResolved' @@ -60,10 +65,7 @@ export interface ILspConnection { ): void; on( event: 'goTo', - callback: ( - location: Location | Location[] | LocationLink[] | null, - documentUri: string - ) => void + callback: (location: AnyLocation, documentUri: string) => void ): void; on( event: 'rename', @@ -103,6 +105,7 @@ export interface ILspConnection { location: IPosition, token: ITokenInfo, documentInfo: IDocumentInfo, + emit?: boolean, triggerCharacter?: string, triggerKind?: lsProtocol.CompletionTriggerKind ): void; diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index b17519b33..7292a940f 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -11,7 +11,8 @@ import { ILspOptions, IPosition, ITokenInfo, - IDocumentInfo + IDocumentInfo, + AnyLocation } from './types'; /** @@ -285,10 +286,11 @@ export class LspWsConnection extends events.EventEmitter }); } - public getCompletion( + public async getCompletion( location: IPosition, token: ITokenInfo, documentInfo: IDocumentInfo, + emit = true, triggerCharacter?: string, triggerKind?: protocol.CompletionTriggerKind ) { @@ -296,30 +298,28 @@ export class LspWsConnection extends events.EventEmitter return; } - this.connection - .sendRequest( - 'textDocument/completion', - { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - }, - context: { - triggerKind: triggerKind || protocol.CompletionTriggerKind.Invoked, - triggerCharacter - } - } as protocol.CompletionParams - ) - .then(params => { - if (!params) { - this.emit('completion', params); - return; - } - this.emit('completion', 'items' in params ? params.items : params); - }); + const params: protocol.CompletionParams = { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + }, + context: { + triggerKind: triggerKind || protocol.CompletionTriggerKind.Invoked, + triggerCharacter + } + }; + + const items = await this.connection.sendRequest< + protocol.CompletionList | protocol.CompletionItem[] + >('textDocument/completion', params); + + if (emit) { + this.emit('completion', items && 'items' in items ? items.items : items); + } + return items; } public getDetailedCompletion(completionItem: protocol.CompletionItem) { @@ -402,27 +402,35 @@ export class LspWsConnection extends events.EventEmitter * Request a link to the definition of the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getDefinition(location: IPosition, documentInfo: IDocumentInfo) { - if (!this.isReady || !this.isDefinitionSupported()) { + public async getDefinition( + location: IPosition, + documentInfo: IDocumentInfo, + emit = true + ) { + if (!(this.isReady && this.isDefinitionSupported())) { return; } - this.connection - .sendRequest( - 'textDocument/definition', - { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - } - } as protocol.TextDocumentPositionParams - ) - .then(result => { - this.emit('goTo', result, documentInfo.uri); - }); + const params: protocol.TextDocumentPositionParams = { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + } + }; + + const targets = await this.connection.sendRequest( + 'textDocument/definition', + params + ); + + if (emit) { + this.emit('goTo', targets, documentInfo.uri); + } + + return targets; } /** From 949407db8c38a6c5ea3666e1c45b2a0bce47fb00 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 23:16:30 -0500 Subject: [PATCH 36/63] refactor signature to use promises --- .../adapters/codemirror/features/signature.ts | 51 +++++++++---------- .../jupyterlab/components/completion.ts | 25 +++++---- .../src/test/connection.test.ts | 2 +- .../lsp-ws-connection/src/ws-connection.ts | 41 +++++++++------ 4 files changed, 64 insertions(+), 55 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts index 6d1e90897..a0cb4d4e5 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts @@ -8,11 +8,6 @@ export class Signature extends CodeMirrorLSPFeature { protected signature_character: IRootPosition; protected _signatureCharacters: string[]; - register(): void { - this.connection_handlers.set('signature', this.handleSignature.bind(this)); - super.register(); - } - protected get_markup_for_signature_help( response: lsProtocol.SignatureHelp, language: string @@ -117,33 +112,37 @@ export class Signature extends CodeMirrorLSPFeature { } get signatureCharacters() { - if ( - typeof this._signatureCharacters === 'undefined' || - !this._signatureCharacters.length - ) { + if (!this._signatureCharacters?.length) { this._signatureCharacters = this.connection.getLanguageSignatureCharacters(); } return this._signatureCharacters; } - afterChange( - change: CodeMirror.EditorChange, - root_position: IRootPosition - ): void { + afterChange(change: CodeMirror.EditorChange, root_position: IRootPosition) { let last_character = this.extract_last_character(change); - if (this.signatureCharacters.indexOf(last_character) > -1) { - this.signature_character = root_position; - let virtual_position = this.virtual_editor.root_position_to_virtual_position( - root_position - ); - this.virtual_editor.console.log( - 'Signature will be requested for', - virtual_position - ); - this.connection.getSignatureHelp( - virtual_position, - this.virtual_document.document_info - ); + + if (this.signatureCharacters.indexOf(last_character) === -1) { + return; } + + this.signature_character = root_position; + + let virtual_position = this.virtual_editor.root_position_to_virtual_position( + root_position + ); + + this.virtual_editor.console.log( + 'Signature will be requested for', + virtual_position + ); + + this.connection + .getSignatureHelp( + virtual_position, + this.virtual_document.document_info, + false + ) + .then(this.handleSignature) + .catch(console.warn); } } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index 20f7d2d2d..f590b8ea3 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -196,19 +196,18 @@ export class LSPConnector extends DataConnector< console.log('[LSP][Completer] Token:', token); - let completion_items = - ((await connection.getCompletion( - cursor, - { - start, - end, - text: token.value - }, - document.document_info, - false, - typed_character, - this.trigger_kind - )) || []) as lsProtocol.CompletionItem[]; + let completion_items = ((await connection.getCompletion( + cursor, + { + start, + end, + text: token.value + }, + document.document_info, + false, + typed_character, + this.trigger_kind + )) || []) as lsProtocol.CompletionItem[]; let prefix = token.value.slice(0, position_in_token + 1); diff --git a/packages/lsp-ws-connection/src/test/connection.test.ts b/packages/lsp-ws-connection/src/test/connection.test.ts index 9f12634ed..6fdb6948a 100644 --- a/packages/lsp-ws-connection/src/test/connection.test.ts +++ b/packages/lsp-ws-connection/src/test/connection.test.ts @@ -379,7 +379,7 @@ describe('LspWsConnection', () => { // 2. After receiving capabilities from the server, we will send a completion mockSocket.send.onSecondCall().callsFake(str => { - connection.getCompletion( + void connection.getCompletion( { line: 1, ch: 8 diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 7292a940f..2f6c1dc0b 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -336,8 +336,12 @@ export class LspWsConnection extends events.EventEmitter }); } - public getSignatureHelp(location: IPosition, documentInfo: IDocumentInfo) { - if (!this.isReady || !this.serverCapabilities?.signatureHelpProvider) { + public async getSignatureHelp( + location: IPosition, + documentInfo: IDocumentInfo, + emit = true + ) { + if (!(this.isReady && this.serverCapabilities?.signatureHelpProvider)) { return; } @@ -352,19 +356,26 @@ export class LspWsConnection extends events.EventEmitter return; } - this.connection - .sendRequest('textDocument/signatureHelp', { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - } - } as protocol.TextDocumentPositionParams) - .then(params => { - this.emit('signature', params); - }); + const params: protocol.TextDocumentPositionParams = { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + } + }; + + const help = await this.connection.sendRequest( + 'textDocument/signatureHelp', + params + ); + + if (emit) { + this.emit('signature', help, documentInfo.uri); + } + + return help; } /** From 1e03f437c289b61812dc146fa3b0f7ee536e6a42 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Tue, 21 Jan 2020 23:38:57 -0500 Subject: [PATCH 37/63] more work on promises for signature, hover --- .../src/adapters/codemirror/features/hover.ts | 25 +++++++---- .../adapters/codemirror/features/signature.ts | 2 +- .../src/test/connection.test.ts | 2 +- .../lsp-ws-connection/src/ws-connection.ts | 42 ++++++++++++------- 4 files changed, 45 insertions(+), 26 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index 198399731..bc81f97ef 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -21,7 +21,7 @@ export class Hover extends CodeMirrorLSPFeature { protected hover_marker: CodeMirror.TextMarker; private virtual_position: IVirtualPosition; - private debounced_get_hover: Debouncer; + private debounced_get_hover: Debouncer>; register(): void { this.wrapper_handlers.set('mousemove', this.handleMouseOver.bind(this)); @@ -46,12 +46,18 @@ export class Hover extends CodeMirrorLSPFeature { }); this.connection_handlers.set('hover', this.handleHover.bind(this)); // TODO: make the debounce rate configurable - this.debounced_get_hover = new Debouncer(() => { - this.connection.getHoverTooltip( - this.virtual_position, - this.virtual_document.document_info - ); - }, 50); + this.debounced_get_hover = new Debouncer>( + async () => { + const hover = await this.connection.getHoverTooltip( + this.virtual_position, + this.virtual_document.document_info, + false + ); + console.log('NRB: hover', hover); + return hover; + }, + 50 + ); super.register(); } @@ -141,7 +147,7 @@ export class Hover extends CodeMirrorLSPFeature { return target.closest('.CodeMirror-sizer') !== null; } - public _handleMouseOver(event: MouseEvent) { + public async _handleMouseOver(event: MouseEvent) { // currently the events are coming from notebook panel; ideally these would be connected to individual cells, // (only cells with code) instead, but this is more complex to implement right. In any case filtering // is needed to determine in hovered character belongs to this virtual document @@ -176,7 +182,8 @@ export class Hover extends CodeMirrorLSPFeature { if (!is_equal(root_position, this.hover_character)) { this.hover_character = root_position; this.virtual_position = virtual_position; - void this.debounced_get_hover.invoke(); + const hover = await this.debounced_get_hover.invoke(); + this.handleHover(hover, this.virtual_document.document_info.uri); } } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts index a0cb4d4e5..8edd4f1c3 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts @@ -142,7 +142,7 @@ export class Signature extends CodeMirrorLSPFeature { this.virtual_document.document_info, false ) - .then(this.handleSignature) + .then(help => this.handleSignature(help)) .catch(console.warn); } } diff --git a/packages/lsp-ws-connection/src/test/connection.test.ts b/packages/lsp-ws-connection/src/test/connection.test.ts index 6fdb6948a..ce2757fd2 100644 --- a/packages/lsp-ws-connection/src/test/connection.test.ts +++ b/packages/lsp-ws-connection/src/test/connection.test.ts @@ -284,7 +284,7 @@ describe('LspWsConnection', () => { // 2. After receiving capabilities from the server, we will send a hover mockSocket.send.onSecondCall().callsFake(str => { - connection.getHoverTooltip( + void connection.getHoverTooltip( { line: 1, ch: 0 diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 2f6c1dc0b..6e5ece45f 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -267,23 +267,35 @@ export class LspWsConnection extends events.EventEmitter ); } - public getHoverTooltip(location: IPosition, documentInfo: IDocumentInfo) { - if (!this.isInitialized) { + public async getHoverTooltip( + location: IPosition, + documentInfo: IDocumentInfo, + emit = true + ) { + if (!this.isReady) { return; } - this.connection - .sendRequest('textDocument/hover', { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - } - } as protocol.TextDocumentPositionParams) - .then(params => { - this.emit('hover', params, documentInfo.uri); - }); + + const params: protocol.TextDocumentPositionParams = { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + } + }; + + const hover = await this.connection.sendRequest( + 'textDocument/hover', + params + ); + + if (emit) { + this.emit('hover', hover, documentInfo.uri); + } + + return hover; } public async getCompletion( From 792749e86da2064aa706ac509e1483ae80edb6c2 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 00:12:08 -0500 Subject: [PATCH 38/63] rework on hover, completion --- .../src/adapters/codemirror/features/hover.ts | 1 - .../jupyterlab/components/completion.ts | 19 +++++++++---------- packages/lsp-ws-connection/src/types.ts | 4 ++++ .../lsp-ws-connection/src/ws-connection.ts | 18 +++++++++++------- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index bc81f97ef..251fb3e84 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -53,7 +53,6 @@ export class Hover extends CodeMirrorLSPFeature { this.virtual_document.document_info, false ); - console.log('NRB: hover', hover); return hover; }, 50 diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index f590b8ea3..adb62b971 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -21,6 +21,8 @@ import { } from '../../../positioning'; import { LSPConnection } from '../../../connection'; +const DEBUG = 0; + /* Feedback: anchor - not clear from docs bundle - very not clear from the docs, interface or better docs would be nice to have @@ -68,10 +70,7 @@ export class LSPConnector extends DataConnector< } protected get _has_kernel(): boolean { - return ( - typeof this.options.session !== 'undefined' && - this.options.session.kernel !== null - ); + return this.options.session?.kernel != null; } protected get _kernel_language(): string { @@ -95,7 +94,7 @@ export class LSPConnector extends DataConnector< * * @param request - The completion request text and details. */ - fetch( + async fetch( request: CompletionHandler.IRequest ): Promise { let editor = this._editor; @@ -104,7 +103,7 @@ export class LSPConnector extends DataConnector< const token = editor.getTokenForPosition(cursor); if (this.suppress_auto_invoke_in.indexOf(token.type) !== -1) { - console.log('Suppressing completer auto-invoke in', token.type); + DEBUG && console.log('Suppressing completer auto-invoke in', token.type); return; } @@ -194,7 +193,7 @@ export class LSPConnector extends DataConnector< // to the matches... // Suggested in https://github.com/jupyterlab/jupyterlab/issues/7044, TODO PR - console.log('[LSP][Completer] Token:', token); + DEBUG && console.log('[LSP][Completer] Token:', token); let completion_items = ((await connection.getCompletion( cursor, @@ -281,7 +280,7 @@ export class LSPConnector extends DataConnector< } else if (lsp.matches.length === 0) { return kernel; } - console.log('[LSP][Completer] Merging completions:', lsp, kernel); + DEBUG && console.log('[LSP][Completer] Merging completions:', lsp, kernel); // Populate the result with a copy of the lsp matches. const matches = lsp.matches.slice(); @@ -301,7 +300,7 @@ export class LSPConnector extends DataConnector< const cursor = editor.getCursorPosition(); const line = editor.getLine(cursor.line); prefix = line.substring(kernel.start, lsp.start); - console.log('[LSP][Completer] Removing kernel prefix: ', prefix); + DEBUG && console.log('[LSP][Completer] Removing kernel prefix: ', prefix); } else if (lsp.start < kernel.start) { console.warn('[LSP][Completer] Kernel start > LSP start'); } @@ -316,7 +315,7 @@ export class LSPConnector extends DataConnector< // TODO push the CompletionItem suggestion with proper sorting, this is a mess let priority_matches = new Set(); - if (typeof kernel.metadata._jupyter_types_experimental !== 'undefined') { + if (kernel.metadata._jupyter_types_experimental == null) { let kernel_types = kernel.metadata._jupyter_types_experimental as Array< IItemType >; diff --git a/packages/lsp-ws-connection/src/types.ts b/packages/lsp-ws-connection/src/types.ts index 7bee973be..9c721e4cf 100644 --- a/packages/lsp-ws-connection/src/types.ts +++ b/packages/lsp-ws-connection/src/types.ts @@ -24,6 +24,10 @@ export type AnyLocation = | lsProtocol.LocationLink[] | null; +export type AnyCompletion = + | lsProtocol.CompletionList + | lsProtocol.CompletionItem[]; + type ConnectionEvent = | 'completion' | 'completionResolved' diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 6e5ece45f..0cf9a3948 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -12,7 +12,8 @@ import { IPosition, ITokenInfo, IDocumentInfo, - AnyLocation + AnyLocation, + AnyCompletion } from './types'; /** @@ -306,7 +307,7 @@ export class LspWsConnection extends events.EventEmitter triggerCharacter?: string, triggerKind?: protocol.CompletionTriggerKind ) { - if (!this.isConnected || !this.serverCapabilities?.completionProvider) { + if (!(this.isReady && this.serverCapabilities?.completionProvider)) { return; } @@ -324,14 +325,17 @@ export class LspWsConnection extends events.EventEmitter } }; - const items = await this.connection.sendRequest< - protocol.CompletionList | protocol.CompletionItem[] - >('textDocument/completion', params); + const items = await this.connection.sendRequest( + 'textDocument/completion', + params + ); + + const itemList = items && 'items' in items ? items.items : items; if (emit) { - this.emit('completion', items && 'items' in items ? items.items : items); + this.emit('completion', itemList); } - return items; + return itemList; } public getDetailedCompletion(completionItem: protocol.CompletionItem) { From 763aa6d549edd3c10d8bd5ae12e48c1df0174839 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 00:18:59 -0500 Subject: [PATCH 39/63] typeofpocalypse --- packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts | 2 +- .../src/adapters/codemirror/features/completion.ts | 2 +- .../src/adapters/codemirror/features/diagnostics.ts | 2 +- .../adapters/codemirror/features/diagnostics_listing.tsx | 4 ++-- .../src/adapters/codemirror/features/hover.ts | 2 +- packages/jupyterlab-lsp/src/extractors/defaults.ts | 2 +- packages/jupyterlab-lsp/src/extractors/regexp.ts | 5 +---- packages/jupyterlab-lsp/src/magics/rpy2.ts | 6 +++--- packages/jupyterlab-lsp/src/virtual/document.ts | 2 +- 9 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index a0f914721..7fda291d8 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -194,7 +194,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { let start = PositionConverter.lsp_to_cm(range.start) as IVirtualPosition; let end = PositionConverter.lsp_to_cm(range.end) as IVirtualPosition; - if (typeof cm_editor === 'undefined') { + if (cm_editor == null) { let start_in_root = this.transform_virtual_position_to_root_position( start ); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts index 6a96c3d07..c5060753c 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts @@ -8,7 +8,7 @@ export class Completion extends CodeMirrorLSPFeature { get completionCharacters() { if ( - typeof this._completionCharacters === 'undefined' || + this._completionCharacters == null || !this._completionCharacters.length ) { this._completionCharacters = this.connection.getLanguageCompletionCharacters(); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index dfcbec0d1..1d61ec9f2 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -405,7 +405,7 @@ export function message_without_code(diagnostic: lsProtocol.Diagnostic) { let message = diagnostic.message; let code_str = '' + diagnostic.code; if ( - typeof diagnostic.code !== 'undefined' && + diagnostic.code != null && diagnostic.code !== '' && message.startsWith(code_str + '') ) { diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx index ff2af897a..246514700 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx @@ -148,7 +148,7 @@ class Column { } is_available(context: IListingContext) { - if (typeof this.options.is_available !== 'undefined') { + if (this.options.is_available != null) { return this.options.is_available(context); } return true; @@ -264,7 +264,7 @@ export class DiagnosticsListing extends VDomRenderer { render() { let diagnostics_db = this.model.diagnostics; const editor = this.model.virtual_editor; - if (!diagnostics_db || typeof editor === 'undefined') { + if (!diagnostics_db || editor == null) { return
No issues detected, great job!
; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index 251fb3e84..b5b08fe0d 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -155,7 +155,7 @@ export class Hover extends CodeMirrorLSPFeature { // happens because mousemove is attached to panel, not individual code cells, // and because some regions of the editor (between lines) have no characters - if (typeof root_position === 'undefined') { + if (root_position == null) { // this.remove_range_highlight(); this.hover_character = null; return; diff --git a/packages/jupyterlab-lsp/src/extractors/defaults.ts b/packages/jupyterlab-lsp/src/extractors/defaults.ts index e61d2b475..6787fa559 100644 --- a/packages/jupyterlab-lsp/src/extractors/defaults.ts +++ b/packages/jupyterlab-lsp/src/extractors/defaults.ts @@ -11,7 +11,7 @@ function rpy2_replacer(match: string, ...args: string[]) { // define dummy input variables using empty data frames let inputs = r.inputs.map(i => i + ' <- data.frame();').join(' '); let code: string; - if (typeof r.rest === 'undefined') { + if (r.rest == null) { code = ''; } else { code = r.rest.startsWith(' ') ? r.rest.slice(1) : r.rest; diff --git a/packages/jupyterlab-lsp/src/extractors/regexp.ts b/packages/jupyterlab-lsp/src/extractors/regexp.ts index ee1a53084..0fabb10eb 100644 --- a/packages/jupyterlab-lsp/src/extractors/regexp.ts +++ b/packages/jupyterlab-lsp/src/extractors/regexp.ts @@ -49,10 +49,7 @@ export class RegExpForeignCodeExtractor implements IForeignCodeExtractor { let end = this.global_expression.lastIndex; - if ( - this.options.keep_in_host || - typeof this.options.keep_in_host === 'undefined' - ) { + if (this.options.keep_in_host || this.options.keep_in_host == null) { host_code_fragment = code.substring(started_from, end); } else { if (started_from === match.index) { diff --git a/packages/jupyterlab-lsp/src/magics/rpy2.ts b/packages/jupyterlab-lsp/src/magics/rpy2.ts index 0e6a66cb7..b187f78e8 100644 --- a/packages/jupyterlab-lsp/src/magics/rpy2.ts +++ b/packages/jupyterlab-lsp/src/magics/rpy2.ts @@ -7,7 +7,7 @@ export function extract_r_args(args: string[], content_position: number) { for (let i = 0; i < args.length; i = i + 2) { let arg = args[i]; let variable = args[i + 1]; - if (typeof arg === 'undefined') { + if (arg == null) { break; } else if (arg === 'i' || arg === '-input') { inputs.push(variable); @@ -63,14 +63,14 @@ export function rpy2_reverse_pattern(quote = '"', multi_line = false): string { export function rpy2_reverse_replacement(match: string, ...args: string[]) { let outputs = []; for (let i = 0; i < 10; i++) { - if (typeof args[i] === 'undefined') { + if (args[i] == null) { break; } outputs.push(args[i]); } let inputs = []; for (let i = 13; i < 23; i++) { - if (typeof args[i] === 'undefined') { + if (args[i] == null) { break; } inputs.push(args[i]); diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index cc7a3d19b..89db589b9 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -656,7 +656,7 @@ export class VirtualDocument { } get root(): VirtualDocument { - if (typeof this.parent === 'undefined') { + if (this.parent == null) { return this; } return this.parent.root; From c64556fc3b9ef7c31785065f88a012cdadc25848 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 00:36:30 -0500 Subject: [PATCH 40/63] handle malformed end ranges --- .../src/adapters/codemirror/features/diagnostics.ts | 13 +++++++++++-- packages/lsp-ws-connection/src/ws-connection.ts | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index 1d61ec9f2..62b154015 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -2,7 +2,7 @@ import * as CodeMirror from 'codemirror'; import * as lsProtocol from 'vscode-languageserver-protocol'; import { Menu } from '@phosphor/widgets'; import { PositionConverter } from '../../../converter'; -import { IVirtualPosition } from '../../../positioning'; +import { IVirtualPosition, IEditorPosition } from '../../../positioning'; import { diagnosticSeverityNames } from '../../../lsp'; import { DefaultMap } from '../../../utils'; import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; @@ -308,7 +308,16 @@ export class Diagnostics extends CodeMirrorLSPFeature { let cm_editor = document.get_editor_at_virtual_line(start); let start_in_editor = document.transform_virtual_to_editor(start); - let end_in_editor = document.transform_virtual_to_editor(end); + let end_in_editor: IEditorPosition; + + // some servers return strange positions for ends + try { + end_in_editor = document.transform_virtual_to_editor(end); + } catch (err) { + DEBUG && console.warn('LSP: Malformed range for diagnostic', end); + end_in_editor = { ...start_in_editor, ch: start_in_editor.ch + 1 }; + } + let range_in_editor = { start: start_in_editor, end: end_in_editor diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 0cf9a3948..42f756936 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -273,7 +273,7 @@ export class LspWsConnection extends events.EventEmitter documentInfo: IDocumentInfo, emit = true ) { - if (!this.isReady) { + if (!(this.isReady && this.serverCapabilities?.hoverProvider)) { return; } From 2be2b68e4955b072d4aebec7f49b83395013f867 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 00:50:23 -0500 Subject: [PATCH 41/63] work on type defs --- .../lsp-ws-connection/src/ws-connection.ts | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 42f756936..f149dfc60 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -464,27 +464,35 @@ export class LspWsConnection extends events.EventEmitter * Request a link to the type definition of the current symbol. The results will not be displayed * unless they are within the same file URI */ - public getTypeDefinition(location: IPosition, documentInfo: IDocumentInfo) { + public async getTypeDefinition( + location: IPosition, + documentInfo: IDocumentInfo, + emit = true + ) { if (!this.isReady || !this.isTypeDefinitionSupported()) { return; } - this.connection - .sendRequest( - 'textDocument/typeDefinition', - { - textDocument: { - uri: documentInfo.uri - }, - position: { - line: location.line, - character: location.ch - } - } as protocol.TextDocumentPositionParams - ) - .then(result => { - this.emit('goTo', result); - }); + const params: protocol.TextDocumentPositionParams = { + textDocument: { + uri: documentInfo.uri + }, + position: { + line: location.line, + character: location.ch + } + }; + + const locations = await this.connection.sendRequest( + 'textDocument/typeDefinition', + params + ); + + if (emit) { + this.emit('goTo', locations); + } + + return locations; } /** From 4450a0bc8b8a759b15cfe4013a8ae19108103910 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 07:24:08 -0500 Subject: [PATCH 42/63] more initialization check into notebook setup --- atest/05_Features/Completion.robot | 7 ------- atest/05_Features/Signature.robot | 1 - atest/Keywords.robot | 4 +++- scripts/lint.py | 6 +++++- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/atest/05_Features/Completion.robot b/atest/05_Features/Completion.robot index bc9d3b9c4..599ef3ced 100644 --- a/atest/05_Features/Completion.robot +++ b/atest/05_Features/Completion.robot @@ -11,7 +11,6 @@ Works With Kernel Running [Documentation] The suggestions from kernel and LSP should get integrated. [Tags] language:python Setup Notebook Python Completion.ipynb - Wait Until Fully Initialized Enter Cell Editor 1 line=2 Capture Page Screenshot 01-entered-cell.png Trigger Completer @@ -31,7 +30,6 @@ Works With Kernel Running Works When Kernel Is Shut Down [Tags] language:python Setup Notebook Python Completion.ipynb - Wait Until Fully Initialized Lab Command Shut Down All Kernels… Capture Page Screenshot 01-shutting-kernels.png Accept Default Dialog Option @@ -59,7 +57,6 @@ User Can Select Lowercase After Starting Uppercase [Tags] language:python Setup Notebook Python Completion.ipynb # `from time import Tim` → `from time import time` - Wait Until Fully Initialized Enter Cell Editor 5 line=1 Trigger Completer Completer Should Suggest time @@ -73,7 +70,6 @@ Mid Token Completions Do Not Overwrite # `dispdata` → `display_tabledata` Place Cursor In Cell Editor At 9 line=1 character=4 Capture Page Screenshot 01-cursor-placed.png - Wait Until Fully Initialized Press Keys None TAB Capture Page Screenshot 02-completed.png Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 9 display_tabledata @@ -88,7 +84,6 @@ Completion Works For Tokens Separated By Space Setup Notebook Python Completion.ipynb # `from statistics ` → `from statistics import` Enter Cell Editor 13 line=1 - Wait Until Fully Initialized Trigger Completer Completer Should Suggest import Press Keys None ENTER @@ -102,7 +97,6 @@ Kernel And LSP Completions Merge Prefix Conflicts Are Resolved # `import os.pat` → `import os.pathsep` Setup Notebook Python Completion.ipynb Enter Cell Editor 15 line=1 - Wait Until Fully Initialized Trigger Completer Completer Should Suggest pathsep Select Completer Suggestion pathsep @@ -113,7 +107,6 @@ Triggers Completer On Dot [Tags] language:python Setup Notebook Python Completion.ipynb Enter Cell Editor 2 line=1 - Wait Until Fully Initialized Press Keys None . Wait Until Keyword Succeeds 10x 0.5s Cell Editor Should Equal 2 list. Wait Until Page Contains Element ${COMPLETER_BOX} timeout=35s diff --git a/atest/05_Features/Signature.robot b/atest/05_Features/Signature.robot index 341faac85..ea242371b 100644 --- a/atest/05_Features/Signature.robot +++ b/atest/05_Features/Signature.robot @@ -9,7 +9,6 @@ ${SIGNATURE_BOX} css:.lsp-signature-help *** Test Cases *** Triggers Signature Help After A Keystroke Setup Notebook Python Signature.ipynb - Wait Until Fully Initialized Enter Cell Editor 1 line=6 Capture Page Screenshot 01-entered-cell.png Press Keys None ( diff --git a/atest/Keywords.robot b/atest/Keywords.robot index e7a928ee3..3325f61ee 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -184,7 +184,9 @@ Setup Notebook Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} Try to Close All Tabs Open ${file} in ${MENU NOTEBOOK} + Wait Until Fully Initialized Capture Page Screenshot 00-opened.png + Capture Page Screenshot 01-initialized.png Open Diagnostics Panel Lab Command Show Diagnostics Panel @@ -215,7 +217,7 @@ Place Cursor In Cell Editor At Execute JavaScript return document.querySelector('.jp-Cell:nth-child(${cell_nr}) .CodeMirror').CodeMirror.setCursor({line: ${line} - 1, ch: ${character}}) Wait Until Fully Initialized - Wait Until Element Contains ${STATUSBAR} Fully initialized timeout=35s + Wait Until Element Contains ${STATUSBAR} Fully initialized timeout=60s Open Context Menu Over [Arguments] ${sel} diff --git a/scripts/lint.py b/scripts/lint.py index ce99dd6dd..13c70308d 100644 --- a/scripts/lint.py +++ b/scripts/lint.py @@ -10,7 +10,11 @@ ROOT = Path(__file__).parent.parent -PY_SRC = list((ROOT / "py_src").rglob("*.py")) +PY_SRC = [ + path + for path in (ROOT / "py_src").rglob("*.py") + if ".ipynb_checkpoints" not in str(path) +] PY_SCRIPTS = list((ROOT / "scripts").rglob("*.py")) PY_DOCS = list((ROOT / "docs").rglob("*.py")) PY_ATEST = list((ROOT / "atest").glob("*.py")) From 74991ec1fcad1ae34f9062a4f667dc6c0bad3fc4 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 07:24:34 -0500 Subject: [PATCH 43/63] restore verbose logging, remove DEBUG --- .../src/adapters/codemirror/cm_adapter.ts | 11 ++-- .../codemirror/features/diagnostics.ts | 41 +++++++-------- .../codemirror/features/highlights.ts | 13 ++--- .../adapters/codemirror/features/jump_to.ts | 19 +++---- .../jupyterlab/components/completion.ts | 10 ++-- .../src/adapters/jupyterlab/file_editor.ts | 4 +- .../src/adapters/jupyterlab/jl_adapter.ts | 52 ++++++++----------- .../src/adapters/jupyterlab/notebook.ts | 22 ++++---- .../jupyterlab-lsp/src/connection_manager.ts | 52 +++++++------------ .../jupyterlab-lsp/src/virtual/console.ts | 6 +-- .../jupyterlab-lsp/src/virtual/document.ts | 4 +- .../src/virtual/editors/notebook.ts | 4 +- 12 files changed, 95 insertions(+), 143 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 4a79166a5..05d814573 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -6,8 +6,6 @@ import { IRootPosition } from '../../positioning'; import { ILSPFeature } from './feature'; import { IJupyterLabComponentsManager } from '../jupyterlab/jl_adapter'; -const DEBUG = 0; - export class CodeMirrorAdapter { features: Map; @@ -44,10 +42,9 @@ export class CodeMirrorAdapter { try { await until_ready(() => this.last_change != null, 30, 22); } catch (err) { - DEBUG && - console.log( - 'No change obtained from CodeMirror editor within the expected time of 0.66s' - ); + console.log( + 'No change obtained from CodeMirror editor within the expected time of 0.66s' + ); return; } @@ -58,7 +55,7 @@ export class CodeMirrorAdapter { try { root_position = this.editor.getDoc().getCursor('end') as IRootPosition; } catch (err) { - DEBUG && console.log('LSP: Root positon not found'); + console.log('LSP: Root positon not found'); return; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index 62b154015..645e5c981 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -16,8 +16,6 @@ import { import { VirtualDocument } from '../../../virtual/document'; import { VirtualEditor } from '../../../virtual/editor'; -const DEBUG = 0; - // TODO: settings const default_severity = 2; @@ -250,11 +248,10 @@ export class Diagnostics extends CodeMirrorLSPFeature { range.end ) as IVirtualPosition; if (start.line > this.virtual_document.last_virtual_line) { - DEBUG && - console.log( - 'Malformed diagnostic was skipped (out of lines) ', - diagnostics - ); + console.log( + 'Malformed diagnostic was skipped (out of lines) ', + diagnostics + ); return; } @@ -268,7 +265,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { start_in_root ); } catch (e) { - DEBUG && console.log(e, diagnostics); + console.log(e, diagnostics); return; } @@ -276,13 +273,12 @@ export class Diagnostics extends CodeMirrorLSPFeature { // and the user already changed the document so // that now this regions is in another virtual document! if (this.virtual_document !== document) { - DEBUG && - console.log( - `Ignoring inspections from ${response.uri}`, - ` (this region is covered by a another virtual document: ${document.uri})`, - ` inspections: `, - diagnostics - ); + console.log( + `Ignoring inspections from ${response.uri}`, + ` (this region is covered by a another virtual document: ${document.uri})`, + ` inspections: `, + diagnostics + ); return; } @@ -291,11 +287,10 @@ export class Diagnostics extends CodeMirrorLSPFeature { .get(start.line) .skip_inspect.indexOf(document.id_path) !== -1 ) { - DEBUG && - console.log( - 'Ignoring inspections silenced for this document:', - diagnostics - ); + console.log( + 'Ignoring inspections silenced for this document:', + diagnostics + ); return; } @@ -314,7 +309,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { try { end_in_editor = document.transform_virtual_to_editor(end); } catch (err) { - DEBUG && console.warn('LSP: Malformed range for diagnostic', end); + console.warn('LSP: Malformed range for diagnostic', end); end_in_editor = { ...start_in_editor, ch: start_in_editor.ch + 1 }; } @@ -371,8 +366,8 @@ export class Diagnostics extends CodeMirrorLSPFeature { console.warn( 'Marking inspection (diagnostic text) failed, see following logs (2):' ); - DEBUG && console.log(diagnostics); - DEBUG && console.log(e); + console.log(diagnostics); + console.log(e); return; } this.marked_diagnostics.set(diagnostic_hash, marker); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 2bb0453b1..6e6233866 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -5,8 +5,6 @@ import { VirtualDocument } from '../../../virtual/document'; import { IRootPosition } from '../../../positioning'; import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; -const DEBUG = 0; - export class Highlights extends CodeMirrorLSPFeature { name = 'Highlights'; protected highlight_markers: CodeMirror.TextMarker[] = []; @@ -83,7 +81,7 @@ export class Highlights extends CodeMirrorLSPFeature { .getDoc() .getCursor('start') as IRootPosition; } catch (err) { - DEBUG && console.warn('LSP: no root position available'); + console.warn('LSP: no root position available'); return; } @@ -91,11 +89,10 @@ export class Highlights extends CodeMirrorLSPFeature { try { document = this.virtual_editor.document_at_root_position(root_position); } catch (e) { - DEBUG && - console.warn( - 'LSP: Could not obtain virtual document from position', - root_position - ); + console.warn( + 'LSP: Could not obtain virtual document from position', + root_position + ); return; } if (document !== this.virtual_document) { diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts index 57af652dd..5774dc326 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts @@ -4,8 +4,6 @@ import { IVirtualPosition } from '../../../positioning'; import { uri_to_contents_path, uris_equal } from '../../../utils'; import { AnyLocation } from 'lsp-ws-connection/src/types'; -const DEBUG = 0; - export class JumpToDefinition extends CodeMirrorLSPFeature { name = 'JumpToDefinition'; static commands: Array = [ @@ -33,7 +31,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { get_uri_and_range(location_or_locations: AnyLocation) { if (location_or_locations == null) { - DEBUG && console.log('No jump targets found'); + console.log('No jump targets found'); return; } // some language servers appear to return a single object @@ -48,8 +46,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { return; } - DEBUG && - console.log('Will jump to the first of suggested locations:', locations); + console.log('Will jump to the first of suggested locations:', locations); const location_or_link = locations[0]; @@ -70,7 +67,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { const target_info = this.get_uri_and_range(location_or_locations); if (target_info == null) { - DEBUG && console.log('No jump targets found'); + console.log('No jump targets found'); } let { uri, range } = target_info; @@ -86,8 +83,8 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { virtual_position ); let editor_position_ce = PositionConverter.cm_to_ce(editor_position); - DEBUG && console.log(`Jumping to ${editor_index}th editor of ${uri}`); - DEBUG && console.log('Jump target within editor:', editor_position_ce); + console.log(`Jumping to ${editor_index}th editor of ${uri}`); + console.log('Jump target within editor:', editor_position_ce); this.jumper.jump({ token: { offset: this.jumper.getOffset(editor_position_ce, editor_index), @@ -98,9 +95,9 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { } else { // otherwise there is no virtual document and we expect the returned position to be source position: let source_position_ce = PositionConverter.cm_to_ce(virtual_position); - DEBUG && console.log(`Jumping to external file: ${uri}`); - DEBUG && - console.log('Jump target (source location):', source_position_ce); + console.log(`Jumping to external file: ${uri}`); + + console.log('Jump target (source location):', source_position_ce); // can it be resolved vs our guessed server root? const contents_path = uri_to_contents_path(uri); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index adb62b971..c55289323 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -21,8 +21,6 @@ import { } from '../../../positioning'; import { LSPConnection } from '../../../connection'; -const DEBUG = 0; - /* Feedback: anchor - not clear from docs bundle - very not clear from the docs, interface or better docs would be nice to have @@ -103,7 +101,7 @@ export class LSPConnector extends DataConnector< const token = editor.getTokenForPosition(cursor); if (this.suppress_auto_invoke_in.indexOf(token.type) !== -1) { - DEBUG && console.log('Suppressing completer auto-invoke in', token.type); + console.log('Suppressing completer auto-invoke in', token.type); return; } @@ -193,7 +191,7 @@ export class LSPConnector extends DataConnector< // to the matches... // Suggested in https://github.com/jupyterlab/jupyterlab/issues/7044, TODO PR - DEBUG && console.log('[LSP][Completer] Token:', token); + console.log('[LSP][Completer] Token:', token); let completion_items = ((await connection.getCompletion( cursor, @@ -280,7 +278,7 @@ export class LSPConnector extends DataConnector< } else if (lsp.matches.length === 0) { return kernel; } - DEBUG && console.log('[LSP][Completer] Merging completions:', lsp, kernel); + console.log('[LSP][Completer] Merging completions:', lsp, kernel); // Populate the result with a copy of the lsp matches. const matches = lsp.matches.slice(); @@ -300,7 +298,7 @@ export class LSPConnector extends DataConnector< const cursor = editor.getCursorPosition(); const line = editor.getLine(cursor.line); prefix = line.substring(kernel.start, lsp.start); - DEBUG && console.log('[LSP][Completer] Removing kernel prefix: ', prefix); + console.log('[LSP][Completer] Removing kernel prefix: ', prefix); } else if (lsp.start < kernel.start) { console.warn('[LSP][Completer] Kernel start > LSP start'); } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts index 376b9270c..011659d9a 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts @@ -12,8 +12,6 @@ import { CodeEditor } from '@jupyterlab/codeeditor'; import { VirtualFileEditor } from '../../virtual/editors/file_editor'; import { DocumentConnectionManager } from '../../connection_manager'; -const DEBUG = 0; - export class FileEditorAdapter extends JupyterLabWidgetAdapter { editor: FileEditor; jumper: FileEditorJumper; @@ -73,7 +71,7 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { ); this.connect_contentChanged_signal(); - DEBUG && console.log('LSP: file ready for connection:', this.path); + console.log('LSP: file ready for connection:', this.path); this.connect_document(this.virtual_editor.virtual_document).catch( console.warn ); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 706c6a297..bc964bbd3 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -33,8 +33,6 @@ import { } from '../../connection_manager'; import { Rename } from '../codemirror/features/rename'; -const DEBUG = 0; - export const lsp_features: Array = [ Completion, Diagnostics, @@ -134,11 +132,10 @@ export abstract class JupyterLabWidgetAdapter this.adapters = new Map(); this.connection_manager = connection_manager; this.connection_manager.closed.connect((manger, { virtual_document }) => { - DEBUG && - console.log( - 'LSP: connection closed, disconnecting adapter', - virtual_document.id_path - ); + console.log( + 'LSP: connection closed, disconnecting adapter', + virtual_document.id_path + ); if (virtual_document !== this.virtual_editor?.virtual_document) { return; } @@ -237,12 +234,12 @@ export abstract class JupyterLabWidgetAdapter await this.virtual_editor.update_documents().then(() => { // refresh the document on the LSP server this.document_changed(virtual_document, virtual_document, true); - DEBUG && - console.log( - 'LSP: virtual document(s) for', - this.document_path, - 'have been initialized' - ); + + console.log( + 'LSP: virtual document(s) for', + this.document_path, + 'have been initialized' + ); }); } @@ -268,24 +265,19 @@ export abstract class JupyterLabWidgetAdapter let adapter = this.adapters.get(virtual_document.id_path); if (connection == null) { - DEBUG && - console.log( - 'LSP: Skipping document update signal: connection not ready' - ); + console.log('LSP: Skipping document update signal: connection not ready'); return; } if (adapter == null) { - DEBUG && - console.log('LSP: Skipping document update signal: adapter not ready'); + console.log('LSP: Skipping document update signal: adapter not ready'); return; } - DEBUG && - console.log( - 'LSP: virtual document', - virtual_document.id_path, - 'has changed sending update' - ); + console.log( + 'LSP: virtual document', + virtual_document.id_path, + 'has changed sending update' + ); connection.sendFullTextChange( virtual_document.value, virtual_document.document_info @@ -331,10 +323,10 @@ export abstract class JupyterLabWidgetAdapter private async connect(virtual_document: VirtualDocument) { let language = virtual_document.language; - DEBUG && - console.log( - `LSP: will connect using root path: ${this.root_path} and language: ${language}` - ); + + console.log( + `LSP: will connect using root path: ${this.root_path} and language: ${language}` + ); let options = { virtual_document, @@ -392,7 +384,7 @@ export abstract class JupyterLabWidgetAdapter this, adapter_features ); - DEBUG && console.log('LSP: Adapter for', this.document_path, 'is ready.'); + console.log('LSP: Adapter for', this.document_path, 'is ready.'); return adapter; } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 27be9da6f..9a99dfceb 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -16,8 +16,6 @@ import { nbformat } from '@jupyterlab/coreutils'; import ILanguageInfoMetadata = nbformat.ILanguageInfoMetadata; import { DocumentConnectionManager } from '../../connection_manager'; -const DEBUG = 0; - export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; widget: NotebookPanel; @@ -53,17 +51,16 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { this.widget.context.session.kernelChanged.connect((_session, change) => { if (!change.newValue) { - DEBUG && console.log('LSP: kernel was shut down'); + console.log('LSP: kernel was shut down'); return; } change.newValue.ready .then(async spec => { - DEBUG && - console.log( - 'LSP: Changed to ' + - change.newValue.info.language_info.name + - ' kernel, reconnecting' - ); + console.log( + 'LSP: Changed to ' + + change.newValue.info.language_info.name + + ' kernel, reconnecting' + ); await until_ready(this.is_ready.bind(this), -1); this.reload_connection(); }) @@ -94,7 +91,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { try { return this.widget.context.session.kernel.info.language_info; } catch (e) { - DEBUG && console.log('LSP: Could not get kernel metadata'); + console.log('LSP: Could not get kernel metadata'); return null; } } @@ -121,10 +118,9 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { } async init_once_ready() { - DEBUG && - console.log('LSP: waiting for', this.document_path, 'to fully load'); + console.log('LSP: waiting for', this.document_path, 'to fully load'); await until_ready(this.is_ready.bind(this), -1); - DEBUG && console.log('LSP:', this.document_path, 'ready for connection'); + console.log('LSP:', this.document_path, 'ready for connection'); this.virtual_editor = new VirtualEditorForNotebook( this.widget.content, diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 95afb6eb5..53136f675 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -4,8 +4,6 @@ import { Signal } from '@phosphor/signaling'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { sleep, until_ready } from './utils'; -const DEBUG = 0; - export interface IDocumentConnectionData { virtual_document: VirtualDocument; connection: LSPConnection; @@ -70,11 +68,10 @@ export class DocumentConnectionManager { connect_document_signals(virtual_document: VirtualDocument) { virtual_document.foreign_document_opened.connect((host, context) => { - DEBUG && - console.log( - 'LSP: ConnectionManager received foreign document: ', - context.foreign_document.id_path - ); + console.log( + 'LSP: ConnectionManager received foreign document: ', + context.foreign_document.id_path + ); }); virtual_document.foreign_document_closed.connect( @@ -98,7 +95,7 @@ export class DocumentConnectionManager { private async connect_socket( options: ISocketConnectionOptions ): Promise { - DEBUG && console.log('LSP: Connection Socket', options); + console.log('LSP: Connection Socket', options); let { virtual_document, language } = options; this.connect_document_signals(virtual_document); @@ -168,12 +165,12 @@ export class DocumentConnectionManager { success = true; }) .catch(e => { - DEBUG && console.warn(e); + console.warn(e); }); - DEBUG && - console.log( - 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' - ); + + console.log( + 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' + ); await sleep(interval); // gradually increase the time delay, up to 5 sec @@ -182,7 +179,7 @@ export class DocumentConnectionManager { } async connect(options: ISocketConnectionOptions) { - DEBUG && console.log('LSP: connection requested', options); + console.log('LSP: connection requested', options); let connection = await this.connect_socket(options); connection.on('serverInitialized', capabilities => { @@ -191,24 +188,6 @@ export class DocumentConnectionManager { let { virtual_document, document_path } = options; - await until_ready( - () => { - // @ts-ignore - return connection.isReady; - }, - 50, - 50 - ).catch(() => { - throw Error('LSP: Connect timed out for ' + virtual_document.id_path); - }); - DEBUG && - console.log( - 'LSP:', - document_path, - virtual_document.id_path, - 'connected.' - ); - connection.on('close', closed_manually => { if (!closed_manually) { console.warn('LSP: Connection unexpectedly disconnected'); @@ -219,6 +198,15 @@ export class DocumentConnectionManager { } }); + try { + await until_ready(() => connection.isReady, 100, 50); + } catch { + console.warn(`LSP: Connect timed out for ${virtual_document.id_path}`); + return; + } + + console.log('LSP:', document_path, virtual_document.id_path, 'connected.'); + this.connected.emit({ connection, virtual_document }); return connection; diff --git a/packages/jupyterlab-lsp/src/virtual/console.ts b/packages/jupyterlab-lsp/src/virtual/console.ts index 658de0f03..0f2f884ac 100644 --- a/packages/jupyterlab-lsp/src/virtual/console.ts +++ b/packages/jupyterlab-lsp/src/virtual/console.ts @@ -1,7 +1,5 @@ import '../../style/console.css'; -const DEBUG = 0; - export abstract class EditorLogConsole { abstract log(...args: any[]): void; abstract warn(...args: any[]): void; @@ -33,7 +31,7 @@ export class FloatingConsole extends EditorLogConsole { } log(...args: any[]) { - DEBUG && this.append(this.to_string(args), 'log'); + this.append(this.to_string(args), 'log'); } warn(...args: any[]) { this.append(this.to_string(args), 'warn'); @@ -45,7 +43,7 @@ export class FloatingConsole extends EditorLogConsole { export class BrowserConsole extends EditorLogConsole { log(...args: any[]) { - DEBUG && console.log('LSP: ', ...args); + console.log('LSP: ', ...args); } warn(...args: any[]) { console.warn('LSP: ', ...args); diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index 89db589b9..f9f29070f 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -18,8 +18,6 @@ import { IDocumentInfo } from 'lsp-ws-connection/src'; import { DocumentConnectionManager } from '../connection_manager'; -const DEBUG = 0; - type language = string; interface IVirtualLine { @@ -582,7 +580,7 @@ export class VirtualDocument { } close_foreign(document: VirtualDocument) { - DEBUG && console.log('LSP: closing document', document); + console.log('LSP: closing document', document); this.foreign_document_closed.emit({ foreign_document: document, parent_host: this diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index 189cd76d8..df4dde195 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -11,8 +11,6 @@ import { IVirtualPosition } from '../../positioning'; -const DEBUG = 0; - // @ts-ignore class DocDispatcher implements CodeMirror.Doc { virtual_editor: VirtualEditorForNotebook; @@ -185,7 +183,7 @@ export class VirtualEditorForNotebook extends VirtualEditor { let editor = this.get_editor_at_root_line(pos); return editor.charCoords(pos, mode); } catch (e) { - DEBUG && console.log(e); + console.log(e); return { bottom: 0, left: 0, right: 0, top: 0 }; } } From 2d470a129b55026011ffe2b88d38d0124b8baee7 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 21:22:51 -0500 Subject: [PATCH 44/63] it would appear server_root no longer does anything --- .../src/adapters/jupyterlab/file_editor.ts | 2 -- .../src/adapters/jupyterlab/jl_adapter.ts | 4 +-- .../src/adapters/jupyterlab/notebook.ts | 2 -- .../jupyterlab-lsp/src/connection_manager.ts | 4 --- packages/jupyterlab-lsp/src/index.ts | 31 ------------------- 5 files changed, 1 insertion(+), 42 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts index 011659d9a..4bb4ce729 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts @@ -49,7 +49,6 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { app: JupyterFrontEnd, protected completion_manager: ICompletionManager, rendermime_registry: IRenderMimeRegistry, - server_root: string, connection_manager: DocumentConnectionManager ) { super( @@ -57,7 +56,6 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { editor_widget, rendermime_registry, 'completer:invoke-file', - server_root, connection_manager ); this.jumper = jumper; diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index bc964bbd3..89d1e960e 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -121,7 +121,6 @@ export abstract class JupyterLabWidgetAdapter protected widget: IDocumentWidget, protected rendermime_registry: IRenderMimeRegistry, invoke: string, - private server_root: string, connection_manager: DocumentConnectionManager ) { this.status_message = new StatusMessage(); @@ -318,7 +317,7 @@ export abstract class JupyterLabWidgetAdapter public get_features(virtual_document: VirtualDocument) { let adapter = this.adapters.get(virtual_document.id_path); - return adapter.features; + return adapter?.features; } private async connect(virtual_document: VirtualDocument) { @@ -332,7 +331,6 @@ export abstract class JupyterLabWidgetAdapter virtual_document, language, root_path: this.root_path, - server_root: this.server_root, document_path: this.document_path }; diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 9a99dfceb..eee9e6db4 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -31,7 +31,6 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { app: JupyterFrontEnd, completion_manager: ICompletionManager, rendermime_registry: IRenderMimeRegistry, - server_root: string, connection_manager: DocumentConnectionManager ) { super( @@ -39,7 +38,6 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { editor_widget, rendermime_registry, 'completer:invoke-notebook', - server_root, connection_manager ); this.editor = editor_widget.content; diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 53136f675..8a4ab29df 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -19,10 +19,6 @@ interface ISocketConnectionOptions { * The root path in the JupyterLab (virtual) path space */ root_path: string; - /** - * The real root path (on the server), if exposed by the server. If present, it has to be an absolute path. - */ - server_root: string | null; /** * Path to the document in the JupyterLab space */ diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index fd537699e..a21ff7434 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -71,35 +71,6 @@ const plugin: JupyterFrontEndPlugin = { status_bar: IStatusBar ) => { const connection_manager = new DocumentConnectionManager(); - // temporary workaround for getting the absolute path - let server_root = paths.directories.serverRoot; - if (server_root.startsWith('~')) { - // try to guess the home location: - let user_settings = paths.directories.userSettings; - if (user_settings.startsWith('/home/')) { - server_root = server_root.replace( - '~', - user_settings.substring(0, user_settings.indexOf('/', 6)) - ); - console.log( - 'Guessing Linux the server root using user settings path', - server_root - ); - } else if (user_settings.startsWith('/Users/')) { - server_root = server_root.replace( - '~', - user_settings.substring(0, user_settings.indexOf('/', 7)) - ); - console.log( - 'Guessing Mac the server root using user settings path', - server_root - ); - } else { - console.warn( - 'Unable to solve the absolute path: some LSP servers may not work correctly' - ); - } - } const status_bar_item = new LSPStatus(); @@ -155,7 +126,6 @@ const plugin: JupyterFrontEndPlugin = { app, completion_manager, rendermime_registry, - server_root, connection_manager ); file_editor_adapters.set(fileEditor.id, adapter); @@ -179,7 +149,6 @@ const plugin: JupyterFrontEndPlugin = { app, completion_manager, rendermime_registry, - server_root, connection_manager ); notebook_adapters.set(widget.id, adapter); From bbbb4f94fee52a66b6a7b77bd26feaa7a7fc1442 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 23:19:37 -0500 Subject: [PATCH 45/63] substantially increase ready check interval (50 -> 200) --- packages/jupyterlab-lsp/src/connection_manager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 8a4ab29df..e78e5ffc8 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -195,7 +195,7 @@ export class DocumentConnectionManager { }); try { - await until_ready(() => connection.isReady, 100, 50); + await until_ready(() => connection.isReady, 100, 200); } catch { console.warn(`LSP: Connect timed out for ${virtual_document.id_path}`); return; From f0d38a1d8aa441931e0f8efcd50852350490e3fd Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Wed, 22 Jan 2020 23:22:52 -0500 Subject: [PATCH 46/63] don't react to document changes until connection is ready --- packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 89d1e960e..ca058da43 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -263,7 +263,7 @@ export abstract class JupyterLabWidgetAdapter ); let adapter = this.adapters.get(virtual_document.id_path); - if (connection == null) { + if (!connection?.isReady) { console.log('LSP: Skipping document update signal: connection not ready'); return; } From 29be1291892cd8631a18e0ae8f0ba011d4ac176e Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 23 Jan 2020 22:49:55 -0500 Subject: [PATCH 47/63] some looking at memory profiling --- .../src/adapters/codemirror/cm_adapter.ts | 2 + .../src/adapters/codemirror/feature.ts | 3 + .../codemirror/features/diagnostics.ts | 2 +- .../src/adapters/codemirror/features/hover.ts | 19 ++-- .../src/adapters/codemirror/testutils.ts | 6 +- .../src/adapters/jupyterlab/jl_adapter.ts | 70 +++++++++++---- .../src/adapters/jupyterlab/notebook.ts | 87 +++++++++++++------ packages/jupyterlab-lsp/src/virtual/editor.ts | 34 ++++++-- .../src/virtual/editors/notebook.ts | 3 - py_src/jupyter_lsp/stdio.py | 2 +- 10 files changed, 164 insertions(+), 64 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 05d814573..50c1930db 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -94,6 +94,8 @@ export class CodeMirrorAdapter { for (let feature of this.features.values()) { feature.remove(); } + this.features.clear(); this.editor.off('change', this.doc_change_handler); + this.doc_change_handler = null; } } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index 7fda291d8..b4f1a387b 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -170,14 +170,17 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { for (let [event_name, handler] of this.editor_handlers) { this.virtual_editor.off(event_name, handler); } + this.editor_handlers.clear(); // unregister connection handlers for (let [event_name, handler] of this.connection_handlers) { this.connection.off(event_name, handler); } + this.connection_handlers.clear(); // unregister editor wrapper handlers for (let [event_name, handler] of this.wrapper_handlers) { this.wrapper.removeEventListener(event_name, handler); } + this.wrapper_handlers.clear(); } afterChange( diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index 645e5c981..908ccd145 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -59,7 +59,7 @@ class DiagnosticsPanel { } export const diagnostics_panel = new DiagnosticsPanel(); -export const diagnostics_databases = new Map< +export const diagnostics_databases = new WeakMap< VirtualEditor, DiagnosticsDatabase >(); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index b5b08fe0d..228d9cc85 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -47,19 +47,21 @@ export class Hover extends CodeMirrorLSPFeature { this.connection_handlers.set('hover', this.handleHover.bind(this)); // TODO: make the debounce rate configurable this.debounced_get_hover = new Debouncer>( - async () => { - const hover = await this.connection.getHoverTooltip( - this.virtual_position, - this.virtual_document.document_info, - false - ); - return hover; - }, + this.on_hover, 50 ); super.register(); } + on_hover = async () => { + const hover = await this.connection.getHoverTooltip( + this.virtual_position, + this.virtual_document.document_info, + false + ); + return hover; + }; + protected static get_markup_for_hover( response: lsProtocol.Hover ): lsProtocol.MarkupContent { @@ -255,6 +257,7 @@ export class Hover extends CodeMirrorLSPFeature { remove(): void { this.remove_range_highlight(); this.debounced_get_hover.dispose(); + this.debounced_get_hover = null; super.remove(); } } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index a891f7dbd..c9ebf9000 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -100,7 +100,11 @@ export abstract class FeatureTestEnvironment remove_tooltip: () => { // nothing yet }, - jumper: null + jumper: null, + isDisposed: false, + dispose: () => { + // nothing yet + } }; } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index ca058da43..c074d2676 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -52,6 +52,8 @@ export interface IJupyterLabComponentsManager { ) => FreeTooltip; remove_tooltip: () => void; jumper: CodeJumper; + dispose(): void; + isDisposed: boolean; } export class StatusMessage { @@ -115,6 +117,7 @@ export abstract class JupyterLabWidgetAdapter private _tooltip: FreeTooltip; public connection_manager: DocumentConnectionManager; public status_message: StatusMessage; + public isDisposed = false; protected constructor( protected app: JupyterFrontEnd, @@ -123,26 +126,56 @@ export abstract class JupyterLabWidgetAdapter invoke: string, connection_manager: DocumentConnectionManager ) { - this.status_message = new StatusMessage(); - this.widget.context.pathChanged.connect(this.reload_connection.bind(this)); - this.widget.context.saveState.connect(this.on_save_state.bind(this)); - this.invoke_command = invoke; this.document_connected = new Signal(this); + this.invoke_command = invoke; this.adapters = new Map(); + this.status_message = new StatusMessage(); this.connection_manager = connection_manager; - this.connection_manager.closed.connect((manger, { virtual_document }) => { - console.log( - 'LSP: connection closed, disconnecting adapter', - virtual_document.id_path - ); - if (virtual_document !== this.virtual_editor?.virtual_document) { - return; - } - this.disconnect_adapter(virtual_document); - }); - // register completion connectors - this.document_connected.connect(() => this.connect_completion()); + // set up signal connections + this.widget.context.pathChanged.connect(this.reload_connection, this); + this.widget.context.saveState.connect(this.on_save_state, this); + this.connection_manager.closed.connect(this.on_connection_closed, this); + this.document_connected.connect(this.connect_completion, this); + this.widget.disposed.connect(this.dispose, this); + } + + on_connection_closed( + manager: DocumentConnectionManager, + { virtual_document }: IDocumentConnectionData + ) { + console.log( + 'LSP: connection closed, disconnecting adapter', + virtual_document.id_path + ); + if (virtual_document !== this.virtual_editor?.virtual_document) { + return; + } + this.dispose(); + } + + dispose() { + if (this.isDisposed) { + return; + } + if (this.virtual_editor?.virtual_document) { + this.disconnect_adapter(this.virtual_editor?.virtual_document); + } + + this.widget.context.pathChanged.disconnect(this.reload_connection, this); + this.widget.context.saveState.disconnect(this.on_save_state, this); + this.connection_manager.closed.disconnect(this.on_connection_closed, this); + this.document_connected.disconnect(this.connect_completion, this); + this.widget.disposed.disconnect(this.dispose, this); + this.widget.context.model.contentChanged.disconnect( + this.update_documents, + this + ); + this.adapters.forEach(adapter => adapter.remove()); + this.adapters.clear(); + this.virtual_editor.dispose(); + this.virtual_editor = void 0; + this.isDisposed = true; } abstract virtual_editor: VirtualEditor; @@ -243,7 +276,7 @@ export abstract class JupyterLabWidgetAdapter } protected async connect_document(virtual_document: VirtualDocument) { - virtual_document.changed.connect(this.document_changed.bind(this)); + virtual_document.changed.connect(this.document_changed, this); virtual_document.foreign_document_opened.connect(async (host, context) => { this.connect_document(context.foreign_document).catch(console.warn); @@ -356,7 +389,8 @@ export abstract class JupyterLabWidgetAdapter */ connect_contentChanged_signal() { this.widget.context.model.contentChanged.connect( - this.update_documents.bind(this) + this.update_documents, + this ); } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index eee9e6db4..5e9eccf5c 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -15,6 +15,8 @@ import { Cell } from '@jupyterlab/cells'; import { nbformat } from '@jupyterlab/coreutils'; import ILanguageInfoMetadata = nbformat.ILanguageInfoMetadata; import { DocumentConnectionManager } from '../../connection_manager'; +import { IClientSession } from '@jupyterlab/apputils'; +import { Session } from '@jupyterlab/services'; export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; @@ -47,27 +49,47 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { .then() .catch(console.warn); - this.widget.context.session.kernelChanged.connect((_session, change) => { - if (!change.newValue) { - console.log('LSP: kernel was shut down'); - return; - } - change.newValue.ready - .then(async spec => { - console.log( - 'LSP: Changed to ' + - change.newValue.info.language_info.name + - ' kernel, reconnecting' - ); - await until_ready(this.is_ready.bind(this), -1); - this.reload_connection(); - }) - .catch(e => { - console.warn(e); - // try to reconnect anyway - this.reload_connection(); - }); - }); + this.widget.context.session.kernelChanged.connect( + this.on_kernel_changed, + this + ); + } + + on_kernel_changed( + _session: IClientSession, + change: Session.IKernelChangedArgs + ) { + if (!change.newValue) { + console.log('LSP: kernel was shut down'); + return; + } + change.newValue.ready + .then(async spec => { + console.log( + 'LSP: Changed to ' + + change.newValue.info.language_info.name + + ' kernel, reconnecting' + ); + await until_ready(this.is_ready.bind(this), -1); + this.reload_connection(); + }) + .catch(e => { + console.warn(e); + // try to reconnect anyway + this.reload_connection(); + }); + } + + dispose() { + if (this.isDisposed) { + return; + } + this.widget.context.session.kernelChanged.disconnect( + this.on_kernel_changed, + this + ); + this.widget.content.activeCellChanged.disconnect(this.on_completions, this); + super.dispose(); } is_ready() { @@ -148,6 +170,11 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { }); } + completion_handlers = new Map< + Cell, + ICompletionManager.ICompletableAttributes + >(); + connect_completion() { // see https://github.com/jupyterlab/jupyterlab/blob/c0e9eb94668832d1208ad3b00a9791ef181eca4c/packages/completer-extension/src/index.ts#L198-L213 const cell = this.widget.content.activeCell; @@ -160,11 +187,19 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: cell.editor, parent: this.widget }); - this.widget.content.activeCellChanged.connect((notebook, cell) => { - this.set_completion_connector(cell); + this.completion_handlers.set(cell, handler); + } - handler.editor = cell.editor; - handler.connector = this.current_completion_connector; - }); + on_completions(notebook: Notebook, cell: Cell) { + if (cell == null) { + return; + } + this.set_completion_connector(cell); + const handler = this.completion_handlers.get(cell); + if (handler == null) { + return; + } + handler.editor = cell.editor; + handler.connector = this.current_completion_connector; } } diff --git a/packages/jupyterlab-lsp/src/virtual/editor.ts b/packages/jupyterlab-lsp/src/virtual/editor.ts index cb7d91699..202c6ff7f 100644 --- a/packages/jupyterlab-lsp/src/virtual/editor.ts +++ b/packages/jupyterlab-lsp/src/virtual/editor.ts @@ -35,6 +35,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { */ has_cells: boolean; console: EditorLogConsole; + isDisposed = false; public constructor( protected language: () => string, @@ -46,7 +47,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { ) { this.create_virtual_document(); this.documents_updated = new Signal(this); - this.documents_updated.connect(this.on_updated.bind(this)); + this.documents_updated.connect(this.on_updated, this); this.console = create_console('browser'); } @@ -62,6 +63,23 @@ export abstract class VirtualEditor implements CodeMirror.Editor { ); } + dispose() { + if (this.isDisposed) { + return; + } + this.documents_updated.disconnect(this.on_updated, this); + + this._event_wrappers.forEach((wrapped_handler, [eventName]) => { + this.forEveryBlockEditor(cm_editor => { + cm_editor.off(eventName, wrapped_handler); + }, false); + }); + + this._event_wrappers.clear(); + + this.isDisposed = true; + } + /** * Once all the foreign documents were refreshed, the unused documents (and their connections) * should be terminated if their lifetime has expired. @@ -93,7 +111,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { private is_update_in_progress: boolean = false; private can_update() { - return !this.is_update_in_progress && !this.update_lock; + return !this.isDisposed && !this.is_update_in_progress && !this.update_lock; } private update_lock: boolean = false; @@ -177,10 +195,14 @@ export abstract class VirtualEditor implements CodeMirror.Editor { } abstract forEveryBlockEditor( - callback: (cm_editor: CodeMirror.Editor) => void + callback: (cm_editor: CodeMirror.Editor) => void, + monitor_for_new_blocks?: boolean ): void; - private _event_wrappers = new Map(); + private _event_wrappers = new Map< + [string, CodeMirrorHandler], + WrappedHandler + >(); /** * Proxy the event handler binding to the CodeMirror editors, @@ -199,7 +221,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { ); } }; - this._event_wrappers.set(handler, wrapped_handler); + this._event_wrappers.set([eventName, handler], wrapped_handler); this.forEveryBlockEditor(cm_editor => { cm_editor.on(eventName, wrapped_handler); @@ -207,7 +229,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { } off(eventName: string, handler: CodeMirrorHandler, ...args: any[]): void { - let wrapped_handler = this._event_wrappers.get(handler); + let wrapped_handler = this._event_wrappers.get([eventName, handler]); this.forEveryBlockEditor(cm_editor => { cm_editor.off(eventName, wrapped_handler); diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index df4dde195..39d441cf8 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -343,9 +343,6 @@ export class VirtualEditorForNotebook extends VirtualEditor { callback: (cm_editor: CodeMirror.Editor) => any, monitor_for_new_blocks = true ) { - if (this.notebook.isDisposed) { - return; - } const cells_with_handlers = new Set(); for (let cell of this.notebook.widgets) { diff --git a/py_src/jupyter_lsp/stdio.py b/py_src/jupyter_lsp/stdio.py index dbfd8e472..e032591e3 100644 --- a/py_src/jupyter_lsp/stdio.py +++ b/py_src/jupyter_lsp/stdio.py @@ -118,8 +118,8 @@ async def read_one(self) -> Text: if raw is None: # pragma: no cover self.log.warning( "%s failed to read message of length %s", - content_length, self, + content_length, ) await self.sleep() retries -= 1 From bcb4e65daa64206a2dc503630621d11b6cc3bab6 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Thu, 30 Jan 2020 08:54:28 -0500 Subject: [PATCH 48/63] significant memory leak investigation underway --- .../src/adapters/codemirror/cm_adapter.ts | 54 +++-- .../src/adapters/codemirror/feature.ts | 11 +- .../codemirror/features/completion.ts | 11 +- .../codemirror/features/diagnostics.ts | 78 ++++--- .../codemirror/features/highlights.ts | 36 +-- .../src/adapters/codemirror/features/hover.ts | 18 +- .../adapters/codemirror/features/jump_to.ts | 20 +- .../adapters/codemirror/features/rename.ts | 4 +- .../adapters/codemirror/features/signature.ts | 35 +-- .../src/adapters/codemirror/testutils.ts | 4 +- .../jupyterlab/components/completion.ts | 35 ++- .../jupyterlab/components/statusbar.tsx | 64 +++--- .../src/adapters/jupyterlab/file_editor.ts | 8 +- .../src/adapters/jupyterlab/jl_adapter.ts | 116 +++++++--- .../src/adapters/jupyterlab/notebook.ts | 37 ++-- .../jupyterlab-lsp/src/command_manager.ts | 13 +- packages/jupyterlab-lsp/src/connection.ts | 4 +- .../jupyterlab-lsp/src/connection_manager.ts | 209 ++++++++++++------ packages/jupyterlab-lsp/src/index.ts | 29 ++- packages/jupyterlab-lsp/src/utils.ts | 9 +- .../jupyterlab-lsp/src/virtual/console.ts | 8 +- .../jupyterlab-lsp/src/virtual/document.ts | 56 ++++- packages/jupyterlab-lsp/src/virtual/editor.ts | 33 ++- .../src/virtual/editors/file_editor.ts | 3 + .../src/virtual/editors/notebook.ts | 38 +++- .../lsp-ws-connection/src/ws-connection.ts | 4 +- 26 files changed, 629 insertions(+), 308 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 50c1930db..6aac12caf 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -1,16 +1,18 @@ import * as CodeMirror from 'codemirror'; import { until_ready } from '../../utils'; -import { CodeMirrorHandler, VirtualEditor } from '../../virtual/editor'; +import { VirtualEditor } from '../../virtual/editor'; import { VirtualDocument } from '../../virtual/document'; import { IRootPosition } from '../../positioning'; import { ILSPFeature } from './feature'; import { IJupyterLabComponentsManager } from '../jupyterlab/jl_adapter'; +const DEBUG = 0; + export class CodeMirrorAdapter { features: Map; + isDisposed = false; private last_change: CodeMirror.EditorChange; - private doc_change_handler: CodeMirrorHandler; constructor( protected editor: VirtualEditor, @@ -18,19 +20,19 @@ export class CodeMirrorAdapter { protected jupyterlab_components: IJupyterLabComponentsManager, features = new Array() ) { - this.doc_change_handler = this.saveChange.bind(this); - this.editor.on('change', this.doc_change_handler); + this.editor.on('change', this.saveChange); this.features = new Map(); for (let feature of features) { feature.register(); if (!feature.is_registered) { - this.editor.console.warn( - 'The feature ', - feature, - 'was not registered properly' - ); + DEBUG && + this.editor.console.warn( + 'The feature ', + feature, + 'was not registered properly' + ); } this.features.set(feature.name, feature); } @@ -42,9 +44,10 @@ export class CodeMirrorAdapter { try { await until_ready(() => this.last_change != null, 30, 22); } catch (err) { - console.log( - 'No change obtained from CodeMirror editor within the expected time of 0.66s' - ); + DEBUG && + console.log( + 'No change obtained from CodeMirror editor within the expected time of 0.66s' + ); return; } @@ -55,7 +58,7 @@ export class CodeMirrorAdapter { try { root_position = this.editor.getDoc().getCursor('end') as IRootPosition; } catch (err) { - console.log('LSP: Root positon not found'); + DEBUG && console.log('LSP: Root positon not found'); return; } @@ -76,8 +79,8 @@ export class CodeMirrorAdapter { } return true; } catch (e) { - this.editor.console.log('updateAfterChange failure'); - this.editor.console.error(e); + DEBUG && this.editor.console.log('updateAfterChange failure'); + DEBUG && this.editor.console.error(e); } this.invalidateLastChange(); } @@ -86,16 +89,27 @@ export class CodeMirrorAdapter { this.last_change = null; } - public saveChange(doc: CodeMirror.Doc, change: CodeMirror.EditorChange) { + public saveChange = ( + doc: CodeMirror.Doc, + change: CodeMirror.EditorChange + ) => { this.last_change = change; - } + }; - public remove() { + public dispose() { + if (this.isDisposed) { + return; + } for (let feature of this.features.values()) { feature.remove(); } this.features.clear(); - this.editor.off('change', this.doc_change_handler); - this.doc_change_handler = null; + this.editor.off('change', this.saveChange); + + // just to be sure + this.editor = null; + + // actually disposed + this.isDisposed = true; } } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index b4f1a387b..668a0ff0a 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -20,6 +20,8 @@ import * as CodeMirror from 'codemirror'; import { ICommandContext } from '../../command_manager'; import { DefaultMap } from '../../utils'; +const DEBUG = 0; + export enum CommandEntryPoint { CellContextMenu, FileEditorContextMenu @@ -325,9 +327,10 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { for (let e of change.edits) { let offset = offset_from_lsp(e.range.start, lines); if (edits_by_offset.has(offset)) { - console.warn( - 'Edits should not overlap, ignoring an overlapping edit' - ); + DEBUG && + console.warn( + 'Edits should not overlap, ignoring an overlapping edit' + ); } else { edits_by_offset.set(offset, e); applied_changes += 1; @@ -455,7 +458,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { // note: this does not matter for actions invoke from context menu // as those loose focus anyways (this might be addressed elsewhere) } catch (e) { - console.log('Could not place the cursor back', e); + DEBUG && console.log('Could not place the cursor back', e); } return 1; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts index c5060753c..869724956 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts @@ -2,6 +2,8 @@ import { CompletionTriggerKind } from '../../../lsp'; import * as CodeMirror from 'codemirror'; import { CodeMirrorLSPFeature } from '../feature'; +const DEBUG = 0; + export class Completion extends CodeMirrorLSPFeature { name = 'Completion'; private _completionCharacters: string[]; @@ -25,10 +27,11 @@ export class Completion extends CodeMirrorLSPFeature { // requires an up-to-date virtual document on the LSP side, so we need to wait for sync. let last_character = this.extract_last_character(change); if (this.completionCharacters.indexOf(last_character) > -1) { - this.virtual_editor.console.log( - 'Will invoke completer after', - last_character - ); + DEBUG && + this.virtual_editor.console.log( + 'Will invoke completer after', + last_character + ); this.jupyterlab_components.invoke_completer( CompletionTriggerKind.TriggerCharacter ); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index 908ccd145..e2a834338 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -16,6 +16,8 @@ import { import { VirtualDocument } from '../../../virtual/document'; import { VirtualEditor } from '../../../virtual/editor'; +const DEBUG = 0; + // TODO: settings const default_severity = 2; @@ -131,14 +133,8 @@ export class Diagnostics extends CodeMirrorLSPFeature { ]; register(): void { - this.connection_handlers.set( - 'diagnostic', - this.handleDiagnostic.bind(this) - ); - this.wrapper_handlers.set( - 'focusin', - this.switchDiagnosticsPanelSource.bind(this) - ); + this.connection_handlers.set('diagnostic', this.handleDiagnostic); + this.wrapper_handlers.set('focusin', this.switchDiagnosticsPanelSource); this.unique_editor_ids = new DefaultMap(() => this.unique_editor_ids.size); if (!diagnostics_databases.has(this.virtual_editor)) { diagnostics_databases.set(this.virtual_editor, new DiagnosticsDatabase()); @@ -160,7 +156,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { */ public diagnostics_db: DiagnosticsDatabase; - switchDiagnosticsPanelSource() { + switchDiagnosticsPanelSource = () => { if ( diagnostics_panel.content.model.virtual_editor === this.virtual_editor ) { @@ -169,7 +165,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { diagnostics_panel.content.model.diagnostics = this.diagnostics_db; diagnostics_panel.content.model.virtual_editor = this.virtual_editor; diagnostics_panel.update(); - } + }; protected collapse_overlapping_diagnostics( diagnostics: lsProtocol.Diagnostic[] @@ -220,7 +216,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { return map; } - public handleDiagnostic(response: lsProtocol.PublishDiagnosticsParams) { + public handleDiagnostic = (response: lsProtocol.PublishDiagnosticsParams) => { if (response.uri !== this.virtual_document.document_info.uri) { return; } @@ -248,10 +244,11 @@ export class Diagnostics extends CodeMirrorLSPFeature { range.end ) as IVirtualPosition; if (start.line > this.virtual_document.last_virtual_line) { - console.log( - 'Malformed diagnostic was skipped (out of lines) ', - diagnostics - ); + DEBUG && + console.log( + 'Malformed diagnostic was skipped (out of lines) ', + diagnostics + ); return; } @@ -265,7 +262,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { start_in_root ); } catch (e) { - console.log(e, diagnostics); + DEBUG && console.log(e, diagnostics); return; } @@ -273,12 +270,13 @@ export class Diagnostics extends CodeMirrorLSPFeature { // and the user already changed the document so // that now this regions is in another virtual document! if (this.virtual_document !== document) { - console.log( - `Ignoring inspections from ${response.uri}`, - ` (this region is covered by a another virtual document: ${document.uri})`, - ` inspections: `, - diagnostics - ); + DEBUG && + console.log( + `Ignoring inspections from ${response.uri}`, + ` (this region is covered by a another virtual document: ${document.uri})`, + ` inspections: `, + diagnostics + ); return; } @@ -287,10 +285,11 @@ export class Diagnostics extends CodeMirrorLSPFeature { .get(start.line) .skip_inspect.indexOf(document.id_path) !== -1 ) { - console.log( - 'Ignoring inspections silenced for this document:', - diagnostics - ); + DEBUG && + console.log( + 'Ignoring inspections silenced for this document:', + diagnostics + ); return; } @@ -309,7 +308,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { try { end_in_editor = document.transform_virtual_to_editor(end); } catch (err) { - console.warn('LSP: Malformed range for diagnostic', end); + DEBUG && console.warn('LSP: Malformed range for diagnostic', end); end_in_editor = { ...start_in_editor, ch: start_in_editor.ch + 1 }; } @@ -363,11 +362,12 @@ export class Diagnostics extends CodeMirrorLSPFeature { .getDoc() .markText(start_in_editor, end_in_editor, options); } catch (e) { - console.warn( - 'Marking inspection (diagnostic text) failed, see following logs (2):' - ); - console.log(diagnostics); - console.log(e); + DEBUG && + console.warn( + 'Marking inspection (diagnostic text) failed, see following logs (2):' + ); + DEBUG && console.log(diagnostics); + DEBUG && console.log(e); return; } this.marked_diagnostics.set(diagnostic_hash, marker); @@ -381,9 +381,9 @@ export class Diagnostics extends CodeMirrorLSPFeature { this.diagnostics_db.set(this.virtual_document, diagnostics_list); diagnostics_panel.update(); } catch (e) { - console.warn(e); + DEBUG && console.warn(e); } - } + }; protected remove_unused_diagnostic_markers(to_retain: Set) { this.marked_diagnostics.forEach( @@ -400,6 +400,16 @@ export class Diagnostics extends CodeMirrorLSPFeature { // remove all markers this.remove_unused_diagnostic_markers(new Set()); this.diagnostics_db.clear(); + diagnostics_databases.delete(this.virtual_editor); + this.unique_editor_ids.clear(); + + if ( + diagnostics_panel.content.model.virtual_editor === this.virtual_editor + ) { + diagnostics_panel.content.model.virtual_editor = null; + diagnostics_panel.content.model.diagnostics = null; + } + diagnostics_panel.update(); super.remove(); } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 6e6233866..6c6ef82a4 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -5,6 +5,8 @@ import { VirtualDocument } from '../../../virtual/document'; import { IRootPosition } from '../../../positioning'; import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; +const DEBUG = 0; + export class Highlights extends CodeMirrorLSPFeature { name = 'Highlights'; protected highlight_markers: CodeMirror.TextMarker[] = []; @@ -27,17 +29,16 @@ export class Highlights extends CodeMirrorLSPFeature { ]; register(): void { - this.connection_handlers.set('highlight', this.handleHighlight.bind(this)); - this.editor_handlers.set( - 'cursorActivity', - this.onCursorActivity.bind(this) - ); + this.connection_handlers.set('highlight', this.handleHighlight); + this.editor_handlers.set('cursorActivity', this.onCursorActivity); super.register(); } remove(): void { - super.remove(); + this.handleHighlight = null; + this.onCursorActivity = null; this.clear_markers(); + super.remove(); } protected clear_markers() { @@ -47,10 +48,10 @@ export class Highlights extends CodeMirrorLSPFeature { this.highlight_markers = []; } - protected handleHighlight( + protected handleHighlight = ( items: lsProtocol.DocumentHighlight[], documentUri: string - ) { + ) => { if (documentUri !== this.virtual_document.document_info.uri) { return; } @@ -71,9 +72,9 @@ export class Highlights extends CodeMirrorLSPFeature { ); this.highlight_markers.push(marker); } - } + }; - protected async onCursorActivity() { + protected onCursorActivity = async () => { let root_position: IRootPosition; try { @@ -81,7 +82,7 @@ export class Highlights extends CodeMirrorLSPFeature { .getDoc() .getCursor('start') as IRootPosition; } catch (err) { - console.warn('LSP: no root position available'); + DEBUG && console.warn('LSP: no root position available'); return; } @@ -89,10 +90,11 @@ export class Highlights extends CodeMirrorLSPFeature { try { document = this.virtual_editor.document_at_root_position(root_position); } catch (e) { - console.warn( - 'LSP: Could not obtain virtual document from position', - root_position - ); + DEBUG && + console.warn( + 'LSP: Could not obtain virtual document from position', + root_position + ); return; } if (document !== this.virtual_document) { @@ -109,7 +111,7 @@ export class Highlights extends CodeMirrorLSPFeature { ); this.handleHighlight(highlights, this.virtual_document.document_info.uri); } catch (e) { - console.warn('Could not get highlights:', e); + DEBUG && console.warn('Could not get highlights:', e); } - } + }; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index 228d9cc85..182ff1a1b 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -24,12 +24,12 @@ export class Hover extends CodeMirrorLSPFeature { private debounced_get_hover: Debouncer>; register(): void { - this.wrapper_handlers.set('mousemove', this.handleMouseOver.bind(this)); + this.wrapper_handlers.set('mousemove', this.handleMouseOver); this.wrapper_handlers.set( 'mouseleave', // TODO: remove_tooltip() but allow the mouse to leave if it enters the tooltip // (a bit tricky: normally we would just place the tooltip within, but it was designed to be attached to body) - this.remove_range_highlight.bind(this) + this.remove_range_highlight ); // show hover after pressing the modifier key this.wrapper_handlers.set('keydown', (event: KeyboardEvent) => { @@ -44,7 +44,7 @@ export class Hover extends CodeMirrorLSPFeature { ); } }); - this.connection_handlers.set('hover', this.handleHover.bind(this)); + this.connection_handlers.set('hover', this.handleHover); // TODO: make the debounce rate configurable this.debounced_get_hover = new Debouncer>( this.on_hover, @@ -96,7 +96,7 @@ export class Hover extends CodeMirrorLSPFeature { } } - public handleHover(response: lsProtocol.Hover, documentUri: string) { + public handleHover = (response: lsProtocol.Hover, documentUri: string) => { if (documentUri !== this.virtual_document.document_info.uri) { return; } @@ -136,7 +136,7 @@ export class Hover extends CodeMirrorLSPFeature { cm_editor, editor_position ); - } + }; protected is_token_empty(token: CodeMirror.Token) { return token.string.length === 0; @@ -188,7 +188,7 @@ export class Hover extends CodeMirrorLSPFeature { } } - public handleMouseOver(event: MouseEvent) { + public handleMouseOver = (event: MouseEvent) => { // proceed when no hover modifier or hover modifier pressed this.show_next_tooltip = !hover_modifier || getModifierState(event, hover_modifier); @@ -205,7 +205,7 @@ export class Hover extends CodeMirrorLSPFeature { throw e; } } - } + }; protected editor_range_for_hover(range: lsProtocol.Range): IEditorRange { let character = this.hover_character; @@ -246,13 +246,13 @@ export class Hover extends CodeMirrorLSPFeature { this.remove_range_highlight(); } - protected remove_range_highlight() { + protected remove_range_highlight = () => { if (this.hover_marker) { this.hover_marker.clear(); this.hover_marker = null; } this.last_hover_character = null; - } + }; remove(): void { this.remove_range_highlight(); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts index 5774dc326..c0b25ad7c 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts @@ -4,6 +4,8 @@ import { IVirtualPosition } from '../../../positioning'; import { uri_to_contents_path, uris_equal } from '../../../utils'; import { AnyLocation } from 'lsp-ws-connection/src/types'; +const DEBUG = 0; + export class JumpToDefinition extends CodeMirrorLSPFeature { name = 'JumpToDefinition'; static commands: Array = [ @@ -31,7 +33,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { get_uri_and_range(location_or_locations: AnyLocation) { if (location_or_locations == null) { - console.log('No jump targets found'); + DEBUG && console.log('No jump targets found'); return; } // some language servers appear to return a single object @@ -46,7 +48,8 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { return; } - console.log('Will jump to the first of suggested locations:', locations); + DEBUG && + console.log('Will jump to the first of suggested locations:', locations); const location_or_link = locations[0]; @@ -67,7 +70,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { const target_info = this.get_uri_and_range(location_or_locations); if (target_info == null) { - console.log('No jump targets found'); + DEBUG && console.log('No jump targets found'); } let { uri, range } = target_info; @@ -83,8 +86,8 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { virtual_position ); let editor_position_ce = PositionConverter.cm_to_ce(editor_position); - console.log(`Jumping to ${editor_index}th editor of ${uri}`); - console.log('Jump target within editor:', editor_position_ce); + DEBUG && console.log(`Jumping to ${editor_index}th editor of ${uri}`); + DEBUG && console.log('Jump target within editor:', editor_position_ce); this.jumper.jump({ token: { offset: this.jumper.getOffset(editor_position_ce, editor_index), @@ -95,9 +98,10 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { } else { // otherwise there is no virtual document and we expect the returned position to be source position: let source_position_ce = PositionConverter.cm_to_ce(virtual_position); - console.log(`Jumping to external file: ${uri}`); + DEBUG && console.log(`Jumping to external file: ${uri}`); - console.log('Jump target (source location):', source_position_ce); + DEBUG && + console.log('Jump target (source location):', source_position_ce); // can it be resolved vs our guessed server root? const contents_path = uri_to_contents_path(uri); @@ -125,7 +129,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { this.jumper.global_jump({ uri, ...jump_data }, false); return; } catch (err) { - console.warn(err); + DEBUG && console.warn(err); } this.jumper.global_jump( diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts index e0af38dff..abe2aaddd 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts @@ -9,6 +9,8 @@ import { Diagnostics } from './diagnostics'; import { VirtualEditor } from '../../../virtual/editor'; import { VirtualEditorForNotebook } from '../../../virtual/editors/notebook'; +const DEBUG = 0; + export class Rename extends CodeMirrorLSPFeature { name = 'Rename'; static commands: Array = [ @@ -88,7 +90,7 @@ export class Rename extends CodeMirrorLSPFeature { this.status_message.set(status, 5 * 1000); } catch (error) { - console.warn(error); + DEBUG && console.warn(error); } return outcome; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts index 8edd4f1c3..b4fa08227 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts @@ -3,6 +3,8 @@ import { IRootPosition } from '../../../positioning'; import * as CodeMirror from 'codemirror'; import { CodeMirrorLSPFeature } from '../feature'; +const DEBUG = 0; + export class Signature extends CodeMirrorLSPFeature { name = 'Signature'; protected signature_character: IRootPosition; @@ -69,10 +71,11 @@ export class Signature extends CodeMirrorLSPFeature { } } else { if (item.documentation.kind !== 'markdown') { - this.virtual_editor.console.warn( - 'Unknown MarkupContent kind:', - item.documentation.kind - ); + DEBUG && + this.virtual_editor.console.warn( + 'Unknown MarkupContent kind:', + item.documentation.kind + ); } markdown += item.documentation.value; } @@ -83,7 +86,7 @@ export class Signature extends CodeMirrorLSPFeature { private handleSignature(response: lsProtocol.SignatureHelp) { this.jupyterlab_components.remove_tooltip(); - this.virtual_editor.console.log('Signature received', response); + DEBUG && this.virtual_editor.console.log('Signature received', response); if (!this.signature_character || !response || !response.signatures.length) { return; } @@ -96,12 +99,13 @@ export class Signature extends CodeMirrorLSPFeature { let language = this.get_language_at(editor_position, cm_editor); let markup = this.get_markup_for_signature_help(response, language); - this.virtual_editor.console.log( - 'Signature will be shown', - language, - markup, - root_position - ); + DEBUG && + this.virtual_editor.console.log( + 'Signature will be shown', + language, + markup, + root_position + ); let tooltip = this.jupyterlab_components.create_tooltip( markup, @@ -131,10 +135,11 @@ export class Signature extends CodeMirrorLSPFeature { root_position ); - this.virtual_editor.console.log( - 'Signature will be requested for', - virtual_position - ); + DEBUG && + this.virtual_editor.console.log( + 'Signature will be requested for', + virtual_position + ); this.connection .getSignatureHelp( diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index c9ebf9000..57d43e54e 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -23,6 +23,8 @@ import createNotebook = NBTestUtils.createNotebook; import { CodeMirrorAdapter } from './cm_adapter'; import { VirtualDocument } from '../../virtual/document'; +const DEBUG = 0; + interface IFeatureTestEnvironment { host: HTMLElement; virtual_editor: VirtualEditor; @@ -251,6 +253,6 @@ export async function synchronize_content( try { await adapter.updateAfterChange(); } catch (e) { - console.warn(e); + DEBUG && console.warn(e); } } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index c55289323..9b9582aae 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -21,6 +21,8 @@ import { } from '../../../positioning'; import { LSPConnection } from '../../../connection'; +const DEBUG = 0; + /* Feedback: anchor - not clear from docs bundle - very not clear from the docs, interface or better docs would be nice to have @@ -34,8 +36,9 @@ export class LSPConnector extends DataConnector< void, CompletionHandler.IRequest > { - private readonly _editor: CodeEditor.IEditor; - private readonly _connections: Map; + isDisposed = false; + private _editor: CodeEditor.IEditor; + private _connections: Map; private _context_connector: ContextConnector; private _kernel_connector: KernelConnector; private _kernel_and_context_connector: CompletionConnector; @@ -67,6 +70,20 @@ export class LSPConnector extends DataConnector< this.options = options; } + dispose() { + if (this.isDisposed) { + return; + } + this._connections = null; + this.virtual_editor = null; + this._context_connector = null; + this._kernel_connector = null; + this._kernel_and_context_connector = null; + this.options = null; + this._editor = null; + this.isDisposed = true; + } + protected get _has_kernel(): boolean { return this.options.session?.kernel != null; } @@ -101,7 +118,7 @@ export class LSPConnector extends DataConnector< const token = editor.getTokenForPosition(cursor); if (this.suppress_auto_invoke_in.indexOf(token.type) !== -1) { - console.log('Suppressing completer auto-invoke in', token.type); + DEBUG && console.log('Suppressing completer auto-invoke in', token.type); return; } @@ -165,11 +182,11 @@ export class LSPConnector extends DataConnector< document, position_in_token ).catch(e => { - console.warn('LSP: hint failed', e); + DEBUG && console.warn('LSP: hint failed', e); return this.fallback_connector.fetch(request); }); } catch (e) { - console.warn('LSP: kernel completions failed', e); + DEBUG && console.warn('LSP: kernel completions failed', e); return this.fallback_connector.fetch(request); } } @@ -191,7 +208,7 @@ export class LSPConnector extends DataConnector< // to the matches... // Suggested in https://github.com/jupyterlab/jupyterlab/issues/7044, TODO PR - console.log('[LSP][Completer] Token:', token); + DEBUG && console.log('[LSP][Completer] Token:', token); let completion_items = ((await connection.getCompletion( cursor, @@ -278,7 +295,7 @@ export class LSPConnector extends DataConnector< } else if (lsp.matches.length === 0) { return kernel; } - console.log('[LSP][Completer] Merging completions:', lsp, kernel); + DEBUG && console.log('[LSP][Completer] Merging completions:', lsp, kernel); // Populate the result with a copy of the lsp matches. const matches = lsp.matches.slice(); @@ -298,9 +315,9 @@ export class LSPConnector extends DataConnector< const cursor = editor.getCursorPosition(); const line = editor.getLine(cursor.line); prefix = line.substring(kernel.start, lsp.start); - console.log('[LSP][Completer] Removing kernel prefix: ', prefix); + DEBUG && console.log('[LSP][Completer] Removing kernel prefix: ', prefix); } else if (lsp.start < kernel.start) { - console.warn('[LSP][Completer] Kernel start > LSP start'); + DEBUG && console.warn('[LSP][Completer] Kernel start > LSP start'); } let remove_prefix = (value: string) => { diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx index 8d2b9323f..d48aadfb3 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/statusbar.tsx @@ -22,6 +22,7 @@ import { JupyterLabWidgetAdapter } from '../jl_adapter'; import { collect_documents, VirtualDocument } from '../../../virtual/document'; import { LSPConnection } from '../../../connection'; import { PageConfig } from '@jupyterlab/coreutils'; +import { DocumentConnectionManager } from '../../../connection_manager'; interface IServerStatusProps { server: SCHEMA.LanguageServerSession; @@ -62,16 +63,13 @@ class CollapsibleList extends React.Component< constructor(props: any) { super(props); this.state = { isCollapsed: props.startCollapsed || false }; - - // This binding is necessary to make `this` work in the callback - this.handleClick = this.handleClick.bind(this); } - handleClick() { + handleClick = () => { this.setState(state => ({ isCollapsed: !state.isCollapsed })); - } + }; render() { return ( @@ -98,7 +96,7 @@ class LSPPopup extends VDomRenderer { this.addClass('lsp-popover'); } render() { - if (!this.model) { + if (!this.model?.connection_manager) { return null; } const servers_available = this.model.servers_available_not_in_use.map( @@ -118,7 +116,7 @@ class LSPPopup extends VDomRenderer { // TODO: stop button // TODO: add a config buttons next to the language header let list = documents.map((document, i) => { - let connection = this.model.adapter.connection_manager.connections.get( + let connection = this.model.connection_manager.connections.get( document.id_path ); @@ -315,6 +313,7 @@ export namespace LSPStatus { */ export class Model extends VDomModel { server_extension_status: SCHEMA.ServersResponse = null; + private _connection_manager: DocumentConnectionManager; constructor() { super(); @@ -425,13 +424,12 @@ export namespace LSPStatus { } get status(): IStatus { - let connection_manager = this.adapter.connection_manager; - const detected_documents = connection_manager.documents; + const detected_documents = this._connection_manager.documents; let connected_documents = new Set(); let initialized_documents = new Set(); detected_documents.forEach((document, id_path) => { - let connection = connection_manager.connections.get(id_path); + let connection = this._connection_manager.connections.get(id_path); if (!connection) { return; } @@ -447,7 +445,7 @@ export namespace LSPStatus { // there may be more open connections than documents if a document was recently closed // and the grace period has not passed yet let open_connections = new Array(); - connection_manager.connections.forEach((connection, path) => { + this._connection_manager.connections.forEach((connection, path) => { if (connection.isConnected) { open_connections.push(connection); } @@ -489,7 +487,7 @@ export namespace LSPStatus { } get feature_message(): string { - return this.adapter ? this.adapter.status_message.message : ''; + return this.adapter?.status_message?.message || ''; } get long_message(): string { @@ -538,31 +536,41 @@ export namespace LSPStatus { } set adapter(adapter: JupyterLabWidgetAdapter | null) { - const oldAdapter = this._adapter; - - if (oldAdapter != null) { - oldAdapter.connection_manager.connected.disconnect(this._onChange); - oldAdapter.connection_manager.initialized.connect(this._onChange); - oldAdapter.connection_manager.disconnected.disconnect(this._onChange); - oldAdapter.connection_manager.closed.disconnect(this._onChange); - oldAdapter.connection_manager.documents_changed.disconnect( - this._onChange - ); - oldAdapter.status_message.changed.connect(this._onChange); + if (this._adapter != null) { + this._adapter.status_message.changed.connect(this._onChange); } if (adapter != null) { - adapter.connection_manager.connected.connect(this._onChange); - adapter.connection_manager.initialized.connect(this._onChange); - adapter.connection_manager.disconnected.connect(this._onChange); - adapter.connection_manager.closed.connect(this._onChange); - adapter.connection_manager.documents_changed.connect(this._onChange); adapter.status_message.changed.connect(this._onChange); } this._adapter = adapter; } + get connection_manager() { + return this._connection_manager; + } + + set connection_manager(connection_manager) { + if (this._connection_manager != null) { + this._connection_manager.connected.disconnect(this._onChange); + this._connection_manager.initialized.connect(this._onChange); + this._connection_manager.disconnected.disconnect(this._onChange); + this._connection_manager.closed.disconnect(this._onChange); + this._connection_manager.documents_changed.disconnect(this._onChange); + } + + if (connection_manager != null) { + connection_manager.connected.connect(this._onChange); + connection_manager.initialized.connect(this._onChange); + connection_manager.disconnected.connect(this._onChange); + connection_manager.closed.connect(this._onChange); + connection_manager.documents_changed.connect(this._onChange); + } + + this._connection_manager = connection_manager; + } + private _onChange = () => { this.stateChanged.emit(void 0); }; diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts index 4bb4ce729..a65bd6367 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts @@ -12,6 +12,8 @@ import { CodeEditor } from '@jupyterlab/codeeditor'; import { VirtualFileEditor } from '../../virtual/editors/file_editor'; import { DocumentConnectionManager } from '../../connection_manager'; +const DEBUG = 0; + export class FileEditorAdapter extends JupyterLabWidgetAdapter { editor: FileEditor; jumper: FileEditorJumper; @@ -69,14 +71,12 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { ); this.connect_contentChanged_signal(); - console.log('LSP: file ready for connection:', this.path); + DEBUG && console.log('LSP: file ready for connection:', this.path); this.connect_document(this.virtual_editor.virtual_document).catch( console.warn ); - this.editor.model.mimeTypeChanged.connect( - this.reload_connection.bind(this) - ); + this.editor.model.mimeTypeChanged.connect(this.reload_connection, this); } connect_completion() { diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index c074d2676..483674578 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -12,7 +12,7 @@ import * as lsProtocol from 'vscode-languageserver-protocol'; import { FreeTooltip } from './components/free_tooltip'; import { Widget } from '@phosphor/widgets'; import { VirtualEditor } from '../../virtual/editor'; -import { VirtualDocument } from '../../virtual/document'; +import { VirtualDocument, IForeignContext } from '../../virtual/document'; import { Signal } from '@phosphor/signaling'; import { IEditorPosition, IRootPosition } from '../../positioning'; import { LSPConnection } from '../../connection'; @@ -33,6 +33,8 @@ import { } from '../../connection_manager'; import { Rename } from '../codemirror/features/rename'; +const DEBUG = 0; + export const lsp_features: Array = [ Completion, Diagnostics, @@ -78,14 +80,14 @@ export class StatusMessage { this.message = message; this.changed.emit(''); if (timeout == null && timeout !== -1) { - setTimeout(this.cleanup.bind(this), timeout); + setTimeout(this.cleanup, timeout); } } - cleanup() { + cleanup = () => { this.message = ''; this.changed.emit(''); - } + }; } /** @@ -144,10 +146,11 @@ export abstract class JupyterLabWidgetAdapter manager: DocumentConnectionManager, { virtual_document }: IDocumentConnectionData ) { - console.log( - 'LSP: connection closed, disconnecting adapter', - virtual_document.id_path - ); + DEBUG && + console.log( + 'LSP: connection closed, disconnecting adapter', + virtual_document.id_path + ); if (virtual_document !== this.virtual_editor?.virtual_document) { return; } @@ -171,10 +174,29 @@ export abstract class JupyterLabWidgetAdapter this.update_documents, this ); - this.adapters.forEach(adapter => adapter.remove()); + for (let adapter of this.adapters.values()) { + adapter.dispose(); + } this.adapters.clear(); + + this.connection_manager.disconnect_document_signals( + this.virtual_editor.virtual_document + ); this.virtual_editor.dispose(); - this.virtual_editor = void 0; + + this.current_completion_connector.dispose(); + + // just to be sure + this.virtual_editor = null; + this.app = null; + this.widget = null; + this._tooltip = null; + this.connection_manager = null; + this.current_completion_connector = null; + this.rendermime_registry = null; + this.widget = null; + + // actually disposed this.isDisposed = true; } @@ -267,24 +289,52 @@ export abstract class JupyterLabWidgetAdapter // refresh the document on the LSP server this.document_changed(virtual_document, virtual_document, true); - console.log( - 'LSP: virtual document(s) for', - this.document_path, - 'have been initialized' - ); + DEBUG && + console.log( + 'LSP: virtual document(s) for', + this.document_path, + 'have been initialized' + ); }); } protected async connect_document(virtual_document: VirtualDocument) { virtual_document.changed.connect(this.document_changed, this); - virtual_document.foreign_document_opened.connect(async (host, context) => { - this.connect_document(context.foreign_document).catch(console.warn); - }); + virtual_document.foreign_document_opened.connect( + this.on_foreign_document_opened, + this + ); await this.connect(virtual_document).catch(console.warn); } + protected async on_foreign_document_opened( + host: VirtualDocument, + context: IForeignContext + ) { + const { foreign_document } = context; + this.connect_document(foreign_document).catch(console.warn); + + foreign_document.foreign_document_closed.connect( + this.on_foreign_document_closed, + this + ); + } + + on_foreign_document_closed(host: VirtualDocument, context: IForeignContext) { + const { foreign_document } = context; + foreign_document.foreign_document_closed.disconnect( + this.on_foreign_document_closed, + this + ); + foreign_document.foreign_document_opened.disconnect( + this.on_foreign_document_opened, + this + ); + foreign_document.changed.disconnect(this.document_changed, this); + } + document_changed( virtual_document: VirtualDocument, document: VirtualDocument, @@ -297,19 +347,24 @@ export abstract class JupyterLabWidgetAdapter let adapter = this.adapters.get(virtual_document.id_path); if (!connection?.isReady) { - console.log('LSP: Skipping document update signal: connection not ready'); + DEBUG && + console.log( + 'LSP: Skipping document update signal: connection not ready' + ); return; } if (adapter == null) { - console.log('LSP: Skipping document update signal: adapter not ready'); + DEBUG && + console.log('LSP: Skipping document update signal: adapter not ready'); return; } - console.log( - 'LSP: virtual document', - virtual_document.id_path, - 'has changed sending update' - ); + DEBUG && + console.log( + 'LSP: virtual document', + virtual_document.id_path, + 'has changed sending update' + ); connection.sendFullTextChange( virtual_document.value, virtual_document.document_info @@ -344,7 +399,7 @@ export abstract class JupyterLabWidgetAdapter let adapter = this.adapters.get(virtual_document.id_path); this.adapters.delete(virtual_document.id_path); if (adapter != null) { - adapter.remove(); + adapter.dispose(); } } @@ -356,9 +411,10 @@ export abstract class JupyterLabWidgetAdapter private async connect(virtual_document: VirtualDocument) { let language = virtual_document.language; - console.log( - `LSP: will connect using root path: ${this.root_path} and language: ${language}` - ); + DEBUG && + console.log( + `LSP: will connect using root path: ${this.root_path} and language: ${language}` + ); let options = { virtual_document, @@ -416,7 +472,7 @@ export abstract class JupyterLabWidgetAdapter this, adapter_features ); - console.log('LSP: Adapter for', this.document_path, 'is ready.'); + DEBUG && console.log('LSP: Adapter for', this.document_path, 'is ready.'); return adapter; } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 5e9eccf5c..da323c584 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -18,6 +18,8 @@ import { DocumentConnectionManager } from '../../connection_manager'; import { IClientSession } from '@jupyterlab/apputils'; import { Session } from '@jupyterlab/services'; +const DEBUG = 0; + export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; widget: NotebookPanel; @@ -60,21 +62,22 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { change: Session.IKernelChangedArgs ) { if (!change.newValue) { - console.log('LSP: kernel was shut down'); + DEBUG && console.log('LSP: kernel was shut down'); return; } change.newValue.ready .then(async spec => { - console.log( - 'LSP: Changed to ' + - change.newValue.info.language_info.name + - ' kernel, reconnecting' - ); - await until_ready(this.is_ready.bind(this), -1); + DEBUG && + console.log( + 'LSP: Changed to ' + + change.newValue.info.language_info.name + + ' kernel, reconnecting' + ); + await until_ready(this.is_ready, -1); this.reload_connection(); }) .catch(e => { - console.warn(e); + DEBUG && console.warn(e); // try to reconnect anyway this.reload_connection(); }); @@ -89,10 +92,15 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { this ); this.widget.content.activeCellChanged.disconnect(this.on_completions, this); + for (const handler of this.completion_handlers.values()) { + handler.connector = null; + handler.editor = null; + } + this.completion_handlers.clear(); super.dispose(); } - is_ready() { + is_ready = () => { return ( !this.widget.isDisposed && this.widget.context.isReady && @@ -101,7 +109,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { this.widget.context.session.kernel !== null && this.language_info() !== null ); - } + }; get document_path(): string { return this.widget.context.path; @@ -111,7 +119,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { try { return this.widget.context.session.kernel.info.language_info; } catch (e) { - console.log('LSP: Could not get kernel metadata'); + DEBUG && console.log('LSP: Could not get kernel metadata'); return null; } } @@ -138,9 +146,10 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { } async init_once_ready() { - console.log('LSP: waiting for', this.document_path, 'to fully load'); - await until_ready(this.is_ready.bind(this), -1); - console.log('LSP:', this.document_path, 'ready for connection'); + DEBUG && + console.log('LSP: waiting for', this.document_path, 'to fully load'); + await until_ready(this.is_ready, -1); + DEBUG && console.log('LSP:', this.document_path, 'ready for connection'); this.virtual_editor = new VirtualEditorForNotebook( this.widget.content, diff --git a/packages/jupyterlab-lsp/src/command_manager.ts b/packages/jupyterlab-lsp/src/command_manager.ts index a1612b26f..ce727fa22 100644 --- a/packages/jupyterlab-lsp/src/command_manager.ts +++ b/packages/jupyterlab-lsp/src/command_manager.ts @@ -23,6 +23,8 @@ import { PositionConverter } from './converter'; export const file_editor_adapters: Map = new Map(); export const notebook_adapters: Map = new Map(); +const DEBUG = 0; + function is_context_menu_over_token(adapter: JupyterLabWidgetAdapter) { let position = adapter.get_position_from_context_menu(); if (!position) { @@ -149,10 +151,11 @@ export abstract class ContextCommandManager extends LSPCommandManager { try { context = this.current_adapter.get_context_from_context_menu(); } catch (e) { - console.warn( - 'contextMenu is attached, but could not get the context', - e - ); + DEBUG && + console.warn( + 'contextMenu is attached, but could not get the context', + e + ); context = null; } } @@ -172,7 +175,7 @@ export abstract class ContextCommandManager extends LSPCommandManager { command.is_enabled(context) ); } catch (e) { - console.warn('is_visible failed', e); + DEBUG && console.warn('is_visible failed', e); return false; } } diff --git a/packages/jupyterlab-lsp/src/connection.ts b/packages/jupyterlab-lsp/src/connection.ts index 6e7fbe5ee..442d948a7 100644 --- a/packages/jupyterlab-lsp/src/connection.ts +++ b/packages/jupyterlab-lsp/src/connection.ts @@ -11,6 +11,8 @@ import { } from 'lsp-ws-connection'; import { until_ready } from './utils'; +const DEBUG = 0; + interface ILSPOptions extends ILspOptions {} export class LSPConnection extends LspWsConnection { @@ -81,7 +83,7 @@ export class LSPConnection extends LspWsConnection { }); }) .catch(() => { - console.error('Could not connect onClose signal'); + DEBUG && console.error('Could not connect onClose signal'); }); return this; } diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index e78e5ffc8..77317a694 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -1,9 +1,15 @@ -import { VirtualDocument } from './virtual/document'; +import { VirtualDocument, IForeignContext } from './virtual/document'; import { LSPConnection } from './connection'; + import { Signal } from '@phosphor/signaling'; import { PageConfig, URLExt } from '@jupyterlab/coreutils'; import { sleep, until_ready } from './utils'; +// Name-only import so as to not trigger inclusion in main bundle +import * as ConnectionModuleType from './connection'; + +const DEBUG = 0; + export interface IDocumentConnectionData { virtual_document: VirtualDocument; connection: LSPConnection; @@ -63,35 +69,58 @@ export class DocumentConnectionManager { } connect_document_signals(virtual_document: VirtualDocument) { - virtual_document.foreign_document_opened.connect((host, context) => { - console.log( - 'LSP: ConnectionManager received foreign document: ', - context.foreign_document.id_path - ); - }); + virtual_document.foreign_document_opened.connect( + this.on_foreign_document_opened, + this + ); virtual_document.foreign_document_closed.connect( - (host, { foreign_document }) => { - const connection = this.connections.get(foreign_document.id_path); - if (connection) { - connection.close(); - this.connections.delete(foreign_document.id_path); - } else { - console.warn('LSP: no connection for', foreign_document); - } - this.documents.delete(foreign_document.id_path); - this.documents_changed.emit(this.documents); - } + this.on_foreign_document_closed, + this ); this.documents.set(virtual_document.id_path, virtual_document); this.documents_changed.emit(this.documents); } + disconnect_document_signals(virtual_document: VirtualDocument, emit = true) { + virtual_document.foreign_document_opened.disconnect( + this.on_foreign_document_opened, + this + ); + + virtual_document.foreign_document_closed.disconnect( + this.on_foreign_document_closed, + this + ); + + this.documents.delete(virtual_document.id_path); + for (const foreign of virtual_document.foreign_documents.values()) { + this.disconnect_document_signals(foreign, false); + } + + if (emit) { + this.documents_changed.emit(this.documents); + } + } + + on_foreign_document_opened(_host: VirtualDocument, context: IForeignContext) { + DEBUG && + console.log( + 'LSP: ConnectionManager received foreign document: ', + context.foreign_document.id_path + ); + } + + on_foreign_document_closed(_host: VirtualDocument, context: IForeignContext) { + const { foreign_document } = context; + this.disconnect_document_signals(foreign_document); + } + private async connect_socket( options: ISocketConnectionOptions ): Promise { - console.log('LSP: Connection Socket', options); + DEBUG && console.log('LSP: Connection Socket', options); let { virtual_document, language } = options; this.connect_document_signals(virtual_document); @@ -101,44 +130,77 @@ export class DocumentConnectionManager { language ); - const connection = await Private.connection(language, uris); + const connection = await Private.connection( + language, + uris, + this.on_new_connection + ); + if (connection.isReady) { + connection.sendOpen(virtual_document.document_info); + } + + this.connections.set(virtual_document.id_path, connection); + + return connection; + } + + on_new_connection = (connection: LSPConnection) => { connection.on('error', e => { - console.warn(e); + DEBUG && console.warn(e); // TODO invalid now let error: Error = e.length && e.length >= 1 ? e[0] : new Error(); // TODO: those codes may be specific to my proxy client, need to investigate if (error.message.indexOf('code = 1005') !== -1) { - console.warn('LSP: Connection failed for ' + virtual_document.id_path); - console.warn('LSP: disconnecting ' + virtual_document.id_path); - this.closed.emit({ connection, virtual_document }); - this.ignored_languages.add(virtual_document.language); - console.warn( - `Cancelling further attempts to connect ${virtual_document.id_path} and other documents for this language (no support from the server)` - ); + DEBUG && console.warn(`LSP: Connection failed for ${connection}`); + this.forEachDocumentOfConnection(connection, virtual_document => { + DEBUG && + console.warn('LSP: disconnecting ' + virtual_document.id_path); + this.closed.emit({ connection, virtual_document }); + this.ignored_languages.add(virtual_document.language); + DEBUG && + console.warn( + `Cancelling further attempts to connect ${virtual_document.id_path} and other documents for this language (no support from the server)` + ); + }); } else if (error.message.indexOf('code = 1006') !== -1) { - console.warn( - 'LSP: Connection closed by the server ' + virtual_document.id_path - ); + DEBUG && console.warn('LSP: Connection closed by the server '); } else { - console.error( - 'LSP: Connection error of ' + virtual_document.id_path + ':', - e - ); + DEBUG && console.error('LSP: Connection error:', e); } }); - if (connection.isReady) { - connection.sendOpen(virtual_document.document_info); - } else { - connection.on('serverInitialized', () => { - connection.sendOpen(virtual_document.document_info); + connection.on('serverInitialized', capabilities => { + this.forEachDocumentOfConnection(connection, virtual_document => { + this.initialized.emit({ connection, virtual_document }); }); - } + }); - this.connections.set(virtual_document.id_path, connection); + connection.on('close', closed_manually => { + if (!closed_manually) { + DEBUG && console.warn('LSP: Connection unexpectedly disconnected'); + } else { + DEBUG && console.warn('LSP: Connection closed'); + this.forEachDocumentOfConnection(connection, virtual_document => { + this.closed.emit({ connection, virtual_document }); + }); + } + }); + }; - return connection; + private forEachDocumentOfConnection( + connection: LSPConnection, + callback: (virtual_document: VirtualDocument) => void + ) { + for (const [ + virtual_document_id_path, + a_connection + ] of this.connections.entries()) { + if (connection !== a_connection) { + continue; + } + callback(this.documents.get(virtual_document_id_path)); + } } public async retry_to_connect( @@ -161,12 +223,13 @@ export class DocumentConnectionManager { success = true; }) .catch(e => { - console.warn(e); + DEBUG && console.warn(e); }); - console.log( - 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' - ); + DEBUG && + console.log( + 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' + ); await sleep(interval); // gradually increase the time delay, up to 5 sec @@ -175,33 +238,30 @@ export class DocumentConnectionManager { } async connect(options: ISocketConnectionOptions) { - console.log('LSP: connection requested', options); + DEBUG && console.log('LSP: connection requested', options); let connection = await this.connect_socket(options); - connection.on('serverInitialized', capabilities => { - this.initialized.emit({ connection, virtual_document }); - }); - let { virtual_document, document_path } = options; - connection.on('close', closed_manually => { - if (!closed_manually) { - console.warn('LSP: Connection unexpectedly disconnected'); - this.retry_to_connect(options, 0.5).catch(console.warn); - } else { - console.warn('LSP: Connection closed'); - this.closed.emit({ connection, virtual_document }); + if (!connection.isReady) { + try { + await until_ready(() => connection.isReady, 100, 200); + } catch { + DEBUG && + console.warn( + `LSP: Connect timed out for ${virtual_document.id_path}` + ); + return; } - }); - - try { - await until_ready(() => connection.isReady, 100, 200); - } catch { - console.warn(`LSP: Connect timed out for ${virtual_document.id_path}`); - return; } - console.log('LSP:', document_path, virtual_document.id_path, 'connected.'); + DEBUG && + console.log( + 'LSP:', + document_path, + virtual_document.id_path, + 'connected.' + ); this.connected.emit({ connection, virtual_document }); @@ -246,14 +306,20 @@ export namespace DocumentConnectionManager { namespace Private { const _connections = new Map(); + let _promise: Promise; export async function connection( language: string, - uris: DocumentConnectionManager.IURIs + uris: DocumentConnectionManager.IURIs, + onCreate: (connection: LSPConnection) => void ): Promise { - const { LSPConnection } = await import( - /* webpackChunkName: "jupyter-lsp-connection" */ './connection' - ); + if (_promise == null) { + _promise = import( + /* webpackChunkName: "jupyter-lsp-connection" */ './connection' + ); + } + + const { LSPConnection } = await _promise; let connection = _connections.get(language); if (connection == null) { @@ -265,6 +331,7 @@ namespace Private { }); _connections.set(language, connection); connection.connect(socket); + onCreate(connection); } connection = _connections.get(language); diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index a21ff7434..8743fe306 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -36,6 +36,8 @@ import { LSPStatus } from './adapters/jupyterlab/components/statusbar'; import { IDocumentWidget } from '@jupyterlab/docregistry/lib/registry'; import { DocumentConnectionManager } from './connection_manager'; +const DEBUG = 0; + const lsp_commands: Array = [].concat( ...lsp_features.map(feature => feature.commands) ); @@ -73,6 +75,7 @@ const plugin: JupyterFrontEndPlugin = { const connection_manager = new DocumentConnectionManager(); const status_bar_item = new LSPStatus(); + status_bar_item.model.connection_manager = connection_manager; labShell.currentChanged.connect(() => { const current = labShell.currentWidget; @@ -108,8 +111,8 @@ const plugin: JupyterFrontEndPlugin = { ); fileEditorTracker.widgetUpdated.connect((_sender, _widget) => { - // console.log(sender); - // console.log(widget); + DEBUG && console.log(_sender); + DEBUG && console.log(_widget); // TODO? // adapter.remove(); // connection.close(); @@ -129,6 +132,17 @@ const plugin: JupyterFrontEndPlugin = { connection_manager ); file_editor_adapters.set(fileEditor.id, adapter); + + file_editor_adapters.set(widget.id, adapter); + const disconnect = () => { + file_editor_adapters.delete(widget.id); + widget.disposed.disconnect(disconnect); + adapter.dispose(); + if (status_bar_item.model.adapter === adapter) { + status_bar_item.model.adapter = null; + } + }; + widget.disposed.connect(disconnect); } }); @@ -152,6 +166,15 @@ const plugin: JupyterFrontEndPlugin = { connection_manager ); notebook_adapters.set(widget.id, adapter); + const disconnect = () => { + notebook_adapters.delete(widget.id); + widget.disposed.disconnect(disconnect); + adapter.dispose(); + if (status_bar_item.model.adapter === adapter) { + status_bar_item.model.adapter = null; + } + }; + widget.disposed.connect(disconnect); }); // position context menu entries after 10th but before 11th default entry @@ -193,7 +216,7 @@ const plugin: JupyterFrontEndPlugin = { }); }) .catch((reason: Error) => { - console.error(reason.message); + DEBUG && console.error(reason.message); }); }, autoStart: true diff --git a/packages/jupyterlab-lsp/src/utils.ts b/packages/jupyterlab-lsp/src/utils.ts index 849b787d4..f06adf2c4 100644 --- a/packages/jupyterlab-lsp/src/utils.ts +++ b/packages/jupyterlab-lsp/src/utils.ts @@ -1,5 +1,7 @@ import { PageConfig } from '@jupyterlab/coreutils'; +const DEBUG = 0; + const RE_PATH_ANCHOR = /^file:\/\/([^\/]+|\/[A-Z]:)/; export async function sleep(timeout: number) { @@ -59,9 +61,10 @@ export function getModifierState( case 'Meta': return event.metaKey; default: - console.warn( - `State of the modifier key "${modifierKey}" could not be determined.` - ); + DEBUG && + console.warn( + `State of the modifier key "${modifierKey}" could not be determined.` + ); } } diff --git a/packages/jupyterlab-lsp/src/virtual/console.ts b/packages/jupyterlab-lsp/src/virtual/console.ts index 0f2f884ac..1b625ed18 100644 --- a/packages/jupyterlab-lsp/src/virtual/console.ts +++ b/packages/jupyterlab-lsp/src/virtual/console.ts @@ -1,5 +1,7 @@ import '../../style/console.css'; +const DEBUG = 0; + export abstract class EditorLogConsole { abstract log(...args: any[]): void; abstract warn(...args: any[]): void; @@ -43,13 +45,13 @@ export class FloatingConsole extends EditorLogConsole { export class BrowserConsole extends EditorLogConsole { log(...args: any[]) { - console.log('LSP: ', ...args); + DEBUG && console.log('LSP: ', ...args); } warn(...args: any[]) { - console.warn('LSP: ', ...args); + DEBUG && console.warn('LSP: ', ...args); } error(...args: any[]) { - console.error('LSP: ', ...args); + DEBUG && console.error('LSP: ', ...args); } } diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index f9f29070f..513fc8370 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -18,6 +18,8 @@ import { IDocumentInfo } from 'lsp-ws-connection/src'; import { DocumentConnectionManager } from '../connection_manager'; +const DEBUG = 0; + type language = string; interface IVirtualLine { @@ -52,7 +54,7 @@ interface ISourceLine { foreign_documents_map: Map; } -interface IForeignContext { +export interface IForeignContext { foreign_document: VirtualDocument; parent_host: VirtualDocument; } @@ -138,6 +140,7 @@ export class VirtualDocument { public foreign_document_opened: Signal; public readonly instance_id: number; public standalone: boolean; + isDisposed = false; /** * the remote document uri, version and other server-related info */ @@ -180,7 +183,7 @@ export class VirtualDocument { standalone: boolean, public file_extension: string, public has_lsp_supported_file: boolean, - readonly parent?: VirtualDocument + public parent?: VirtualDocument ) { this.language = language; let overrides = @@ -215,6 +218,39 @@ export class VirtualDocument { this.clear(); } + dispose() { + if (this.isDisposed) { + return; + } + + this.parent = null; + + for (const doc of this.foreign_documents.values()) { + doc.dispose(); + } + + this.close_all_foreign_documents(); + + // clear all the maps + this.foreign_documents.clear(); + this.source_lines.clear(); + this.unused_documents.clear(); + this.unused_standalone_documents.clear(); + this.virtual_lines.clear(); + + // just to be sure + this.cell_magics_overrides = null; + this.document_info = null; + this.foreign_extractors = null; + this.foreign_extractors_registry = null; + this.line_magics_overrides = null; + this.lines = null; + this.overrides_registry = null; + + // actually disposed now + this.isDisposed = true; + } + /** * When this counter goes down to 0, the document will be destroyed and the associated connection will be closed; * This is meant to reduce the number of open connections when a a foreign code snippet was removed from the document. @@ -294,8 +330,8 @@ export class VirtualDocument { }; this.foreign_document_opened.emit(context); // pass through any future signals - document.foreign_document_closed.connect(this.forward_closed_signal); - document.foreign_document_opened.connect(this.forward_opened_signal); + document.foreign_document_closed.connect(this.forward_closed_signal, this); + document.foreign_document_opened.connect(this.forward_opened_signal, this); this.foreign_documents.set(document.virtual_id, document); @@ -580,7 +616,7 @@ export class VirtualDocument { } close_foreign(document: VirtualDocument) { - console.log('LSP: closing document', document); + DEBUG && console.log('LSP: closing document', document); this.foreign_document_closed.emit({ foreign_document: document, parent_host: this @@ -590,8 +626,14 @@ export class VirtualDocument { // and delete the documents within it document.close_all_foreign_documents(); - document.foreign_document_closed.disconnect(this.forward_closed_signal); - document.foreign_document_opened.disconnect(this.forward_opened_signal); + document.foreign_document_closed.disconnect( + this.forward_closed_signal, + this + ); + document.foreign_document_opened.disconnect( + this.forward_opened_signal, + this + ); } close_all_foreign_documents() { diff --git a/packages/jupyterlab-lsp/src/virtual/editor.ts b/packages/jupyterlab-lsp/src/virtual/editor.ts index 202c6ff7f..8ca551d52 100644 --- a/packages/jupyterlab-lsp/src/virtual/editor.ts +++ b/packages/jupyterlab-lsp/src/virtual/editor.ts @@ -12,6 +12,8 @@ import { until_ready } from '../utils'; import { Signal } from '@phosphor/signaling'; import { EditorLogConsole, create_console } from './console'; +const DEBUG = 0; + export type CodeMirrorHandler = (instance: any, ...args: any[]) => void; type WrappedHandler = (instance: CodeMirror.Editor, ...args: any[]) => void; @@ -67,16 +69,25 @@ export abstract class VirtualEditor implements CodeMirror.Editor { if (this.isDisposed) { return; } + this.documents_updated.disconnect(this.on_updated, this); - this._event_wrappers.forEach((wrapped_handler, [eventName]) => { + for (let [[eventName], wrapped_handler] of this._event_wrappers.entries()) { this.forEveryBlockEditor(cm_editor => { cm_editor.off(eventName, wrapped_handler); }, false); - }); + } this._event_wrappers.clear(); + this.virtual_document.dispose(); + + // just to be sure + this.virtual_document = null; + this.overrides_registry = null; + this.foreign_code_extractors = null; + this.code_extractors = null; + this.isDisposed = true; } @@ -88,7 +99,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { try { root_document.close_expired_documents(); } catch (e) { - this.console.warn('LSP: Failed to close expired documents'); + DEBUG && this.console.warn('LSP: Failed to close expired documents'); } } @@ -123,7 +134,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { * @param fn - the callback to execute in update lock */ public async with_update_lock(fn: Function) { - this.console.log('Will enter update lock with', fn); + DEBUG && this.console.log('Will enter update lock with', fn); await until_ready(() => this.can_update(), 12, 10).then(() => { try { this.update_lock = true; @@ -144,6 +155,9 @@ export abstract class VirtualEditor implements CodeMirror.Editor { // defer the update by up to 50 ms (10 retrials * 5 ms break), // awaiting for the previous update to complete. await until_ready(() => this.can_update(), 10, 5).then(() => { + if (this.isDisposed) { + resolve(); + } try { this.is_update_in_progress = true; this.perform_documents_update(); @@ -151,7 +165,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { this.virtual_document.maybe_emit_changed(); resolve(); } catch (e) { - this.console.warn('Documents update failed:', e); + DEBUG && this.console.warn('Documents update failed:', e); reject(e); } finally { this.is_update_in_progress = false; @@ -215,10 +229,11 @@ export abstract class VirtualEditor implements CodeMirror.Editor { try { return handler(this, ...args); } catch (error) { - this.console.warn( - 'Wrapped handler (which should accept a CodeMirror Editor instance) failed', - { error, instance, args, this: this } - ); + DEBUG && + this.console.warn( + 'Wrapped handler (which should accept a CodeMirror Editor instance) failed', + { error, instance, args, this: this } + ); } }; this._event_wrappers.set([eventName, handler], wrapped_handler); diff --git a/packages/jupyterlab-lsp/src/virtual/editors/file_editor.ts b/packages/jupyterlab-lsp/src/virtual/editors/file_editor.ts index 6e50c168e..f19d63a3e 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/file_editor.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/file_editor.ts @@ -57,6 +57,9 @@ export class VirtualFileEditor extends VirtualEditor { } protected perform_documents_update(): void { + if (this.isDisposed) { + return; + } // it is sufficient to update the root document, all nested documents will follow (be re-generated) this.virtual_document.clear(); this.virtual_document.append_code_block( diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index 39d441cf8..90f203a57 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -11,6 +11,8 @@ import { IVirtualPosition } from '../../positioning'; +const DEBUG = 0; + // @ts-ignore class DocDispatcher implements CodeMirror.Doc { virtual_editor: VirtualEditorForNotebook; @@ -56,6 +58,8 @@ export class VirtualEditorForNotebook extends VirtualEditor { cm_editor_to_cell: Map; has_cells = true; + private _proxy: VirtualEditorForNotebook; + constructor( public notebook: Notebook, private wrapper: HTMLElement, @@ -78,23 +82,39 @@ export class VirtualEditorForNotebook extends VirtualEditor { this.overrides_registry = overrides_registry; this.code_extractors = foreign_code_extractors; this.language = language; - let handler = { + this._proxy = new Proxy(this, { get: function( target: VirtualEditorForNotebook, prop: keyof CodeMirror.Editor, receiver: any ) { if (!(prop in target)) { - console.warn( - `Unimplemented method ${prop} for VirtualEditorForNotebook` - ); + DEBUG && + console.warn( + `Unimplemented method ${prop} for VirtualEditorForNotebook` + ); return; } else { return Reflect.get(target, prop, receiver); } } - }; - return new Proxy(this, handler); + }); + return this._proxy; + } + + dispose() { + if (this.isDisposed) { + return; + } + + this.cm_editor_to_cell.clear(); + this.cell_to_corresponding_source_line.clear(); + + super.dispose(); + + // just to be sure + this.forEveryBlockEditor = null; + this._proxy = null; } transform_from_notebook_to_root( @@ -183,7 +203,7 @@ export class VirtualEditorForNotebook extends VirtualEditor { let editor = this.get_editor_at_root_line(pos); return editor.charCoords(pos, mode); } catch (e) { - console.log(e); + DEBUG && console.log(e); return { bottom: 0, left: 0, right: 0, top: 0 }; } } @@ -286,6 +306,10 @@ export class VirtualEditorForNotebook extends VirtualEditor { } protected perform_documents_update(): void { + if (this.isDisposed) { + return; + } + this.virtual_document.clear(); this.cell_to_corresponding_source_line.clear(); this.cm_editor_to_cell.clear(); diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index f149dfc60..796fdd121 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -16,6 +16,8 @@ import { AnyCompletion } from './types'; +const DEBUG = 0; + /** * Changes as compared to upstream: * - markdown is preferred over plaintext @@ -211,7 +213,7 @@ export class LspWsConnection extends events.EventEmitter this.onServerInitialized(params); }, e => { - console.warn('lsp-ws-connection initialization failure', e); + DEBUG && console.warn('lsp-ws-connection initialization failure', e); } ); } From 2145c42db50008b8feb2b447d3f6fb38ba304d44 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 10:42:28 -0500 Subject: [PATCH 49/63] clear up some more memory leaks --- .../src/adapters/codemirror/features/hover.ts | 7 ++++++- packages/jupyterlab-lsp/src/connection_manager.ts | 7 ++++--- packages/lsp-ws-connection/src/ws-connection.ts | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index 182ff1a1b..fbd7a1c4c 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -257,7 +257,12 @@ export class Hover extends CodeMirrorLSPFeature { remove(): void { this.remove_range_highlight(); this.debounced_get_hover.dispose(); - this.debounced_get_hover = null; super.remove(); + + // just to be sure + this.debounced_get_hover = null; + this.remove_range_highlight = null; + this.handleHover = null; + this.on_hover = null; } } diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 77317a694..621508a90 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -136,12 +136,12 @@ export class DocumentConnectionManager { this.on_new_connection ); + this.connections.set(virtual_document.id_path, connection); + if (connection.isReady) { connection.sendOpen(virtual_document.document_info); } - this.connections.set(virtual_document.id_path, connection); - return connection; } @@ -172,6 +172,7 @@ export class DocumentConnectionManager { connection.on('serverInitialized', capabilities => { this.forEachDocumentOfConnection(connection, virtual_document => { + connection.sendOpen(virtual_document.document_info); this.initialized.emit({ connection, virtual_document }); }); }); @@ -245,7 +246,7 @@ export class DocumentConnectionManager { if (!connection.isReady) { try { - await until_ready(() => connection.isReady, 100, 200); + await until_ready(() => connection.isReady, 200, 200); } catch { DEBUG && console.warn( diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index 796fdd121..a72686872 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -218,7 +218,7 @@ export class LspWsConnection extends events.EventEmitter ); } - public sendOpen(documentInfo: IDocumentInfo) { + sendOpen(documentInfo: IDocumentInfo) { const textDocumentMessage: protocol.DidOpenTextDocumentParams = { textDocument: { uri: documentInfo.uri, From 55bb0d3ccdd6015df2a44de8c75bfddfc693db38 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 10:44:48 -0500 Subject: [PATCH 50/63] clobber debug stuff again --- .../src/adapters/codemirror/cm_adapter.ts | 26 ++++---- .../src/adapters/codemirror/feature.ts | 11 ++-- .../codemirror/features/completion.ts | 11 ++-- .../codemirror/features/diagnostics.ts | 50 +++++++--------- .../codemirror/features/highlights.ts | 15 ++--- .../adapters/codemirror/features/jump_to.ts | 20 +++---- .../adapters/codemirror/features/rename.ts | 4 +- .../adapters/codemirror/features/signature.ts | 35 +++++------ .../src/adapters/codemirror/testutils.ts | 4 +- .../jupyterlab/components/completion.ts | 16 +++-- .../src/adapters/jupyterlab/file_editor.ts | 4 +- .../src/adapters/jupyterlab/jl_adapter.ts | 50 +++++++--------- .../src/adapters/jupyterlab/notebook.ts | 24 ++++---- .../jupyterlab-lsp/src/command_manager.ts | 13 ++-- packages/jupyterlab-lsp/src/connection.ts | 4 +- .../jupyterlab-lsp/src/connection_manager.ts | 60 +++++++------------ packages/jupyterlab-lsp/src/index.ts | 8 +-- packages/jupyterlab-lsp/src/utils.ts | 9 +-- .../jupyterlab-lsp/src/virtual/console.ts | 8 +-- .../jupyterlab-lsp/src/virtual/document.ts | 4 +- packages/jupyterlab-lsp/src/virtual/editor.ts | 17 +++--- .../src/virtual/editors/notebook.ts | 11 ++-- .../lsp-ws-connection/src/ws-connection.ts | 4 +- 23 files changed, 161 insertions(+), 247 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts index 6aac12caf..7b9d4493e 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/cm_adapter.ts @@ -6,8 +6,6 @@ import { IRootPosition } from '../../positioning'; import { ILSPFeature } from './feature'; import { IJupyterLabComponentsManager } from '../jupyterlab/jl_adapter'; -const DEBUG = 0; - export class CodeMirrorAdapter { features: Map; isDisposed = false; @@ -27,12 +25,11 @@ export class CodeMirrorAdapter { for (let feature of features) { feature.register(); if (!feature.is_registered) { - DEBUG && - this.editor.console.warn( - 'The feature ', - feature, - 'was not registered properly' - ); + this.editor.console.warn( + 'The feature ', + feature, + 'was not registered properly' + ); } this.features.set(feature.name, feature); } @@ -44,10 +41,9 @@ export class CodeMirrorAdapter { try { await until_ready(() => this.last_change != null, 30, 22); } catch (err) { - DEBUG && - console.log( - 'No change obtained from CodeMirror editor within the expected time of 0.66s' - ); + console.log( + 'No change obtained from CodeMirror editor within the expected time of 0.66s' + ); return; } @@ -58,7 +54,7 @@ export class CodeMirrorAdapter { try { root_position = this.editor.getDoc().getCursor('end') as IRootPosition; } catch (err) { - DEBUG && console.log('LSP: Root positon not found'); + console.log('LSP: Root positon not found'); return; } @@ -79,8 +75,8 @@ export class CodeMirrorAdapter { } return true; } catch (e) { - DEBUG && this.editor.console.log('updateAfterChange failure'); - DEBUG && this.editor.console.error(e); + this.editor.console.log('updateAfterChange failure'); + this.editor.console.error(e); } this.invalidateLastChange(); } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index 668a0ff0a..b4f1a387b 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -20,8 +20,6 @@ import * as CodeMirror from 'codemirror'; import { ICommandContext } from '../../command_manager'; import { DefaultMap } from '../../utils'; -const DEBUG = 0; - export enum CommandEntryPoint { CellContextMenu, FileEditorContextMenu @@ -327,10 +325,9 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { for (let e of change.edits) { let offset = offset_from_lsp(e.range.start, lines); if (edits_by_offset.has(offset)) { - DEBUG && - console.warn( - 'Edits should not overlap, ignoring an overlapping edit' - ); + console.warn( + 'Edits should not overlap, ignoring an overlapping edit' + ); } else { edits_by_offset.set(offset, e); applied_changes += 1; @@ -458,7 +455,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { // note: this does not matter for actions invoke from context menu // as those loose focus anyways (this might be addressed elsewhere) } catch (e) { - DEBUG && console.log('Could not place the cursor back', e); + console.log('Could not place the cursor back', e); } return 1; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts index 869724956..c5060753c 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/completion.ts @@ -2,8 +2,6 @@ import { CompletionTriggerKind } from '../../../lsp'; import * as CodeMirror from 'codemirror'; import { CodeMirrorLSPFeature } from '../feature'; -const DEBUG = 0; - export class Completion extends CodeMirrorLSPFeature { name = 'Completion'; private _completionCharacters: string[]; @@ -27,11 +25,10 @@ export class Completion extends CodeMirrorLSPFeature { // requires an up-to-date virtual document on the LSP side, so we need to wait for sync. let last_character = this.extract_last_character(change); if (this.completionCharacters.indexOf(last_character) > -1) { - DEBUG && - this.virtual_editor.console.log( - 'Will invoke completer after', - last_character - ); + this.virtual_editor.console.log( + 'Will invoke completer after', + last_character + ); this.jupyterlab_components.invoke_completer( CompletionTriggerKind.TriggerCharacter ); diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts index e2a834338..07d83e3a3 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics.ts @@ -16,8 +16,6 @@ import { import { VirtualDocument } from '../../../virtual/document'; import { VirtualEditor } from '../../../virtual/editor'; -const DEBUG = 0; - // TODO: settings const default_severity = 2; @@ -244,11 +242,10 @@ export class Diagnostics extends CodeMirrorLSPFeature { range.end ) as IVirtualPosition; if (start.line > this.virtual_document.last_virtual_line) { - DEBUG && - console.log( - 'Malformed diagnostic was skipped (out of lines) ', - diagnostics - ); + console.log( + 'Malformed diagnostic was skipped (out of lines) ', + diagnostics + ); return; } @@ -262,7 +259,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { start_in_root ); } catch (e) { - DEBUG && console.log(e, diagnostics); + console.log(e, diagnostics); return; } @@ -270,13 +267,12 @@ export class Diagnostics extends CodeMirrorLSPFeature { // and the user already changed the document so // that now this regions is in another virtual document! if (this.virtual_document !== document) { - DEBUG && - console.log( - `Ignoring inspections from ${response.uri}`, - ` (this region is covered by a another virtual document: ${document.uri})`, - ` inspections: `, - diagnostics - ); + console.log( + `Ignoring inspections from ${response.uri}`, + ` (this region is covered by a another virtual document: ${document.uri})`, + ` inspections: `, + diagnostics + ); return; } @@ -285,11 +281,10 @@ export class Diagnostics extends CodeMirrorLSPFeature { .get(start.line) .skip_inspect.indexOf(document.id_path) !== -1 ) { - DEBUG && - console.log( - 'Ignoring inspections silenced for this document:', - diagnostics - ); + console.log( + 'Ignoring inspections silenced for this document:', + diagnostics + ); return; } @@ -308,7 +303,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { try { end_in_editor = document.transform_virtual_to_editor(end); } catch (err) { - DEBUG && console.warn('LSP: Malformed range for diagnostic', end); + console.warn('LSP: Malformed range for diagnostic', end); end_in_editor = { ...start_in_editor, ch: start_in_editor.ch + 1 }; } @@ -362,12 +357,11 @@ export class Diagnostics extends CodeMirrorLSPFeature { .getDoc() .markText(start_in_editor, end_in_editor, options); } catch (e) { - DEBUG && - console.warn( - 'Marking inspection (diagnostic text) failed, see following logs (2):' - ); - DEBUG && console.log(diagnostics); - DEBUG && console.log(e); + console.warn( + 'Marking inspection (diagnostic text) failed, see following logs (2):' + ); + console.log(diagnostics); + console.log(e); return; } this.marked_diagnostics.set(diagnostic_hash, marker); @@ -381,7 +375,7 @@ export class Diagnostics extends CodeMirrorLSPFeature { this.diagnostics_db.set(this.virtual_document, diagnostics_list); diagnostics_panel.update(); } catch (e) { - DEBUG && console.warn(e); + console.warn(e); } }; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 6c6ef82a4..8f22b2a6f 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -5,8 +5,6 @@ import { VirtualDocument } from '../../../virtual/document'; import { IRootPosition } from '../../../positioning'; import { CodeMirrorLSPFeature, IFeatureCommand } from '../feature'; -const DEBUG = 0; - export class Highlights extends CodeMirrorLSPFeature { name = 'Highlights'; protected highlight_markers: CodeMirror.TextMarker[] = []; @@ -82,7 +80,7 @@ export class Highlights extends CodeMirrorLSPFeature { .getDoc() .getCursor('start') as IRootPosition; } catch (err) { - DEBUG && console.warn('LSP: no root position available'); + console.warn('LSP: no root position available'); return; } @@ -90,11 +88,10 @@ export class Highlights extends CodeMirrorLSPFeature { try { document = this.virtual_editor.document_at_root_position(root_position); } catch (e) { - DEBUG && - console.warn( - 'LSP: Could not obtain virtual document from position', - root_position - ); + console.warn( + 'LSP: Could not obtain virtual document from position', + root_position + ); return; } if (document !== this.virtual_document) { @@ -111,7 +108,7 @@ export class Highlights extends CodeMirrorLSPFeature { ); this.handleHighlight(highlights, this.virtual_document.document_info.uri); } catch (e) { - DEBUG && console.warn('Could not get highlights:', e); + console.warn('Could not get highlights:', e); } }; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts index c0b25ad7c..5774dc326 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/jump_to.ts @@ -4,8 +4,6 @@ import { IVirtualPosition } from '../../../positioning'; import { uri_to_contents_path, uris_equal } from '../../../utils'; import { AnyLocation } from 'lsp-ws-connection/src/types'; -const DEBUG = 0; - export class JumpToDefinition extends CodeMirrorLSPFeature { name = 'JumpToDefinition'; static commands: Array = [ @@ -33,7 +31,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { get_uri_and_range(location_or_locations: AnyLocation) { if (location_or_locations == null) { - DEBUG && console.log('No jump targets found'); + console.log('No jump targets found'); return; } // some language servers appear to return a single object @@ -48,8 +46,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { return; } - DEBUG && - console.log('Will jump to the first of suggested locations:', locations); + console.log('Will jump to the first of suggested locations:', locations); const location_or_link = locations[0]; @@ -70,7 +67,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { const target_info = this.get_uri_and_range(location_or_locations); if (target_info == null) { - DEBUG && console.log('No jump targets found'); + console.log('No jump targets found'); } let { uri, range } = target_info; @@ -86,8 +83,8 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { virtual_position ); let editor_position_ce = PositionConverter.cm_to_ce(editor_position); - DEBUG && console.log(`Jumping to ${editor_index}th editor of ${uri}`); - DEBUG && console.log('Jump target within editor:', editor_position_ce); + console.log(`Jumping to ${editor_index}th editor of ${uri}`); + console.log('Jump target within editor:', editor_position_ce); this.jumper.jump({ token: { offset: this.jumper.getOffset(editor_position_ce, editor_index), @@ -98,10 +95,9 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { } else { // otherwise there is no virtual document and we expect the returned position to be source position: let source_position_ce = PositionConverter.cm_to_ce(virtual_position); - DEBUG && console.log(`Jumping to external file: ${uri}`); + console.log(`Jumping to external file: ${uri}`); - DEBUG && - console.log('Jump target (source location):', source_position_ce); + console.log('Jump target (source location):', source_position_ce); // can it be resolved vs our guessed server root? const contents_path = uri_to_contents_path(uri); @@ -129,7 +125,7 @@ export class JumpToDefinition extends CodeMirrorLSPFeature { this.jumper.global_jump({ uri, ...jump_data }, false); return; } catch (err) { - DEBUG && console.warn(err); + console.warn(err); } this.jumper.global_jump( diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts index abe2aaddd..e0af38dff 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/rename.ts @@ -9,8 +9,6 @@ import { Diagnostics } from './diagnostics'; import { VirtualEditor } from '../../../virtual/editor'; import { VirtualEditorForNotebook } from '../../../virtual/editors/notebook'; -const DEBUG = 0; - export class Rename extends CodeMirrorLSPFeature { name = 'Rename'; static commands: Array = [ @@ -90,7 +88,7 @@ export class Rename extends CodeMirrorLSPFeature { this.status_message.set(status, 5 * 1000); } catch (error) { - DEBUG && console.warn(error); + console.warn(error); } return outcome; diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts index b4fa08227..8edd4f1c3 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/signature.ts @@ -3,8 +3,6 @@ import { IRootPosition } from '../../../positioning'; import * as CodeMirror from 'codemirror'; import { CodeMirrorLSPFeature } from '../feature'; -const DEBUG = 0; - export class Signature extends CodeMirrorLSPFeature { name = 'Signature'; protected signature_character: IRootPosition; @@ -71,11 +69,10 @@ export class Signature extends CodeMirrorLSPFeature { } } else { if (item.documentation.kind !== 'markdown') { - DEBUG && - this.virtual_editor.console.warn( - 'Unknown MarkupContent kind:', - item.documentation.kind - ); + this.virtual_editor.console.warn( + 'Unknown MarkupContent kind:', + item.documentation.kind + ); } markdown += item.documentation.value; } @@ -86,7 +83,7 @@ export class Signature extends CodeMirrorLSPFeature { private handleSignature(response: lsProtocol.SignatureHelp) { this.jupyterlab_components.remove_tooltip(); - DEBUG && this.virtual_editor.console.log('Signature received', response); + this.virtual_editor.console.log('Signature received', response); if (!this.signature_character || !response || !response.signatures.length) { return; } @@ -99,13 +96,12 @@ export class Signature extends CodeMirrorLSPFeature { let language = this.get_language_at(editor_position, cm_editor); let markup = this.get_markup_for_signature_help(response, language); - DEBUG && - this.virtual_editor.console.log( - 'Signature will be shown', - language, - markup, - root_position - ); + this.virtual_editor.console.log( + 'Signature will be shown', + language, + markup, + root_position + ); let tooltip = this.jupyterlab_components.create_tooltip( markup, @@ -135,11 +131,10 @@ export class Signature extends CodeMirrorLSPFeature { root_position ); - DEBUG && - this.virtual_editor.console.log( - 'Signature will be requested for', - virtual_position - ); + this.virtual_editor.console.log( + 'Signature will be requested for', + virtual_position + ); this.connection .getSignatureHelp( diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts index 57d43e54e..c9ebf9000 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/testutils.ts @@ -23,8 +23,6 @@ import createNotebook = NBTestUtils.createNotebook; import { CodeMirrorAdapter } from './cm_adapter'; import { VirtualDocument } from '../../virtual/document'; -const DEBUG = 0; - interface IFeatureTestEnvironment { host: HTMLElement; virtual_editor: VirtualEditor; @@ -253,6 +251,6 @@ export async function synchronize_content( try { await adapter.updateAfterChange(); } catch (e) { - DEBUG && console.warn(e); + console.warn(e); } } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index 9b9582aae..f53379363 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -21,8 +21,6 @@ import { } from '../../../positioning'; import { LSPConnection } from '../../../connection'; -const DEBUG = 0; - /* Feedback: anchor - not clear from docs bundle - very not clear from the docs, interface or better docs would be nice to have @@ -118,7 +116,7 @@ export class LSPConnector extends DataConnector< const token = editor.getTokenForPosition(cursor); if (this.suppress_auto_invoke_in.indexOf(token.type) !== -1) { - DEBUG && console.log('Suppressing completer auto-invoke in', token.type); + console.log('Suppressing completer auto-invoke in', token.type); return; } @@ -182,11 +180,11 @@ export class LSPConnector extends DataConnector< document, position_in_token ).catch(e => { - DEBUG && console.warn('LSP: hint failed', e); + console.warn('LSP: hint failed', e); return this.fallback_connector.fetch(request); }); } catch (e) { - DEBUG && console.warn('LSP: kernel completions failed', e); + console.warn('LSP: kernel completions failed', e); return this.fallback_connector.fetch(request); } } @@ -208,7 +206,7 @@ export class LSPConnector extends DataConnector< // to the matches... // Suggested in https://github.com/jupyterlab/jupyterlab/issues/7044, TODO PR - DEBUG && console.log('[LSP][Completer] Token:', token); + console.log('[LSP][Completer] Token:', token); let completion_items = ((await connection.getCompletion( cursor, @@ -295,7 +293,7 @@ export class LSPConnector extends DataConnector< } else if (lsp.matches.length === 0) { return kernel; } - DEBUG && console.log('[LSP][Completer] Merging completions:', lsp, kernel); + console.log('[LSP][Completer] Merging completions:', lsp, kernel); // Populate the result with a copy of the lsp matches. const matches = lsp.matches.slice(); @@ -315,9 +313,9 @@ export class LSPConnector extends DataConnector< const cursor = editor.getCursorPosition(); const line = editor.getLine(cursor.line); prefix = line.substring(kernel.start, lsp.start); - DEBUG && console.log('[LSP][Completer] Removing kernel prefix: ', prefix); + console.log('[LSP][Completer] Removing kernel prefix: ', prefix); } else if (lsp.start < kernel.start) { - DEBUG && console.warn('[LSP][Completer] Kernel start > LSP start'); + console.warn('[LSP][Completer] Kernel start > LSP start'); } let remove_prefix = (value: string) => { diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts index a65bd6367..5d462fdbe 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/file_editor.ts @@ -12,8 +12,6 @@ import { CodeEditor } from '@jupyterlab/codeeditor'; import { VirtualFileEditor } from '../../virtual/editors/file_editor'; import { DocumentConnectionManager } from '../../connection_manager'; -const DEBUG = 0; - export class FileEditorAdapter extends JupyterLabWidgetAdapter { editor: FileEditor; jumper: FileEditorJumper; @@ -71,7 +69,7 @@ export class FileEditorAdapter extends JupyterLabWidgetAdapter { ); this.connect_contentChanged_signal(); - DEBUG && console.log('LSP: file ready for connection:', this.path); + console.log('LSP: file ready for connection:', this.path); this.connect_document(this.virtual_editor.virtual_document).catch( console.warn ); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 483674578..90d5bebb2 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -33,8 +33,6 @@ import { } from '../../connection_manager'; import { Rename } from '../codemirror/features/rename'; -const DEBUG = 0; - export const lsp_features: Array = [ Completion, Diagnostics, @@ -146,11 +144,10 @@ export abstract class JupyterLabWidgetAdapter manager: DocumentConnectionManager, { virtual_document }: IDocumentConnectionData ) { - DEBUG && - console.log( - 'LSP: connection closed, disconnecting adapter', - virtual_document.id_path - ); + console.log( + 'LSP: connection closed, disconnecting adapter', + virtual_document.id_path + ); if (virtual_document !== this.virtual_editor?.virtual_document) { return; } @@ -289,12 +286,11 @@ export abstract class JupyterLabWidgetAdapter // refresh the document on the LSP server this.document_changed(virtual_document, virtual_document, true); - DEBUG && - console.log( - 'LSP: virtual document(s) for', - this.document_path, - 'have been initialized' - ); + console.log( + 'LSP: virtual document(s) for', + this.document_path, + 'have been initialized' + ); }); } @@ -347,24 +343,19 @@ export abstract class JupyterLabWidgetAdapter let adapter = this.adapters.get(virtual_document.id_path); if (!connection?.isReady) { - DEBUG && - console.log( - 'LSP: Skipping document update signal: connection not ready' - ); + console.log('LSP: Skipping document update signal: connection not ready'); return; } if (adapter == null) { - DEBUG && - console.log('LSP: Skipping document update signal: adapter not ready'); + console.log('LSP: Skipping document update signal: adapter not ready'); return; } - DEBUG && - console.log( - 'LSP: virtual document', - virtual_document.id_path, - 'has changed sending update' - ); + console.log( + 'LSP: virtual document', + virtual_document.id_path, + 'has changed sending update' + ); connection.sendFullTextChange( virtual_document.value, virtual_document.document_info @@ -411,10 +402,9 @@ export abstract class JupyterLabWidgetAdapter private async connect(virtual_document: VirtualDocument) { let language = virtual_document.language; - DEBUG && - console.log( - `LSP: will connect using root path: ${this.root_path} and language: ${language}` - ); + console.log( + `LSP: will connect using root path: ${this.root_path} and language: ${language}` + ); let options = { virtual_document, @@ -472,7 +462,7 @@ export abstract class JupyterLabWidgetAdapter this, adapter_features ); - DEBUG && console.log('LSP: Adapter for', this.document_path, 'is ready.'); + console.log('LSP: Adapter for', this.document_path, 'is ready.'); return adapter; } diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index da323c584..3c26133a4 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -18,8 +18,6 @@ import { DocumentConnectionManager } from '../../connection_manager'; import { IClientSession } from '@jupyterlab/apputils'; import { Session } from '@jupyterlab/services'; -const DEBUG = 0; - export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: Notebook; widget: NotebookPanel; @@ -62,22 +60,21 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { change: Session.IKernelChangedArgs ) { if (!change.newValue) { - DEBUG && console.log('LSP: kernel was shut down'); + console.log('LSP: kernel was shut down'); return; } change.newValue.ready .then(async spec => { - DEBUG && - console.log( - 'LSP: Changed to ' + - change.newValue.info.language_info.name + - ' kernel, reconnecting' - ); + console.log( + 'LSP: Changed to ' + + change.newValue.info.language_info.name + + ' kernel, reconnecting' + ); await until_ready(this.is_ready, -1); this.reload_connection(); }) .catch(e => { - DEBUG && console.warn(e); + console.warn(e); // try to reconnect anyway this.reload_connection(); }); @@ -119,7 +116,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { try { return this.widget.context.session.kernel.info.language_info; } catch (e) { - DEBUG && console.log('LSP: Could not get kernel metadata'); + console.log('LSP: Could not get kernel metadata'); return null; } } @@ -146,10 +143,9 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { } async init_once_ready() { - DEBUG && - console.log('LSP: waiting for', this.document_path, 'to fully load'); + console.log('LSP: waiting for', this.document_path, 'to fully load'); await until_ready(this.is_ready, -1); - DEBUG && console.log('LSP:', this.document_path, 'ready for connection'); + console.log('LSP:', this.document_path, 'ready for connection'); this.virtual_editor = new VirtualEditorForNotebook( this.widget.content, diff --git a/packages/jupyterlab-lsp/src/command_manager.ts b/packages/jupyterlab-lsp/src/command_manager.ts index ce727fa22..a1612b26f 100644 --- a/packages/jupyterlab-lsp/src/command_manager.ts +++ b/packages/jupyterlab-lsp/src/command_manager.ts @@ -23,8 +23,6 @@ import { PositionConverter } from './converter'; export const file_editor_adapters: Map = new Map(); export const notebook_adapters: Map = new Map(); -const DEBUG = 0; - function is_context_menu_over_token(adapter: JupyterLabWidgetAdapter) { let position = adapter.get_position_from_context_menu(); if (!position) { @@ -151,11 +149,10 @@ export abstract class ContextCommandManager extends LSPCommandManager { try { context = this.current_adapter.get_context_from_context_menu(); } catch (e) { - DEBUG && - console.warn( - 'contextMenu is attached, but could not get the context', - e - ); + console.warn( + 'contextMenu is attached, but could not get the context', + e + ); context = null; } } @@ -175,7 +172,7 @@ export abstract class ContextCommandManager extends LSPCommandManager { command.is_enabled(context) ); } catch (e) { - DEBUG && console.warn('is_visible failed', e); + console.warn('is_visible failed', e); return false; } } diff --git a/packages/jupyterlab-lsp/src/connection.ts b/packages/jupyterlab-lsp/src/connection.ts index 442d948a7..6e7fbe5ee 100644 --- a/packages/jupyterlab-lsp/src/connection.ts +++ b/packages/jupyterlab-lsp/src/connection.ts @@ -11,8 +11,6 @@ import { } from 'lsp-ws-connection'; import { until_ready } from './utils'; -const DEBUG = 0; - interface ILSPOptions extends ILspOptions {} export class LSPConnection extends LspWsConnection { @@ -83,7 +81,7 @@ export class LSPConnection extends LspWsConnection { }); }) .catch(() => { - DEBUG && console.error('Could not connect onClose signal'); + console.error('Could not connect onClose signal'); }); return this; } diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 621508a90..5e552e631 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -8,8 +8,6 @@ import { sleep, until_ready } from './utils'; // Name-only import so as to not trigger inclusion in main bundle import * as ConnectionModuleType from './connection'; -const DEBUG = 0; - export interface IDocumentConnectionData { virtual_document: VirtualDocument; connection: LSPConnection; @@ -105,11 +103,10 @@ export class DocumentConnectionManager { } on_foreign_document_opened(_host: VirtualDocument, context: IForeignContext) { - DEBUG && - console.log( - 'LSP: ConnectionManager received foreign document: ', - context.foreign_document.id_path - ); + console.log( + 'LSP: ConnectionManager received foreign document: ', + context.foreign_document.id_path + ); } on_foreign_document_closed(_host: VirtualDocument, context: IForeignContext) { @@ -120,7 +117,7 @@ export class DocumentConnectionManager { private async connect_socket( options: ISocketConnectionOptions ): Promise { - DEBUG && console.log('LSP: Connection Socket', options); + console.log('LSP: Connection Socket', options); let { virtual_document, language } = options; this.connect_document_signals(virtual_document); @@ -147,26 +144,25 @@ export class DocumentConnectionManager { on_new_connection = (connection: LSPConnection) => { connection.on('error', e => { - DEBUG && console.warn(e); + console.warn(e); // TODO invalid now let error: Error = e.length && e.length >= 1 ? e[0] : new Error(); // TODO: those codes may be specific to my proxy client, need to investigate if (error.message.indexOf('code = 1005') !== -1) { - DEBUG && console.warn(`LSP: Connection failed for ${connection}`); + console.warn(`LSP: Connection failed for ${connection}`); this.forEachDocumentOfConnection(connection, virtual_document => { - DEBUG && - console.warn('LSP: disconnecting ' + virtual_document.id_path); + console.warn('LSP: disconnecting ' + virtual_document.id_path); this.closed.emit({ connection, virtual_document }); this.ignored_languages.add(virtual_document.language); - DEBUG && - console.warn( - `Cancelling further attempts to connect ${virtual_document.id_path} and other documents for this language (no support from the server)` - ); + + console.warn( + `Cancelling further attempts to connect ${virtual_document.id_path} and other documents for this language (no support from the server)` + ); }); } else if (error.message.indexOf('code = 1006') !== -1) { - DEBUG && console.warn('LSP: Connection closed by the server '); + console.warn('LSP: Connection closed by the server '); } else { - DEBUG && console.error('LSP: Connection error:', e); + console.error('LSP: Connection error:', e); } }); @@ -179,9 +175,9 @@ export class DocumentConnectionManager { connection.on('close', closed_manually => { if (!closed_manually) { - DEBUG && console.warn('LSP: Connection unexpectedly disconnected'); + console.warn('LSP: Connection unexpectedly disconnected'); } else { - DEBUG && console.warn('LSP: Connection closed'); + console.warn('LSP: Connection closed'); this.forEachDocumentOfConnection(connection, virtual_document => { this.closed.emit({ connection, virtual_document }); }); @@ -224,13 +220,12 @@ export class DocumentConnectionManager { success = true; }) .catch(e => { - DEBUG && console.warn(e); + console.warn(e); }); - DEBUG && - console.log( - 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' - ); + console.log( + 'LSP: will attempt to re-connect in ' + interval / 1000 + ' seconds' + ); await sleep(interval); // gradually increase the time delay, up to 5 sec @@ -239,7 +234,7 @@ export class DocumentConnectionManager { } async connect(options: ISocketConnectionOptions) { - DEBUG && console.log('LSP: connection requested', options); + console.log('LSP: connection requested', options); let connection = await this.connect_socket(options); let { virtual_document, document_path } = options; @@ -248,21 +243,12 @@ export class DocumentConnectionManager { try { await until_ready(() => connection.isReady, 200, 200); } catch { - DEBUG && - console.warn( - `LSP: Connect timed out for ${virtual_document.id_path}` - ); + console.warn(`LSP: Connect timed out for ${virtual_document.id_path}`); return; } } - DEBUG && - console.log( - 'LSP:', - document_path, - virtual_document.id_path, - 'connected.' - ); + console.log('LSP:', document_path, virtual_document.id_path, 'connected.'); this.connected.emit({ connection, virtual_document }); diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index 8743fe306..dbbbb0839 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -36,8 +36,6 @@ import { LSPStatus } from './adapters/jupyterlab/components/statusbar'; import { IDocumentWidget } from '@jupyterlab/docregistry/lib/registry'; import { DocumentConnectionManager } from './connection_manager'; -const DEBUG = 0; - const lsp_commands: Array = [].concat( ...lsp_features.map(feature => feature.commands) ); @@ -111,8 +109,8 @@ const plugin: JupyterFrontEndPlugin = { ); fileEditorTracker.widgetUpdated.connect((_sender, _widget) => { - DEBUG && console.log(_sender); - DEBUG && console.log(_widget); + console.log(_sender); + console.log(_widget); // TODO? // adapter.remove(); // connection.close(); @@ -216,7 +214,7 @@ const plugin: JupyterFrontEndPlugin = { }); }) .catch((reason: Error) => { - DEBUG && console.error(reason.message); + console.error(reason.message); }); }, autoStart: true diff --git a/packages/jupyterlab-lsp/src/utils.ts b/packages/jupyterlab-lsp/src/utils.ts index f06adf2c4..849b787d4 100644 --- a/packages/jupyterlab-lsp/src/utils.ts +++ b/packages/jupyterlab-lsp/src/utils.ts @@ -1,7 +1,5 @@ import { PageConfig } from '@jupyterlab/coreutils'; -const DEBUG = 0; - const RE_PATH_ANCHOR = /^file:\/\/([^\/]+|\/[A-Z]:)/; export async function sleep(timeout: number) { @@ -61,10 +59,9 @@ export function getModifierState( case 'Meta': return event.metaKey; default: - DEBUG && - console.warn( - `State of the modifier key "${modifierKey}" could not be determined.` - ); + console.warn( + `State of the modifier key "${modifierKey}" could not be determined.` + ); } } diff --git a/packages/jupyterlab-lsp/src/virtual/console.ts b/packages/jupyterlab-lsp/src/virtual/console.ts index 1b625ed18..0f2f884ac 100644 --- a/packages/jupyterlab-lsp/src/virtual/console.ts +++ b/packages/jupyterlab-lsp/src/virtual/console.ts @@ -1,7 +1,5 @@ import '../../style/console.css'; -const DEBUG = 0; - export abstract class EditorLogConsole { abstract log(...args: any[]): void; abstract warn(...args: any[]): void; @@ -45,13 +43,13 @@ export class FloatingConsole extends EditorLogConsole { export class BrowserConsole extends EditorLogConsole { log(...args: any[]) { - DEBUG && console.log('LSP: ', ...args); + console.log('LSP: ', ...args); } warn(...args: any[]) { - DEBUG && console.warn('LSP: ', ...args); + console.warn('LSP: ', ...args); } error(...args: any[]) { - DEBUG && console.error('LSP: ', ...args); + console.error('LSP: ', ...args); } } diff --git a/packages/jupyterlab-lsp/src/virtual/document.ts b/packages/jupyterlab-lsp/src/virtual/document.ts index 513fc8370..e69a2a09e 100644 --- a/packages/jupyterlab-lsp/src/virtual/document.ts +++ b/packages/jupyterlab-lsp/src/virtual/document.ts @@ -18,8 +18,6 @@ import { IDocumentInfo } from 'lsp-ws-connection/src'; import { DocumentConnectionManager } from '../connection_manager'; -const DEBUG = 0; - type language = string; interface IVirtualLine { @@ -616,7 +614,7 @@ export class VirtualDocument { } close_foreign(document: VirtualDocument) { - DEBUG && console.log('LSP: closing document', document); + console.log('LSP: closing document', document); this.foreign_document_closed.emit({ foreign_document: document, parent_host: this diff --git a/packages/jupyterlab-lsp/src/virtual/editor.ts b/packages/jupyterlab-lsp/src/virtual/editor.ts index 8ca551d52..a6a7ffe4f 100644 --- a/packages/jupyterlab-lsp/src/virtual/editor.ts +++ b/packages/jupyterlab-lsp/src/virtual/editor.ts @@ -12,8 +12,6 @@ import { until_ready } from '../utils'; import { Signal } from '@phosphor/signaling'; import { EditorLogConsole, create_console } from './console'; -const DEBUG = 0; - export type CodeMirrorHandler = (instance: any, ...args: any[]) => void; type WrappedHandler = (instance: CodeMirror.Editor, ...args: any[]) => void; @@ -99,7 +97,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { try { root_document.close_expired_documents(); } catch (e) { - DEBUG && this.console.warn('LSP: Failed to close expired documents'); + this.console.warn('LSP: Failed to close expired documents'); } } @@ -134,7 +132,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { * @param fn - the callback to execute in update lock */ public async with_update_lock(fn: Function) { - DEBUG && this.console.log('Will enter update lock with', fn); + this.console.log('Will enter update lock with', fn); await until_ready(() => this.can_update(), 12, 10).then(() => { try { this.update_lock = true; @@ -165,7 +163,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { this.virtual_document.maybe_emit_changed(); resolve(); } catch (e) { - DEBUG && this.console.warn('Documents update failed:', e); + this.console.warn('Documents update failed:', e); reject(e); } finally { this.is_update_in_progress = false; @@ -229,11 +227,10 @@ export abstract class VirtualEditor implements CodeMirror.Editor { try { return handler(this, ...args); } catch (error) { - DEBUG && - this.console.warn( - 'Wrapped handler (which should accept a CodeMirror Editor instance) failed', - { error, instance, args, this: this } - ); + this.console.warn( + 'Wrapped handler (which should accept a CodeMirror Editor instance) failed', + { error, instance, args, this: this } + ); } }; this._event_wrappers.set([eventName, handler], wrapped_handler); diff --git a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts index 90f203a57..4feda04ad 100644 --- a/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts +++ b/packages/jupyterlab-lsp/src/virtual/editors/notebook.ts @@ -11,8 +11,6 @@ import { IVirtualPosition } from '../../positioning'; -const DEBUG = 0; - // @ts-ignore class DocDispatcher implements CodeMirror.Doc { virtual_editor: VirtualEditorForNotebook; @@ -89,10 +87,9 @@ export class VirtualEditorForNotebook extends VirtualEditor { receiver: any ) { if (!(prop in target)) { - DEBUG && - console.warn( - `Unimplemented method ${prop} for VirtualEditorForNotebook` - ); + console.warn( + `Unimplemented method ${prop} for VirtualEditorForNotebook` + ); return; } else { return Reflect.get(target, prop, receiver); @@ -203,7 +200,7 @@ export class VirtualEditorForNotebook extends VirtualEditor { let editor = this.get_editor_at_root_line(pos); return editor.charCoords(pos, mode); } catch (e) { - DEBUG && console.log(e); + console.log(e); return { bottom: 0, left: 0, right: 0, top: 0 }; } } diff --git a/packages/lsp-ws-connection/src/ws-connection.ts b/packages/lsp-ws-connection/src/ws-connection.ts index a72686872..f39d1ca57 100644 --- a/packages/lsp-ws-connection/src/ws-connection.ts +++ b/packages/lsp-ws-connection/src/ws-connection.ts @@ -16,8 +16,6 @@ import { AnyCompletion } from './types'; -const DEBUG = 0; - /** * Changes as compared to upstream: * - markdown is preferred over plaintext @@ -213,7 +211,7 @@ export class LspWsConnection extends events.EventEmitter this.onServerInitialized(params); }, e => { - DEBUG && console.warn('lsp-ws-connection initialization failure', e); + console.warn('lsp-ws-connection initialization failure', e); } ); } From d384809f35d09eba812e5074049bd9185855ea75 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 10:50:47 -0500 Subject: [PATCH 51/63] handle late cleanup of document vs update --- packages/jupyterlab-lsp/src/virtual/editor.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/jupyterlab-lsp/src/virtual/editor.ts b/packages/jupyterlab-lsp/src/virtual/editor.ts index a6a7ffe4f..cc806048a 100644 --- a/packages/jupyterlab-lsp/src/virtual/editor.ts +++ b/packages/jupyterlab-lsp/src/virtual/editor.ts @@ -153,14 +153,18 @@ export abstract class VirtualEditor implements CodeMirror.Editor { // defer the update by up to 50 ms (10 retrials * 5 ms break), // awaiting for the previous update to complete. await until_ready(() => this.can_update(), 10, 5).then(() => { - if (this.isDisposed) { + if (this.isDisposed || !this.virtual_document) { resolve(); } try { this.is_update_in_progress = true; this.perform_documents_update(); - this.documents_updated.emit(this.virtual_document); - this.virtual_document.maybe_emit_changed(); + + if (this.virtual_document) { + this.documents_updated.emit(this.virtual_document); + this.virtual_document.maybe_emit_changed(); + } + resolve(); } catch (e) { this.console.warn('Documents update failed:', e); From e2a869c00c877517249e7c221abec8134cd53877 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 11:20:39 -0500 Subject: [PATCH 52/63] rework screenshot strategy --- atest/05_Features/Completion.robot | 26 ++------------------------ atest/Keywords.robot | 2 +- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/atest/05_Features/Completion.robot b/atest/05_Features/Completion.robot index 599ef3ced..5c4ccfc51 100644 --- a/atest/05_Features/Completion.robot +++ b/atest/05_Features/Completion.robot @@ -2,6 +2,8 @@ Suite Setup Setup Suite For Screenshots completion Force Tags feature:completion Resource ../Keywords.robot +Test Setup Setup Notebook Python Completion.ipynb +Test Teardown Clean Up After Working With File Completion.ipynb *** Variables *** ${COMPLETER_BOX} css:.jp-Completer.jp-HoverBox @@ -9,8 +11,6 @@ ${COMPLETER_BOX} css:.jp-Completer.jp-HoverBox *** Test Cases *** Works With Kernel Running [Documentation] The suggestions from kernel and LSP should get integrated. - [Tags] language:python - Setup Notebook Python Completion.ipynb Enter Cell Editor 1 line=2 Capture Page Screenshot 01-entered-cell.png Trigger Completer @@ -25,11 +25,8 @@ Works With Kernel Running Capture Page Screenshot 03-completion-confirmed.png ${content} = Get Cell Editor Content 1 Should Contain ${content} TabError - [Teardown] Clean Up After Working With File Completion.ipynb Works When Kernel Is Shut Down - [Tags] language:python - Setup Notebook Python Completion.ipynb Lab Command Shut Down All Kernels… Capture Page Screenshot 01-shutting-kernels.png Accept Default Dialog Option @@ -41,32 +38,23 @@ Works When Kernel Is Shut Down Completer Should Suggest test # this comes from kernel: Completer Should Not Suggest %%timeit - [Teardown] Clean Up After Working With File Completion.ipynb Autocompletes If Only One Option - [Tags] language:python - Setup Notebook Python Completion.ipynb Enter Cell Editor 3 line=1 Press Keys None cle Wait Until Fully Initialized Press Keys None TAB Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 3 list.clear - [Teardown] Clean Up After Working With File Completion.ipynb User Can Select Lowercase After Starting Uppercase - [Tags] language:python - Setup Notebook Python Completion.ipynb # `from time import Tim` → `from time import time` Enter Cell Editor 5 line=1 Trigger Completer Completer Should Suggest time Press Keys None ENTER Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 5 from time import time - [Teardown] Clean Up After Working With File Completion.ipynb Mid Token Completions Do Not Overwrite - [Tags] language:python - Setup Notebook Python Completion.ipynb # `dispdata` → `display_tabledata` Place Cursor In Cell Editor At 9 line=1 character=4 Capture Page Screenshot 01-cursor-placed.png @@ -77,41 +65,31 @@ Mid Token Completions Do Not Overwrite Place Cursor In Cell Editor At 11 line=1 character=4 Press Keys None TAB Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 11 display_table - [Teardown] Clean Up After Working With File Completion.ipynb Completion Works For Tokens Separated By Space - [Tags] language:python - Setup Notebook Python Completion.ipynb # `from statistics ` → `from statistics import` Enter Cell Editor 13 line=1 Trigger Completer Completer Should Suggest import Press Keys None ENTER Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 13 from statistics import - [Teardown] Clean Up After Working With File Completion.ipynb Kernel And LSP Completions Merge Prefix Conflicts Are Resolved [Documentation] Reconciliate Python kernel returning prefixed completions and LSP (pyls) not-prefixed ones - [Tags] language:python # For more details see: https://github.com/krassowski/jupyterlab-lsp/issues/30#issuecomment-576003987 # `import os.pat` → `import os.pathsep` - Setup Notebook Python Completion.ipynb Enter Cell Editor 15 line=1 Trigger Completer Completer Should Suggest pathsep Select Completer Suggestion pathsep Wait Until Keyword Succeeds 40x 0.5s Cell Editor Should Equal 15 import os.pathsep - [Teardown] Clean Up After Working With File Completion.ipynb Triggers Completer On Dot - [Tags] language:python - Setup Notebook Python Completion.ipynb Enter Cell Editor 2 line=1 Press Keys None . Wait Until Keyword Succeeds 10x 0.5s Cell Editor Should Equal 2 list. Wait Until Page Contains Element ${COMPLETER_BOX} timeout=35s Completer Should Suggest append - [Teardown] Clean Up After Working With File Completion.ipynb *** Keywords *** Get Cell Editor Content diff --git a/atest/Keywords.robot b/atest/Keywords.robot index 3325f61ee..b0459080e 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -180,7 +180,7 @@ Clean Up After Working With File Setup Notebook [Arguments] ${Language} ${file} Set Tags language:${Language.lower()} - Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${file.lower()} + Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${TEST NAME.replace(' ', '_)} Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} Try to Close All Tabs Open ${file} in ${MENU NOTEBOOK} From f2a79d6caa4efa62ef90fb26bbbae39588c6c0c8 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 11:42:32 -0500 Subject: [PATCH 53/63] missing quote --- atest/Keywords.robot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atest/Keywords.robot b/atest/Keywords.robot index b0459080e..8b9d23fa5 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -180,7 +180,7 @@ Clean Up After Working With File Setup Notebook [Arguments] ${Language} ${file} Set Tags language:${Language.lower()} - Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${TEST NAME.replace(' ', '_)} + Set Screenshot Directory ${OUTPUT DIR}${/}screenshots${/}notebook${/}${TEST NAME.replace(' ', '_')} Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} Try to Close All Tabs Open ${file} in ${MENU NOTEBOOK} From 3fa15eee87500ab7fb3dfd8c71f72009657b88dd Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 12:09:34 -0500 Subject: [PATCH 54/63] rework document closing (connection doesn't go away) --- atest/Keywords.robot | 2 +- .../src/adapters/codemirror/features/highlights.ts | 3 +++ packages/jupyterlab-lsp/src/connection_manager.ts | 2 -- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/atest/Keywords.robot b/atest/Keywords.robot index 8b9d23fa5..89fce7577 100644 --- a/atest/Keywords.robot +++ b/atest/Keywords.robot @@ -184,8 +184,8 @@ Setup Notebook Copy File examples${/}${file} ${OUTPUT DIR}${/}home${/}${file} Try to Close All Tabs Open ${file} in ${MENU NOTEBOOK} - Wait Until Fully Initialized Capture Page Screenshot 00-opened.png + Wait Until Fully Initialized Capture Page Screenshot 01-initialized.png Open Diagnostics Panel diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 8f22b2a6f..7603c42ed 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -73,6 +73,9 @@ export class Highlights extends CodeMirrorLSPFeature { }; protected onCursorActivity = async () => { + if (!this.virtual_editor?.virtual_document?.document_info) { + return; + } let root_position: IRootPosition; try { diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 5e552e631..5304a3b92 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -256,9 +256,7 @@ export class DocumentConnectionManager { } public unregister_document(virtual_document: VirtualDocument) { - const connection = this.connections.get(virtual_document.id_path); this.connections.delete(virtual_document.id_path); - this.closed.emit({ connection, virtual_document }); } } From 8ae1b9ed3928c0a2b7231ae852b45e9c5900ecd4 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 13:36:24 -0500 Subject: [PATCH 55/63] fix completion regression --- atest/05_Features/Completion.robot | 5 ++-- .../jupyterlab/components/completion.ts | 2 ++ .../src/adapters/jupyterlab/notebook.ts | 28 +++++++++---------- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/atest/05_Features/Completion.robot b/atest/05_Features/Completion.robot index 5c4ccfc51..268abf8b8 100644 --- a/atest/05_Features/Completion.robot +++ b/atest/05_Features/Completion.robot @@ -110,11 +110,12 @@ Select Completer Suggestion Completer Should Suggest [Arguments] ${text} - Page Should Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"] + Wait Until Page Contains Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"] + Capture Page Screenshot ${text.replace(' ', '_')}.png Completer Should Not Suggest [Arguments] ${text} - Page Should Not Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"] + Wait Until Page Does Not Contain Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"] Trigger Completer Press Keys None TAB diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index f53379363..97f65cb05 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -112,6 +112,8 @@ export class LSPConnector extends DataConnector< ): Promise { let editor = this._editor; + console.error('NRB: editor', (editor as any)._editor.display.lineDiv); + const cursor = editor.getCursorPosition(); const token = editor.getTokenForPosition(cursor); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 3c26133a4..1f7738d8a 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -89,11 +89,11 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { this ); this.widget.content.activeCellChanged.disconnect(this.on_completions, this); - for (const handler of this.completion_handlers.values()) { - handler.connector = null; - handler.editor = null; + if (this.current_completion_handler) { + this.current_completion_handler.connector = null; + this.current_completion_handler.editor = null; + this.current_completion_handler = null; } - this.completion_handlers.clear(); super.dispose(); } @@ -175,10 +175,7 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { }); } - completion_handlers = new Map< - Cell, - ICompletionManager.ICompletableAttributes - >(); + current_completion_handler: ICompletionManager.ICompletableAttributes; connect_completion() { // see https://github.com/jupyterlab/jupyterlab/blob/c0e9eb94668832d1208ad3b00a9791ef181eca4c/packages/completer-extension/src/index.ts#L198-L213 @@ -192,19 +189,20 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { editor: cell.editor, parent: this.widget }); - this.completion_handlers.set(cell, handler); + this.current_completion_handler = handler; + this.widget.content.activeCellChanged.connect(this.on_completions, this); } on_completions(notebook: Notebook, cell: Cell) { if (cell == null) { return; } + console.error( + 'NRB: on_completions', + (cell.editor as any)._editor.display.lineDiv + ); this.set_completion_connector(cell); - const handler = this.completion_handlers.get(cell); - if (handler == null) { - return; - } - handler.editor = cell.editor; - handler.connector = this.current_completion_connector; + this.current_completion_handler.editor = cell.editor; + this.current_completion_handler.connector = this.current_completion_connector; } } From 36aa4195683f936e0f4259d3c1278acc268f5c79 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 14:46:22 -0500 Subject: [PATCH 56/63] some cleanup of completion, document updating --- .../jupyterlab/components/completion.ts | 2 -- .../src/adapters/jupyterlab/jl_adapter.ts | 17 ++++------------- .../src/adapters/jupyterlab/notebook.ts | 4 ---- .../jupyterlab-lsp/src/connection_manager.ts | 7 ++----- 4 files changed, 6 insertions(+), 24 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts index 97f65cb05..f53379363 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/components/completion.ts @@ -112,8 +112,6 @@ export class LSPConnector extends DataConnector< ): Promise { let editor = this._editor; - console.error('NRB: editor', (editor as any)._editor.display.lineDiv); - const cursor = editor.getCursorPosition(); const token = editor.getTokenForPosition(cursor); diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 90d5bebb2..6ee22b852 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -1,4 +1,3 @@ -import { PathExt } from '@jupyterlab/coreutils'; import * as CodeMirror from 'codemirror'; import { CodeMirrorAdapter } from '../codemirror/cm_adapter'; import { JupyterFrontEnd } from '@jupyterlab/application'; @@ -29,7 +28,8 @@ import { ICommandContext } from '../../command_manager'; import { JSONObject } from '@phosphor/coreutils'; import { DocumentConnectionManager, - IDocumentConnectionData + IDocumentConnectionData, + ISocketConnectionOptions } from '../../connection_manager'; import { Rename } from '../codemirror/features/rename'; @@ -227,12 +227,6 @@ export abstract class JupyterLabWidgetAdapter abstract get language_file_extension(): string; - get root_path() { - // TODO: serverRoot may need to be included for Hub or Windows, requires testing. - // let root = PageConfig.getOption('serverRoot'); - return PathExt.dirname(this.document_path); - } - // equivalent to triggering didClose and didOpen, as per syncing specification, // but also reloads the connection; used during file rename (or when it was moved) protected reload_connection() { @@ -402,14 +396,11 @@ export abstract class JupyterLabWidgetAdapter private async connect(virtual_document: VirtualDocument) { let language = virtual_document.language; - console.log( - `LSP: will connect using root path: ${this.root_path} and language: ${language}` - ); + console.log(`LSP: will connect using language: ${language}`); - let options = { + let options: ISocketConnectionOptions = { virtual_document, language, - root_path: this.root_path, document_path: this.document_path }; diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts index 1f7738d8a..22664f075 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/notebook.ts @@ -197,10 +197,6 @@ export class NotebookAdapter extends JupyterLabWidgetAdapter { if (cell == null) { return; } - console.error( - 'NRB: on_completions', - (cell.editor as any)._editor.display.lineDiv - ); this.set_completion_connector(cell); this.current_completion_handler.editor = cell.editor; this.current_completion_handler.connector = this.current_completion_connector; diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 5304a3b92..5e6391277 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -13,16 +13,12 @@ export interface IDocumentConnectionData { connection: LSPConnection; } -interface ISocketConnectionOptions { +export interface ISocketConnectionOptions { virtual_document: VirtualDocument; /** * The language identifier, corresponding to the API endpoint on the LSP proxy server. */ language: string; - /** - * The root path in the JupyterLab (virtual) path space - */ - root_path: string; /** * Path to the document in the JupyterLab space */ @@ -257,6 +253,7 @@ export class DocumentConnectionManager { public unregister_document(virtual_document: VirtualDocument) { this.connections.delete(virtual_document.id_path); + this.documents_changed.emit(this.documents); } } From 832c7b567830dd10b8d3ad528f734bc1edc1f2c7 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 15:49:20 -0500 Subject: [PATCH 57/63] hoist pathChanged concern to plugin level --- .../src/adapters/codemirror/feature.ts | 1 + .../features/diagnostics_listing.tsx | 3 ++ .../src/adapters/jupyterlab/jl_adapter.ts | 5 +-- .../jupyterlab-lsp/src/command_manager.ts | 4 +- packages/jupyterlab-lsp/src/index.ts | 43 ++++++++++++++++--- 5 files changed, 46 insertions(+), 10 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts index b4f1a387b..20eb2c9e3 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/feature.ts @@ -148,6 +148,7 @@ export abstract class CodeMirrorLSPFeature implements ILSPFeature { for (let [event_name, handler] of this.wrapper_handlers) { this.wrapper.addEventListener(event_name, handler); } + this.is_registered = true; } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx index 246514700..2c7bd88eb 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/diagnostics_listing.tsx @@ -270,6 +270,9 @@ export class DiagnosticsListing extends VDomRenderer { let by_document = Array.from(diagnostics_db).map( ([virtual_document, diagnostics]) => { + if (virtual_document.isDisposed) { + return []; + } return diagnostics.map((diagnostic_data, i) => { let cell_number: number = null; if (editor.has_cells) { diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 6ee22b852..25911b93c 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -133,7 +133,6 @@ export abstract class JupyterLabWidgetAdapter this.connection_manager = connection_manager; // set up signal connections - this.widget.context.pathChanged.connect(this.reload_connection, this); this.widget.context.saveState.connect(this.on_save_state, this); this.connection_manager.closed.connect(this.on_connection_closed, this); this.document_connected.connect(this.connect_completion, this); @@ -162,7 +161,6 @@ export abstract class JupyterLabWidgetAdapter this.disconnect_adapter(this.virtual_editor?.virtual_document); } - this.widget.context.pathChanged.disconnect(this.reload_connection, this); this.widget.context.saveState.disconnect(this.on_save_state, this); this.connection_manager.closed.disconnect(this.on_connection_closed, this); this.document_connected.disconnect(this.connect_completion, this); @@ -181,7 +179,7 @@ export abstract class JupyterLabWidgetAdapter ); this.virtual_editor.dispose(); - this.current_completion_connector.dispose(); + this.current_completion_connector?.dispose(); // just to be sure this.virtual_editor = null; @@ -239,6 +237,7 @@ export abstract class JupyterLabWidgetAdapter this.connection_manager.unregister_document( this.virtual_editor.virtual_document ); + // recreate virtual document using current path and language this.virtual_editor.create_virtual_document(); // reconnect diff --git a/packages/jupyterlab-lsp/src/command_manager.ts b/packages/jupyterlab-lsp/src/command_manager.ts index a1612b26f..591fa6e2c 100644 --- a/packages/jupyterlab-lsp/src/command_manager.ts +++ b/packages/jupyterlab-lsp/src/command_manager.ts @@ -222,7 +222,7 @@ export class NotebookCommandManager extends ContextCommandManager { cm_cursor ); - return this.current_adapter.get_context(root_position); + return this.current_adapter?.get_context(root_position); } } @@ -243,7 +243,7 @@ export class FileEditorCommandManager extends ContextCommandManager { let editor = this.tracker.currentWidget.content.editor; let ce_cursor = editor.getCursorPosition(); let root_position = PositionConverter.ce_to_cm(ce_cursor) as IRootPosition; - return this.current_adapter.get_context(root_position); + return this.current_adapter?.get_context(root_position); } } diff --git a/packages/jupyterlab-lsp/src/index.ts b/packages/jupyterlab-lsp/src/index.ts index dbbbb0839..d7c9bf32a 100644 --- a/packages/jupyterlab-lsp/src/index.ts +++ b/packages/jupyterlab-lsp/src/index.ts @@ -33,7 +33,10 @@ import { import IPaths = JupyterFrontEnd.IPaths; import { IStatusBar } from '@jupyterlab/statusbar'; import { LSPStatus } from './adapters/jupyterlab/components/statusbar'; -import { IDocumentWidget } from '@jupyterlab/docregistry/lib/registry'; +import { + IDocumentWidget, + DocumentRegistry +} from '@jupyterlab/docregistry/lib/registry'; import { DocumentConnectionManager } from './connection_manager'; const lsp_commands: Array = [].concat( @@ -116,7 +119,9 @@ const plugin: JupyterFrontEndPlugin = { // connection.close(); }); - fileEditorTracker.widgetAdded.connect((sender, widget) => { + const connect_file_editor = ( + widget: IDocumentWidget + ) => { let fileEditor = widget.content; if (fileEditor.editor instanceof CodeMirrorEditor) { @@ -131,17 +136,30 @@ const plugin: JupyterFrontEndPlugin = { ); file_editor_adapters.set(fileEditor.id, adapter); - file_editor_adapters.set(widget.id, adapter); const disconnect = () => { - file_editor_adapters.delete(widget.id); + file_editor_adapters.delete(fileEditor.id); widget.disposed.disconnect(disconnect); + widget.context.pathChanged.disconnect(reconnect); adapter.dispose(); if (status_bar_item.model.adapter === adapter) { status_bar_item.model.adapter = null; } }; + + const reconnect = () => { + disconnect(); + connect_file_editor(widget); + }; + widget.disposed.connect(disconnect); + widget.context.pathChanged.connect(reconnect); + + status_bar_item.model.adapter = adapter; } + }; + + fileEditorTracker.widgetAdded.connect((sender, widget) => { + connect_file_editor(widget); }); let command_manager = new FileEditorCommandManager( @@ -152,7 +170,7 @@ const plugin: JupyterFrontEndPlugin = { ); command_manager.add(lsp_commands); - notebookTracker.widgetAdded.connect(async (sender, widget) => { + const connect_notebook = (widget: NotebookPanel) => { // NOTE: assuming that the default cells content factory produces CodeMirror editors(!) let jumper = new NotebookJumper(widget, documentManager); let adapter = new NotebookAdapter( @@ -164,15 +182,30 @@ const plugin: JupyterFrontEndPlugin = { connection_manager ); notebook_adapters.set(widget.id, adapter); + const disconnect = () => { notebook_adapters.delete(widget.id); widget.disposed.disconnect(disconnect); + widget.context.pathChanged.disconnect(reconnect); adapter.dispose(); if (status_bar_item.model.adapter === adapter) { status_bar_item.model.adapter = null; } }; + + const reconnect = () => { + disconnect(); + connect_notebook(widget); + }; + + widget.context.pathChanged.connect(reconnect); widget.disposed.connect(disconnect); + + status_bar_item.model.adapter = adapter; + }; + + notebookTracker.widgetAdded.connect(async (sender, widget) => { + connect_notebook(widget); }); // position context menu entries after 10th but before 11th default entry From 87173e1af1781b9a3d05132cc4f21f7a5ba996c4 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 16:04:05 -0500 Subject: [PATCH 58/63] just bump the max listeners to something ridiculous --- .../src/adapters/jupyterlab/jl_adapter.ts | 10 +++++----- packages/jupyterlab-lsp/src/connection_manager.ts | 2 ++ packages/jupyterlab-lsp/src/virtual/editor.ts | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts index 25911b93c..652b87091 100644 --- a/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts +++ b/packages/jupyterlab-lsp/src/adapters/jupyterlab/jl_adapter.ts @@ -344,11 +344,11 @@ export abstract class JupyterLabWidgetAdapter return; } - console.log( - 'LSP: virtual document', - virtual_document.id_path, - 'has changed sending update' - ); + // console.log( + // 'LSP: virtual document', + // virtual_document.id_path, + // 'has changed sending update' + // ); connection.sendFullTextChange( virtual_document.value, virtual_document.document_info diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index 5e6391277..ce21b4344 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -311,6 +311,8 @@ namespace Private { serverUri: uris.server, rootUri: uris.base }); + // TODO: remove remaining unbounded users of connection.on + connection.setMaxListeners(999); _connections.set(language, connection); connection.connect(socket); onCreate(connection); diff --git a/packages/jupyterlab-lsp/src/virtual/editor.ts b/packages/jupyterlab-lsp/src/virtual/editor.ts index cc806048a..3cc5bc4f3 100644 --- a/packages/jupyterlab-lsp/src/virtual/editor.ts +++ b/packages/jupyterlab-lsp/src/virtual/editor.ts @@ -132,7 +132,7 @@ export abstract class VirtualEditor implements CodeMirror.Editor { * @param fn - the callback to execute in update lock */ public async with_update_lock(fn: Function) { - this.console.log('Will enter update lock with', fn); + // this.console.log('Will enter update lock with', fn); await until_ready(() => this.can_update(), 12, 10).then(() => { try { this.update_lock = true; From 452969b05a09826e46997d2e37f9b2fae1739ed0 Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sun, 2 Feb 2020 16:41:23 -0500 Subject: [PATCH 59/63] remove highlight and hover connection_handlers --- .../src/adapters/codemirror/features/highlights.ts | 1 - .../jupyterlab-lsp/src/adapters/codemirror/features/hover.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts index 7603c42ed..0890c9c4b 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/highlights.ts @@ -27,7 +27,6 @@ export class Highlights extends CodeMirrorLSPFeature { ]; register(): void { - this.connection_handlers.set('highlight', this.handleHighlight); this.editor_handlers.set('cursorActivity', this.onCursorActivity); super.register(); } diff --git a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts index fbd7a1c4c..465219baf 100644 --- a/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts +++ b/packages/jupyterlab-lsp/src/adapters/codemirror/features/hover.ts @@ -44,7 +44,6 @@ export class Hover extends CodeMirrorLSPFeature { ); } }); - this.connection_handlers.set('hover', this.handleHover); // TODO: make the debounce rate configurable this.debounced_get_hover = new Debouncer>( this.on_hover, From 33b1b047444533ddfaac23c8b8f2c2d16057c8cb Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 3 Feb 2020 17:28:36 -0500 Subject: [PATCH 60/63] restore py38/win tests --- scripts/atest.py | 2 +- scripts/utest.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/atest.py b/scripts/atest.py index 20372893b..ebe5642ee 100644 --- a/scripts/atest.py +++ b/scripts/atest.py @@ -19,7 +19,7 @@ OS_PY_ARGS = { # notebook and ipykernel releases do not yet support python 3.8 on windows - ("Windows", "38"): ["--include", "not-supported", "--runemptysuite"] + # ("Windows", "38"): ["--include", "not-supported", "--runemptysuite"] } diff --git a/scripts/utest.py b/scripts/utest.py index 786bd56d9..f9b542d29 100644 --- a/scripts/utest.py +++ b/scripts/utest.py @@ -10,7 +10,7 @@ OS_PY_ARGS = { # notebook and ipykernel releases do not yet support python 3.8 on windows - ("Windows", "38"): ["-k", "not serverextension"] + # ("Windows", "38"): ["-k", "not serverextension"] } DEFAULT_ARGS = ["--cov-fail-under=100"] From 9e7337fc1a2aa27a490cee26b6d2d3763f2e700e Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 3 Feb 2020 20:10:51 -0500 Subject: [PATCH 61/63] update CHANGELOG --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba6bea96..8504ed4f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,27 @@ # CHANGELOG +## `@krassowski/jupyterlab-lsp 0.8.0` (unreleased) + +- features + + - opens a maximum of one WebSocket per language server ([#165][]) + - lazy-loads language server protocol machinery ([#165][]) + - waits much longer for slow-starting language servers ([#165][]) + - cleans up documents, handlers, events, and signals more aggressively ([#165][]) + - ignores malformed diagnostic ranges, enabling markdown support ([#165][]) + - passes tests on Python 3.8 on Windows ([#165][]) + +## `lsp-ws-connection 0.4.0` (unreleased) + +- breaking changes + + - no longer assumes one document per connection ([#165][]) + - requires documents be opened explicitly ([#165][]) + - use of the `eventEmitter` pattern mostly deprecated in favor of `Promise`s + ([#165][]) + +[#165]: https://github.com/krassowski/jupyterlab-lsp/pull/165 + ## `@krassowski/jupyterlab-lsp 0.7.1` - features From 02569d3e6afa3535ef3f5cbf2a631b59768bb05f Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Mon, 3 Feb 2020 23:39:02 -0500 Subject: [PATCH 62/63] some docs and todos on connection manager --- .../jupyterlab-lsp/src/connection_manager.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/jupyterlab-lsp/src/connection_manager.ts b/packages/jupyterlab-lsp/src/connection_manager.ts index ce21b4344..0e3000082 100644 --- a/packages/jupyterlab-lsp/src/connection_manager.ts +++ b/packages/jupyterlab-lsp/src/connection_manager.ts @@ -123,12 +123,17 @@ export class DocumentConnectionManager { language ); + // lazily load 1) the underlying library (1.5mb) and/or 2) a live WebSocket- + // like connection: either already connected or potentiailly in the process + // of connecting. const connection = await Private.connection( language, uris, this.on_new_connection ); + // if connecting for the first time, all documents subsequent documents will + // be re-opened and synced this.connections.set(virtual_document.id_path, connection); if (connection.isReady) { @@ -138,6 +143,11 @@ export class DocumentConnectionManager { return connection; } + /** + * Fired the first time a connection is opened. These _should_ be the only + * invocation of `.on` (once remaining LSPFeature.connection_handlers are made + * singletons). + */ on_new_connection = (connection: LSPConnection) => { connection.on('error', e => { console.warn(e); @@ -165,6 +175,7 @@ export class DocumentConnectionManager { connection.on('serverInitialized', capabilities => { this.forEachDocumentOfConnection(connection, virtual_document => { connection.sendOpen(virtual_document.document_info); + // TODO: is this still neccessary, e.g. for status bar to update responsively? this.initialized.emit({ connection, virtual_document }); }); }); @@ -196,6 +207,10 @@ export class DocumentConnectionManager { } } + /** + * TODO: presently no longer referenced. A failing connection would close + * the socket, triggering the language server on the other end to exit + */ public async retry_to_connect( options: ISocketConnectionOptions, reconnect_delay: number, @@ -286,16 +301,24 @@ export namespace DocumentConnectionManager { } } +/** + * Namespace primarily for language-keyed cache of LSPConnections + */ namespace Private { const _connections = new Map(); let _promise: Promise; + /** + * Return (or create and initialize) the WebSocket associated with the language + */ export async function connection( language: string, uris: DocumentConnectionManager.IURIs, onCreate: (connection: LSPConnection) => void ): Promise { if (_promise == null) { + // TODO: consider lazy-loading _only_ the modules that _must_ be webpacked + // with custom shims, e.g. `fs` _promise = import( /* webpackChunkName: "jupyter-lsp-connection" */ './connection' ); From 3d454588c47c3917adc8502ea831304dd5090c9d Mon Sep 17 00:00:00 2001 From: Nicholas Bollweg Date: Sat, 8 Feb 2020 11:27:19 -0500 Subject: [PATCH 63/63] linting --- atest/05_Features/Completion.robot | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atest/05_Features/Completion.robot b/atest/05_Features/Completion.robot index 268abf8b8..4857cadaa 100644 --- a/atest/05_Features/Completion.robot +++ b/atest/05_Features/Completion.robot @@ -1,9 +1,9 @@ *** Settings *** Suite Setup Setup Suite For Screenshots completion -Force Tags feature:completion -Resource ../Keywords.robot Test Setup Setup Notebook Python Completion.ipynb Test Teardown Clean Up After Working With File Completion.ipynb +Force Tags feature:completion +Resource ../Keywords.robot *** Variables *** ${COMPLETER_BOX} css:.jp-Completer.jp-HoverBox @@ -111,7 +111,7 @@ Select Completer Suggestion Completer Should Suggest [Arguments] ${text} Wait Until Page Contains Element ${COMPLETER_BOX} .jp-Completer-item[data-value="${text}"] - Capture Page Screenshot ${text.replace(' ', '_')}.png + Capture Page Screenshot ${text.replace(' ', '_')}.png Completer Should Not Suggest [Arguments] ${text}