@jupyter-lsp/jupyterlab-lsp
Version:
Language Server Protocol integration for JupyterLab
352 lines • 12.3 kB
JavaScript
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