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