UNPKG

monaco-editor-core

Version:

A browser based code editor

380 lines (379 loc) • 19.6 kB
/*--------------------------------------------------------------------------------------------- * 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); }