UNPKG

@jupyter-lsp/jupyterlab-lsp

Version:

Language Server Protocol integration for JupyterLab

252 lines 9.23 kB
import { IEditorExtensionRegistry } from '@jupyterlab/codemirror'; import { IEditorTracker } from '@jupyterlab/fileeditor'; import { ILSPFeatureManager, ILSPDocumentConnectionManager } from '@jupyterlab/lsp'; import { IRenderMimeRegistry } from '@jupyterlab/rendermime'; import { ISettingRegistry } from '@jupyterlab/settingregistry'; import { ITableOfContentsRegistry, TableOfContentsModel, TableOfContentsFactory } from '@jupyterlab/toc'; import { Throttler } from '@lumino/polling'; import { ContextAssembler } from '../context'; import { FeatureSettings, Feature } from '../feature'; import { SymbolTag } from '../lsp'; import { PLUGIN_ID } from '../tokens'; import { BrowserConsole } from '../virtual/console'; /** * Table of content model using LSP. */ class LSPTableOfContentsModel extends TableOfContentsModel { constructor(widget, symbolFeature, connectionManager, configuration) { super(widget, configuration); this.widget = widget; this.symbolFeature = symbolFeature; this.connectionManager = connectionManager; } /** * Type of document supported by the model. * * #### Notes * A `data-document-type` attribute with this value will be set * on the tree view `.jp-TableOfContents-content[data-document-type="..."]` */ get documentType() { return 'lsp'; } /** * List of configuration options supported by the model. */ get supportedOptions() { return ['maximalDepth', 'numberHeaders']; } /** * Produce the headings for a document. * * @returns The list of new headings or `null` if nothing needs to be updated. */ async getHeadings() { if (!this.isActive) { return null; } const adapter = [...this.connectionManager.adapters.values()].find(adapter => adapter.widget.node.contains(this.widget.node)); if (!(adapter === null || adapter === void 0 ? void 0 : adapter.virtualDocument)) { return null; } const headings = new Array(); const symbols = await this.symbolFeature.getSymbols.invoke(adapter, adapter.virtualDocument); if (!symbols) { return null; } const processBreadthFirst = (elements, level = 1) => { for (const symbol of elements) { headings.push({ text: symbol.name, level, symbol }); if (symbol.children) { processBreadthFirst(symbol.children, level + 1); } } }; processBreadthFirst(symbols); return headings; } } class LSPEditorTableOfContentsFactory extends TableOfContentsFactory { constructor(tracker, symbolFeature, connectionManager) { super(tracker); this.symbolFeature = symbolFeature; this.connectionManager = connectionManager; } /** * Whether the factory can handle the widget or not. * * @param widget - widget * @returns boolean indicating a ToC can be generated */ isApplicable(widget) { const isApplicable = super.isApplicable(widget); if (isApplicable) { return this.symbolFeature.isApplicable(widget); } return false; } /** * Create a new table of contents model for the widget * * @param widget - widget * @param configuration - Table of contents configuration * @returns The table of contents model */ createNew(widget, configuration) { const model = super.createNew(widget, configuration); const onActiveHeadingChanged = (model, heading) => { if (heading) { widget.content.editor.setCursorPosition({ line: heading.symbol.selectionRange.start.line, column: heading.symbol.selectionRange.start.character }); } }; model.activeHeadingChanged.connect(onActiveHeadingChanged); widget.disposed.connect(() => { model.activeHeadingChanged.disconnect(onActiveHeadingChanged); }); return model; } /** * Create a new table of contents model for the widget * * @param widget - widget * @param configuration - Table of contents configuration * @returns The table of contents model */ _createNew(widget, configuration) { return new LSPTableOfContentsModel(widget, this.symbolFeature, this.connectionManager, configuration); } } function isSymbolInformationArray(response) { return (response.length > 0 && !!response[0].location); } export class SymbolFeature extends Feature { constructor(options) { super(options); this.capabilities = { textDocument: { documentSymbol: { dynamicRegistration: true, tagSupport: { valueSet: [SymbolTag.Deprecated] }, hierarchicalDocumentSymbolSupport: true } } }; this.id = SymbolFeature.id; this.console = new BrowserConsole().scope('Symbol'); this.settings = options.settings; this.contextAssembler = options.contextAssembler; const { tocRegistry, editorTracker } = options; this.connectionManager = options.connectionManager; if (tocRegistry && editorTracker) { tocRegistry.add(new LSPEditorTableOfContentsFactory(editorTracker, this, this.connectionManager)); } this.cache = new WeakMap(); this.getSymbols = this.createThrottler(); this.settings.changed.connect(() => { this.getSymbols = this.createThrottler(); }); } isApplicable(widget) { const adapter = [...this.connectionManager.adapters.values()].find(adapter => adapter.widget.node.contains(widget.node)); if (!(adapter === null || adapter === void 0 ? void 0 : adapter.virtualDocument)) { return false; } const connection = this.connectionManager.connections.get(adapter.virtualDocument.uri); if (!(connection && connection.isReady && connection.serverCapabilities.documentSymbolProvider)) { return false; } return true; } createThrottler() { return new Throttler(this._getSymbols.bind(this), { limit: this.settings.composite.throttlerDelay || 0, edge: 'trailing' }); } async _getSymbols(adapter, virtualDocument) { // TODO: return from cache await adapter.ready; const connection = this.connectionManager.connections.get(virtualDocument.uri); if (!(connection.isReady && connection.serverCapabilities.documentSymbolProvider)) { return null; } // for popup use // 'workspace/symbol' const response = await connection.clientRequests['textDocument/documentSymbol'].request({ textDocument: { uri: virtualDocument.documentInfo.uri } }); // TODO: for some reason after reloading JupyterLab server errors out // unless a file in project gets opened. This may indicate that open // notifications are not sent out properly. return this._handleSymbols(response); } _handleSymbols(response) { if (!response) { return null; } if (isSymbolInformationArray(response)) { return response.map(s => { return { name: s.name, kind: s.kind, tags: s.tags, deprecated: s.deprecated, range: s.location.range, selectionRange: s.location.range }; }); } return response; } } (function (SymbolFeature) { SymbolFeature.id = PLUGIN_ID + ':symbol'; })(SymbolFeature || (SymbolFeature = {})); export const SYMBOL_PLUGIN = { id: SymbolFeature.id, requires: [ ILSPFeatureManager, ISettingRegistry, IRenderMimeRegistry, IEditorExtensionRegistry, ILSPDocumentConnectionManager ], optional: [ITableOfContentsRegistry, IEditorTracker], autoStart: true, activate: async (app, featureManager, settingRegistry, renderMimeRegistry, editorExtensionRegistry, connectionManager, tocRegistry, editorTracker) => { const contextAssembler = new ContextAssembler({ app, connectionManager }); const settings = new FeatureSettings(settingRegistry, PLUGIN_ID + ':hover' // SymbolFeature.id ); await settings.ready; if (settings.composite.disable) { return; } const feature = new SymbolFeature({ settings, renderMimeRegistry, editorExtensionRegistry, connectionManager, contextAssembler, tocRegistry, editorTracker }); featureManager.register(feature); } }; //# sourceMappingURL=symbol.js.map