UNPKG

@jupyter-lsp/jupyterlab-lsp

Version:

Language Server Protocol integration for JupyterLab

352 lines 12.3 kB
import { LabShell } from '@jupyterlab/application'; import { CodeMirrorEditorFactory, EditorLanguageRegistry, CodeMirrorMimeTypeService, EditorExtensionRegistry, ybinding } from '@jupyterlab/codemirror'; import { Context, TextModelFactory, DocumentRegistry } from '@jupyterlab/docregistry'; import { FileEditorFactory } from '@jupyterlab/fileeditor'; import { WidgetLSPAdapterTracker, LanguageServerManager, CodeExtractorsManager, DocumentConnectionManager, FeatureManager } from '@jupyterlab/lsp'; import { LSPConnection } from '@jupyterlab/lsp/lib/connection'; import { Notebook, NotebookModel, NotebookModelFactory, NotebookPanel, StaticNotebook } from '@jupyterlab/notebook'; import { defaultRenderMime } from '@jupyterlab/rendermime/lib/testutils'; import { ServiceManagerMock } from '@jupyterlab/services/lib/testutils'; import { nullTranslator } from '@jupyterlab/translation'; import { PromiseDelegate } from '@lumino/coreutils'; import { Signal } from '@lumino/signaling'; import { FileEditorAdapter } from './adapters/fileeditor'; import { NotebookAdapter } from './adapters/notebook'; import { CodeOverridesManager } from './overrides'; const DEFAULT_SERVER_ID = 'pylsp'; export class MockLanguageServerManager extends LanguageServerManager { async fetchSessions() { const spec = { languages: ['python'] }; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._sessions = new Map(); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._sessions.set(DEFAULT_SERVER_ID, { spec }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._sessionsChanged.emit(void 0); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._specs = new Map(Object.entries({ [DEFAULT_SERVER_ID]: spec })); } } export class MockSettings { constructor(settings) { this.settings = settings; this.changed = new Signal(this); } get composite() { return this.settings; } set(setting, value) { this.settings[setting] = value; } } class MockConnection extends LSPConnection { constructor(options) { super(options); this.options = options; } connect(ws) { this.connection = new MockMessageConnection(); this.onServerInitialized({ capabilities: this.options.serverCapabilities }); this._isConnected = true; } } class MockMessageConnection { onError(handler) { // no-op } onNotification(handler) { // no-op } onRequest(hander) { // no-op } sendNotification(handler) { return Promise.resolve(); } } class MockDocumentConnectionManager extends DocumentConnectionManager { constructor(options) { super(options); this.options = options; } get ready() { return Promise.resolve(); } async connect(options, firstTimeoutSeconds, secondTimeoutMinutes) { let { language, capabilities, virtualDocument } = options; this.connectDocumentSignals(virtualDocument); const uris = { server: '', base: '' }; const matchingServers = this.languageServerManager.getMatchingServers({ language }); const languageServerId = matchingServers.length === 0 ? null : matchingServers[0]; if (!uris) { return; } const connection = new MockConnection({ languageId: language, serverUri: uris.server, rootUri: uris.base, serverIdentifier: languageServerId, capabilities: capabilities, serverCapabilities: {}, ...this.options.connection }); connection.connect(null); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this._connected.emit({ connection, virtualDocument }); return connection; } } export class TestEnvironment { constructor(options) { var _a; this.options = options; this.editorExtensionRegistry = new EditorExtensionRegistry(); this.editorExtensionRegistry.addExtension({ name: 'binding', factory: ({ model }) => EditorExtensionRegistry.createImmutableExtension(ybinding({ ytext: model.sharedModel.ysource })) }); const shell = new LabShell({ waitForRestore: false }); const adapterTracker = new WidgetLSPAdapterTracker({ shell }); const languages = new EditorLanguageRegistry(); this.editorServices = { factoryService: new CodeMirrorEditorFactory({ languages, extensions: this.editorExtensionRegistry }), mimeTypeService: new CodeMirrorMimeTypeService(languages) }; this._languageServerManager = new MockLanguageServerManager({}); this.connectionManager = new MockDocumentConnectionManager({ languageServerManager: this._languageServerManager, connection: (_a = this.options) === null || _a === void 0 ? void 0 : _a.connection, adapterTracker }); this.documentOptions = { ...this.getDefaults(), ...((options === null || options === void 0 ? void 0 : options.document) || {}) }; this.featureManager = new FeatureManager(); } get activeEditor() { return this.adapter.activeEditor.getEditor(); } async init() { this.widget = this.createWidget(); let adapterType = this.getAdapterType(); const docRegistry = new DocumentRegistry(); const { foreignCodeExtractors, overridesRegistry } = this.documentOptions; const overridesManager = new CodeOverridesManager(); for (let language of Object.keys(overridesRegistry)) { const cellOverrides = overridesRegistry[language].cell; for (const cell of cellOverrides) { overridesManager.register({ scope: 'cell', pattern: cell.pattern, replacement: cell.replacement, reverse: cell.reverse }, language); } const lineOverrides = overridesRegistry[language].line; for (const line of lineOverrides) { overridesManager.register({ scope: 'line', pattern: line.pattern, replacement: line.replacement, reverse: line.reverse }, language); } } this.adapter = new adapterType(this.widget, { docRegistry, connectionManager: this.connectionManager, codeOverridesManager: overridesManager, featureManager: this.featureManager, foreignCodeExtractorsManager: foreignCodeExtractors, translator: nullTranslator }); await this.widget.context.sessionContext.ready; await this.adapter.ready; this.connectionManager.adapters.set(this.adapter.virtualDocument.path, this.adapter); await this.connectionManager.ready; } dispose() { this.adapter.dispose(); this._languageServerManager.dispose(); } } export class MockNotebookAdapter extends NotebookAdapter { constructor() { super(...arguments); this.foreingDocumentOpened = new PromiseDelegate(); } get language() { return 'python'; } isReady() { return true; } async onForeignDocumentOpened(_, context) { try { const result = await super.onForeignDocumentOpened(_, context); this.foreingDocumentOpened.resolve(undefined); this.foreingDocumentOpened = new PromiseDelegate(); return result; } catch (e) { console.warn(`onForeignDocumentOpened failed: ${e}`); } } } export class FileEditorTestEnvironment extends TestEnvironment { getAdapterType() { return FileEditorAdapter; } getDefaults() { return { language: 'python', path: 'dummy.py', fileExtension: 'py', hasLspSupportedFile: true, standalone: true, overridesRegistry: {}, foreignCodeExtractors: new CodeExtractorsManager() }; } createWidget() { let factory = new FileEditorFactory({ editorServices: this.editorServices, factoryOptions: { name: 'Editor', fileTypes: ['*'] } }); const context = new Context({ manager: new ServiceManagerMock(), factory: new TextModelFactory(), path: this.documentOptions.path }); void context.initialize(true); void context.sessionContext.initialize(); return factory.createNew(context); } dispose() { super.dispose(); } } export class NotebookTestEnvironment extends TestEnvironment { getAdapterType() { return MockNotebookAdapter; } get notebook() { return this.widget.content; } getDefaults() { return { language: 'python', path: 'notebook.ipynb', fileExtension: 'py', overridesRegistry: {}, foreignCodeExtractors: new CodeExtractorsManager(), hasLspSupportedFile: false, standalone: true }; } createWidget() { const startKernel = true; let context = new Context({ manager: new ServiceManagerMock(), factory: new NotebookModelFactory({}), path: this.documentOptions.path, kernelPreference: { shouldStart: startKernel, canStart: startKernel, autoStartDefault: startKernel } }); void context.initialize(true); void context.sessionContext.initialize(); const editorFactory = this.editorServices.factoryService.newInlineEditor.bind(this.editorServices.factoryService); return new NotebookPanel({ content: new Notebook({ rendermime: defaultRenderMime(), contentFactory: new Notebook.ContentFactory({ editorFactory }), mimeTypeService: this.editorServices.mimeTypeService, notebookConfig: { ...StaticNotebook.defaultNotebookConfig, windowingMode: 'none' } }), context }); } } export function codeCell(source, metadata = { trusted: false }) { return { cell_type: 'code', source: source, metadata: metadata, execution_count: null, outputs: [] }; } export function setNotebookContent(notebook, cells, metadata = pythonNotebookMetadata) { let testNotebook = { cells: cells, metadata: metadata }; const model = new NotebookModel(); model.fromJSON(testNotebook); notebook.model = model; } export const pythonNotebookMetadata = { kernelspec: { display_name: 'Python [default]', language: 'python', name: 'python3' }, language_info: { codemirror_mode: { name: 'ipython', version: 3 }, fileExtension: '.py', mimetype: 'text/x-python', name: 'python', nbconvert_exporter: 'python', pygments_lexer: 'ipython3', version: '3.5.2' }, orig_nbformat: 4.1 }; export function showAllCells(notebook) { notebook.show(); // iterate over every cell to activate the editors for (let i = 0; i < notebook.model.cells.length; i++) { notebook.activeCellIndex = i; notebook.activeCell.show(); } } export function getCellsJSON(notebook) { let cells = []; for (let i = 0; i < notebook.model.cells.length; i++) { cells.push(notebook.model.cells.get(i)); } return cells.map(cell => cell.toJSON()); } //# sourceMappingURL=testutils.js.map