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