monaco-editor-core
Version:
A browser based code editor
380 lines (379 loc) • 19.6 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var ContentHoverWidget_1;
import * as dom from '../../../../base/browser/dom.js';
import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';
import { ResizableContentWidget } from './resizableContentWidget.js';
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IAccessibilityService } from '../../../../platform/accessibility/common/accessibility.js';
import { EditorContextKeys } from '../../../common/editorContextKeys.js';
import { getHoverAccessibleViewHint, HoverWidget } from '../../../../base/browser/ui/hover/hoverWidget.js';
import { Emitter } from '../../../../base/common/event.js';
const HORIZONTAL_SCROLLING_BY = 30;
const CONTAINER_HEIGHT_PADDING = 6;
let ContentHoverWidget = class ContentHoverWidget extends ResizableContentWidget {
static { ContentHoverWidget_1 = this; }
static { this.ID = 'editor.contrib.resizableContentHoverWidget'; }
static { this._lastDimensions = new dom.Dimension(0, 0); }
get isVisibleFromKeyboard() {
return (this._renderedHover?.source === 1 /* HoverStartSource.Keyboard */);
}
get isVisible() {
return this._hoverVisibleKey.get() ?? false;
}
get isFocused() {
return this._hoverFocusedKey.get() ?? false;
}
constructor(editor, contextKeyService, _configurationService, _accessibilityService, _keybindingService) {
const minimumHeight = editor.getOption(67 /* EditorOption.lineHeight */) + 8;
const minimumWidth = 150;
const minimumSize = new dom.Dimension(minimumWidth, minimumHeight);
super(editor, minimumSize);
this._configurationService = _configurationService;
this._accessibilityService = _accessibilityService;
this._keybindingService = _keybindingService;
this._hover = this._register(new HoverWidget());
this._onDidResize = this._register(new Emitter());
this.onDidResize = this._onDidResize.event;
this._minimumSize = minimumSize;
this._hoverVisibleKey = EditorContextKeys.hoverVisible.bindTo(contextKeyService);
this._hoverFocusedKey = EditorContextKeys.hoverFocused.bindTo(contextKeyService);
dom.append(this._resizableNode.domNode, this._hover.containerDomNode);
this._resizableNode.domNode.style.zIndex = '50';
this._register(this._editor.onDidLayoutChange(() => {
if (this.isVisible) {
this._updateMaxDimensions();
}
}));
this._register(this._editor.onDidChangeConfiguration((e) => {
if (e.hasChanged(50 /* EditorOption.fontInfo */)) {
this._updateFont();
}
}));
const focusTracker = this._register(dom.trackFocus(this._resizableNode.domNode));
this._register(focusTracker.onDidFocus(() => {
this._hoverFocusedKey.set(true);
}));
this._register(focusTracker.onDidBlur(() => {
this._hoverFocusedKey.set(false);
}));
this._setRenderedHover(undefined);
this._editor.addContentWidget(this);
}
dispose() {
super.dispose();
this._renderedHover?.dispose();
this._editor.removeContentWidget(this);
}
getId() {
return ContentHoverWidget_1.ID;
}
static _applyDimensions(container, width, height) {
const transformedWidth = typeof width === 'number' ? `${width}px` : width;
const transformedHeight = typeof height === 'number' ? `${height}px` : height;
container.style.width = transformedWidth;
container.style.height = transformedHeight;
}
_setContentsDomNodeDimensions(width, height) {
const contentsDomNode = this._hover.contentsDomNode;
return ContentHoverWidget_1._applyDimensions(contentsDomNode, width, height);
}
_setContainerDomNodeDimensions(width, height) {
const containerDomNode = this._hover.containerDomNode;
return ContentHoverWidget_1._applyDimensions(containerDomNode, width, height);
}
_setHoverWidgetDimensions(width, height) {
this._setContentsDomNodeDimensions(width, height);
this._setContainerDomNodeDimensions(width, height);
this._layoutContentWidget();
}
static _applyMaxDimensions(container, width, height) {
const transformedWidth = typeof width === 'number' ? `${width}px` : width;
const transformedHeight = typeof height === 'number' ? `${height}px` : height;
container.style.maxWidth = transformedWidth;
container.style.maxHeight = transformedHeight;
}
_setHoverWidgetMaxDimensions(width, height) {
ContentHoverWidget_1._applyMaxDimensions(this._hover.contentsDomNode, width, height);
ContentHoverWidget_1._applyMaxDimensions(this._hover.containerDomNode, width, height);
this._hover.containerDomNode.style.setProperty('--vscode-hover-maxWidth', typeof width === 'number' ? `${width}px` : width);
this._layoutContentWidget();
}
_setAdjustedHoverWidgetDimensions(size) {
this._setHoverWidgetMaxDimensions('none', 'none');
const width = size.width;
const height = size.height;
this._setHoverWidgetDimensions(width, height);
}
_updateResizableNodeMaxDimensions() {
const maxRenderingWidth = this._findMaximumRenderingWidth() ?? Infinity;
const maxRenderingHeight = this._findMaximumRenderingHeight() ?? Infinity;
this._resizableNode.maxSize = new dom.Dimension(maxRenderingWidth, maxRenderingHeight);
this._setHoverWidgetMaxDimensions(maxRenderingWidth, maxRenderingHeight);
}
_resize(size) {
ContentHoverWidget_1._lastDimensions = new dom.Dimension(size.width, size.height);
this._setAdjustedHoverWidgetDimensions(size);
this._resizableNode.layout(size.height, size.width);
this._updateResizableNodeMaxDimensions();
this._hover.scrollbar.scanDomNode();
this._editor.layoutContentWidget(this);
this._onDidResize.fire();
}
_findAvailableSpaceVertically() {
const position = this._renderedHover?.showAtPosition;
if (!position) {
return;
}
return this._positionPreference === 1 /* ContentWidgetPositionPreference.ABOVE */ ?
this._availableVerticalSpaceAbove(position)
: this._availableVerticalSpaceBelow(position);
}
_findMaximumRenderingHeight() {
const availableSpace = this._findAvailableSpaceVertically();
if (!availableSpace) {
return;
}
// Padding needed in order to stop the resizing down to a smaller height
let maximumHeight = CONTAINER_HEIGHT_PADDING;
Array.from(this._hover.contentsDomNode.children).forEach((hoverPart) => {
maximumHeight += hoverPart.clientHeight;
});
return Math.min(availableSpace, maximumHeight);
}
_isHoverTextOverflowing() {
// To find out if the text is overflowing, we will disable wrapping, check the widths, and then re-enable wrapping
this._hover.containerDomNode.style.setProperty('--vscode-hover-whiteSpace', 'nowrap');
this._hover.containerDomNode.style.setProperty('--vscode-hover-sourceWhiteSpace', 'nowrap');
const overflowing = Array.from(this._hover.contentsDomNode.children).some((hoverElement) => {
return hoverElement.scrollWidth > hoverElement.clientWidth;
});
this._hover.containerDomNode.style.removeProperty('--vscode-hover-whiteSpace');
this._hover.containerDomNode.style.removeProperty('--vscode-hover-sourceWhiteSpace');
return overflowing;
}
_findMaximumRenderingWidth() {
if (!this._editor || !this._editor.hasModel()) {
return;
}
const overflowing = this._isHoverTextOverflowing();
const initialWidth = (typeof this._contentWidth === 'undefined'
? 0
: this._contentWidth - 2 // - 2 for the borders
);
if (overflowing || this._hover.containerDomNode.clientWidth < initialWidth) {
const bodyBoxWidth = dom.getClientArea(this._hover.containerDomNode.ownerDocument.body).width;
const horizontalPadding = 14;
return bodyBoxWidth - horizontalPadding;
}
else {
return this._hover.containerDomNode.clientWidth + 2;
}
}
isMouseGettingCloser(posx, posy) {
if (!this._renderedHover) {
return false;
}
if (this._renderedHover.initialMousePosX === undefined || this._renderedHover.initialMousePosY === undefined) {
this._renderedHover.initialMousePosX = posx;
this._renderedHover.initialMousePosY = posy;
return false;
}
const widgetRect = dom.getDomNodePagePosition(this.getDomNode());
if (this._renderedHover.closestMouseDistance === undefined) {
this._renderedHover.closestMouseDistance = computeDistanceFromPointToRectangle(this._renderedHover.initialMousePosX, this._renderedHover.initialMousePosY, widgetRect.left, widgetRect.top, widgetRect.width, widgetRect.height);
}
const distance = computeDistanceFromPointToRectangle(posx, posy, widgetRect.left, widgetRect.top, widgetRect.width, widgetRect.height);
if (distance > this._renderedHover.closestMouseDistance + 4 /* tolerance of 4 pixels */) {
// The mouse is getting farther away
return false;
}
this._renderedHover.closestMouseDistance = Math.min(this._renderedHover.closestMouseDistance, distance);
return true;
}
_setRenderedHover(renderedHover) {
this._renderedHover?.dispose();
this._renderedHover = renderedHover;
this._hoverVisibleKey.set(!!renderedHover);
this._hover.containerDomNode.classList.toggle('hidden', !renderedHover);
}
_updateFont() {
const { fontSize, lineHeight } = this._editor.getOption(50 /* EditorOption.fontInfo */);
const contentsDomNode = this._hover.contentsDomNode;
contentsDomNode.style.fontSize = `${fontSize}px`;
contentsDomNode.style.lineHeight = `${lineHeight / fontSize}`;
const codeClasses = Array.prototype.slice.call(this._hover.contentsDomNode.getElementsByClassName('code'));
codeClasses.forEach(node => this._editor.applyFontInfo(node));
}
_updateContent(node) {
const contentsDomNode = this._hover.contentsDomNode;
contentsDomNode.style.paddingBottom = '';
contentsDomNode.textContent = '';
contentsDomNode.appendChild(node);
}
_layoutContentWidget() {
this._editor.layoutContentWidget(this);
this._hover.onContentsChanged();
}
_updateMaxDimensions() {
const height = Math.max(this._editor.getLayoutInfo().height / 4, 250, ContentHoverWidget_1._lastDimensions.height);
const width = Math.max(this._editor.getLayoutInfo().width * 0.66, 500, ContentHoverWidget_1._lastDimensions.width);
this._setHoverWidgetMaxDimensions(width, height);
}
_render(renderedHover) {
this._setRenderedHover(renderedHover);
this._updateFont();
this._updateContent(renderedHover.domNode);
this._updateMaxDimensions();
this.onContentsChanged();
// Simply force a synchronous render on the editor
// such that the widget does not really render with left = '0px'
this._editor.render();
}
getPosition() {
if (!this._renderedHover) {
return null;
}
return {
position: this._renderedHover.showAtPosition,
secondaryPosition: this._renderedHover.showAtSecondaryPosition,
positionAffinity: this._renderedHover.shouldAppearBeforeContent ? 3 /* PositionAffinity.LeftOfInjectedText */ : undefined,
preference: [this._positionPreference ?? 1 /* ContentWidgetPositionPreference.ABOVE */]
};
}
show(renderedHover) {
if (!this._editor || !this._editor.hasModel()) {
return;
}
this._render(renderedHover);
const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode);
const widgetPosition = renderedHover.showAtPosition;
this._positionPreference = this._findPositionPreference(widgetHeight, widgetPosition) ?? 1 /* ContentWidgetPositionPreference.ABOVE */;
// See https://github.com/microsoft/vscode/issues/140339
// TODO: Doing a second layout of the hover after force rendering the editor
this.onContentsChanged();
if (renderedHover.shouldFocus) {
this._hover.containerDomNode.focus();
}
this._onDidResize.fire();
// The aria label overrides the label, so if we add to it, add the contents of the hover
const hoverFocused = this._hover.containerDomNode.ownerDocument.activeElement === this._hover.containerDomNode;
const accessibleViewHint = hoverFocused && getHoverAccessibleViewHint(this._configurationService.getValue('accessibility.verbosity.hover') === true && this._accessibilityService.isScreenReaderOptimized(), this._keybindingService.lookupKeybinding('editor.action.accessibleView')?.getAriaLabel() ?? '');
if (accessibleViewHint) {
this._hover.contentsDomNode.ariaLabel = this._hover.contentsDomNode.textContent + ', ' + accessibleViewHint;
}
}
hide() {
if (!this._renderedHover) {
return;
}
const hoverStoleFocus = this._renderedHover.shouldFocus || this._hoverFocusedKey.get();
this._setRenderedHover(undefined);
this._resizableNode.maxSize = new dom.Dimension(Infinity, Infinity);
this._resizableNode.clearSashHoverState();
this._hoverFocusedKey.set(false);
this._editor.layoutContentWidget(this);
if (hoverStoleFocus) {
this._editor.focus();
}
}
_removeConstraintsRenderNormally() {
// Added because otherwise the initial size of the hover content is smaller than should be
const layoutInfo = this._editor.getLayoutInfo();
this._resizableNode.layout(layoutInfo.height, layoutInfo.width);
this._setHoverWidgetDimensions('auto', 'auto');
}
setMinimumDimensions(dimensions) {
// We combine the new minimum dimensions with the previous ones
this._minimumSize = new dom.Dimension(Math.max(this._minimumSize.width, dimensions.width), Math.max(this._minimumSize.height, dimensions.height));
this._updateMinimumWidth();
}
_updateMinimumWidth() {
const width = (typeof this._contentWidth === 'undefined'
? this._minimumSize.width
: Math.min(this._contentWidth, this._minimumSize.width));
// We want to avoid that the hover is artificially large, so we use the content width as minimum width
this._resizableNode.minSize = new dom.Dimension(width, this._minimumSize.height);
}
onContentsChanged() {
this._removeConstraintsRenderNormally();
const containerDomNode = this._hover.containerDomNode;
let height = dom.getTotalHeight(containerDomNode);
let width = dom.getTotalWidth(containerDomNode);
this._resizableNode.layout(height, width);
this._setHoverWidgetDimensions(width, height);
height = dom.getTotalHeight(containerDomNode);
width = dom.getTotalWidth(containerDomNode);
this._contentWidth = width;
this._updateMinimumWidth();
this._resizableNode.layout(height, width);
if (this._renderedHover?.showAtPosition) {
const widgetHeight = dom.getTotalHeight(this._hover.containerDomNode);
this._positionPreference = this._findPositionPreference(widgetHeight, this._renderedHover.showAtPosition);
}
this._layoutContentWidget();
}
focus() {
this._hover.containerDomNode.focus();
}
scrollUp() {
const scrollTop = this._hover.scrollbar.getScrollPosition().scrollTop;
const fontInfo = this._editor.getOption(50 /* EditorOption.fontInfo */);
this._hover.scrollbar.setScrollPosition({ scrollTop: scrollTop - fontInfo.lineHeight });
}
scrollDown() {
const scrollTop = this._hover.scrollbar.getScrollPosition().scrollTop;
const fontInfo = this._editor.getOption(50 /* EditorOption.fontInfo */);
this._hover.scrollbar.setScrollPosition({ scrollTop: scrollTop + fontInfo.lineHeight });
}
scrollLeft() {
const scrollLeft = this._hover.scrollbar.getScrollPosition().scrollLeft;
this._hover.scrollbar.setScrollPosition({ scrollLeft: scrollLeft - HORIZONTAL_SCROLLING_BY });
}
scrollRight() {
const scrollLeft = this._hover.scrollbar.getScrollPosition().scrollLeft;
this._hover.scrollbar.setScrollPosition({ scrollLeft: scrollLeft + HORIZONTAL_SCROLLING_BY });
}
pageUp() {
const scrollTop = this._hover.scrollbar.getScrollPosition().scrollTop;
const scrollHeight = this._hover.scrollbar.getScrollDimensions().height;
this._hover.scrollbar.setScrollPosition({ scrollTop: scrollTop - scrollHeight });
}
pageDown() {
const scrollTop = this._hover.scrollbar.getScrollPosition().scrollTop;
const scrollHeight = this._hover.scrollbar.getScrollDimensions().height;
this._hover.scrollbar.setScrollPosition({ scrollTop: scrollTop + scrollHeight });
}
goToTop() {
this._hover.scrollbar.setScrollPosition({ scrollTop: 0 });
}
goToBottom() {
this._hover.scrollbar.setScrollPosition({ scrollTop: this._hover.scrollbar.getScrollDimensions().scrollHeight });
}
};
ContentHoverWidget = ContentHoverWidget_1 = __decorate([
__param(1, IContextKeyService),
__param(2, IConfigurationService),
__param(3, IAccessibilityService),
__param(4, IKeybindingService)
], ContentHoverWidget);
export { ContentHoverWidget };
function computeDistanceFromPointToRectangle(pointX, pointY, left, top, width, height) {
const x = (left + width / 2); // x center of rectangle
const y = (top + height / 2); // y center of rectangle
const dx = Math.max(Math.abs(pointX - x) - width / 2, 0);
const dy = Math.max(Math.abs(pointY - y) - height / 2, 0);
return Math.sqrt(dx * dx + dy * dy);
}