UNPKG

@jupyter-lsp/jupyterlab-lsp

Version:

Language Server Protocol integration for JupyterLab

220 lines 8.11 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. // (Parts of the FreeTooltip code are copy-paste from Tooltip, ideally this would be PRed be merged) import { HoverBox } from '@jupyterlab/apputils'; import { isEqual } from '@jupyterlab/lsp'; import { MimeModel } from '@jupyterlab/rendermime'; import { Tooltip } from '@jupyterlab/tooltip'; import { Widget } from '@lumino/widgets'; import { PositionConverter } from '../converter'; const MIN_HEIGHT = 20; const MAX_HEIGHT = 250; const CLASS_NAME = 'lsp-tooltip'; /** * Tooltip which can be placed at any character, not only at the current position (derived from getCursorPosition) */ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore export class FreeTooltip extends Tooltip { constructor(options) { super(options); this.options = options; this._setGeometry(); } setBundle(bundle) { const model = new MimeModel({ data: bundle }); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const content = this._content; content .renderModel(model) .then(() => this._setGeometry()) .catch(console.warn); } handleEvent(event) { if (this.isHidden || this.isDisposed) { return; } const { node } = this; const target = event.target; switch (event.type) { case 'keydown': { const keyCode = event.keyCode; // ESC or Backspace cancel anyways if (node.contains(target) || (!this.options.hideOnKeyPress && keyCode != 27 && keyCode != 8)) { return; } this.dispose(); break; } default: super.handleEvent(event); break; } } // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore _setGeometry() { // Find the start of the current token for hover box placement. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore const editor = this._editor; const cursor = this.options.position == null ? editor.getCursorPosition() : this.options.position; let position; if (this.options.alignment) { const end = editor.getOffsetAt(cursor); const line = editor.getLine(cursor.line); if (!line) { return; } switch (this.options.alignment) { case 'start': { const tokens = line.substring(0, end).split(/\W+/); const last = tokens[tokens.length - 1]; const start = last ? end - last.length : end; position = editor.getPositionAt(start); break; } case 'end': { const tokens = line.substring(0, end).split(/\W+/); const last = tokens[tokens.length - 1]; const start = last ? end - last.length : end; position = editor.getPositionAt(start); break; } } } else { position = cursor; } if (!position) { return; } const anchor = editor.getCoordinateForPosition(position); const style = window.getComputedStyle(this.node); const paddingLeft = parseInt(style.paddingLeft, 10) || 0; // When the editor is attached to the main area, contain the hover box // to the full area available (rather than to the editor itself); the available // area excludes the toolbar, hence the first Widget child between MainAreaWidget // and editor is preferred. const host = editor.host.closest('.jp-MainAreaWidget > .lm-Widget') || editor.host; // Calculate the geometry of the tooltip. HoverBox.setGeometry({ anchor, host: host, maxHeight: MAX_HEIGHT, minHeight: MIN_HEIGHT, node: this.node, offset: { horizontal: -1 * paddingLeft }, privilege: this.options.privilege || 'below', style: style, outOfViewDisplay: { left: 'stick-inside', right: 'stick-outside', top: 'stick-outside', bottom: 'stick-inside' } }); } setPosition(position) { this.options.position = position; this._setGeometry(); } } function markupToBundle(markup) { return markup.kind === 'plaintext' ? { 'text/plain': markup.value } : { 'text/markdown': markup.value }; } export class EditorTooltipManager { constructor(rendermimeRegistry) { this.rendermimeRegistry = rendermimeRegistry; this.currentTooltip = null; } create(options) { this.remove(); this.currentOptions = options; let { markup, position, adapter } = options; let widget = adapter.widget; const bundle = markupToBundle(markup); const tooltip = new FreeTooltip({ ...(options.tooltip || {}), anchor: widget.content, bundle: bundle, editor: options.ceEditor, rendermime: this.rendermimeRegistry, position: PositionConverter.cm_to_ce(position) }); tooltip.addClass(CLASS_NAME); if (options.className) { tooltip.addClass(options.className); } Widget.attach(tooltip, document.body); this.currentTooltip = tooltip; return tooltip; } showOrCreate(options) { const samePosition = this.currentOptions && isEqual(this.currentOptions.position, options.position); const sameMarkup = this.currentOptions && this.currentOptions.markup.value === options.markup.value && this.currentOptions.markup.kind === options.markup.kind; if (this.currentTooltip !== null && !this.currentTooltip.isDisposed && this.currentOptions && this.currentOptions.adapter === options.adapter && (samePosition || sameMarkup) && this.currentOptions.ceEditor === options.ceEditor && this.currentOptions.id === options.id) { // we only allow either position or markup change, because if both changed, // then we may get into problematic race condition in sizing after bundle update. if (!sameMarkup) { this.currentOptions.markup = options.markup; this.currentTooltip.setBundle(markupToBundle(options.markup)); } if (!samePosition) { // setting geometry only works when visible this.currentTooltip.setPosition(PositionConverter.cm_to_ce(options.position)); } this.show(); return this.currentTooltip; } else { this.remove(); return this.create(options); } } get position() { return this.currentOptions.position; } isShown(id) { var _a; if (id && this.currentOptions && ((_a = this.currentOptions) === null || _a === void 0 ? void 0 : _a.id) !== id) { return false; } return (this.currentTooltip !== null && !this.currentTooltip.isDisposed && this.currentTooltip.isVisible); } hide() { if (this.currentTooltip !== null) { this.currentTooltip.hide(); } } show() { if (this.currentTooltip !== null) { this.currentTooltip.show(); } } remove() { if (this.currentTooltip !== null) { this.currentTooltip.dispose(); this.currentTooltip = null; } } } //# sourceMappingURL=free_tooltip.js.map