UNPKG

monaco-editor-core

Version:

A browser based code editor

341 lines (340 loc) • 15.7 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); } }; import * as dom from '../../../../base/browser/dom.js'; import { Disposable } from '../../../../base/common/lifecycle.js'; import { TokenizationRegistry } from '../../../common/languages.js'; import { HoverOperation } from './hoverOperation.js'; import { HoverParticipantRegistry, HoverRangeAnchor } from './hoverTypes.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js'; import { ContentHoverWidget } from './contentHoverWidget.js'; import { ContentHoverComputer } from './contentHoverComputer.js'; import { HoverResult } from './contentHoverTypes.js'; import { Emitter } from '../../../../base/common/event.js'; import { RenderedContentHover } from './contentHoverRendered.js'; import { isMousePositionWithinElement } from './hoverUtils.js'; let ContentHoverWidgetWrapper = class ContentHoverWidgetWrapper extends Disposable { constructor(_editor, _instantiationService, _keybindingService) { super(); this._editor = _editor; this._instantiationService = _instantiationService; this._keybindingService = _keybindingService; this._currentResult = null; this._onContentsChanged = this._register(new Emitter()); this.onContentsChanged = this._onContentsChanged.event; this._contentHoverWidget = this._register(this._instantiationService.createInstance(ContentHoverWidget, this._editor)); this._participants = this._initializeHoverParticipants(); this._computer = new ContentHoverComputer(this._editor, this._participants); this._hoverOperation = this._register(new HoverOperation(this._editor, this._computer)); this._registerListeners(); } _initializeHoverParticipants() { const participants = []; for (const participant of HoverParticipantRegistry.getAll()) { const participantInstance = this._instantiationService.createInstance(participant, this._editor); participants.push(participantInstance); } participants.sort((p1, p2) => p1.hoverOrdinal - p2.hoverOrdinal); this._register(this._contentHoverWidget.onDidResize(() => { this._participants.forEach(participant => participant.handleResize?.()); })); return participants; } _registerListeners() { this._register(this._hoverOperation.onResult((result) => { if (!this._computer.anchor) { // invalid state, ignore result return; } const messages = (result.hasLoadingMessage ? this._addLoadingMessage(result.value) : result.value); this._withResult(new HoverResult(this._computer.anchor, messages, result.isComplete)); })); const contentHoverWidgetNode = this._contentHoverWidget.getDomNode(); this._register(dom.addStandardDisposableListener(contentHoverWidgetNode, 'keydown', (e) => { if (e.equals(9 /* KeyCode.Escape */)) { this.hide(); } })); this._register(dom.addStandardDisposableListener(contentHoverWidgetNode, 'mouseleave', (e) => { this._onMouseLeave(e); })); this._register(TokenizationRegistry.onDidChange(() => { if (this._contentHoverWidget.position && this._currentResult) { this._setCurrentResult(this._currentResult); // render again } })); } /** * Returns true if the hover shows now or will show. */ _startShowingOrUpdateHover(anchor, mode, source, focus, mouseEvent) { const contentHoverIsVisible = this._contentHoverWidget.position && this._currentResult; if (!contentHoverIsVisible) { if (anchor) { this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); return true; } return false; } const isHoverSticky = this._editor.getOption(60 /* EditorOption.hover */).sticky; const isMouseGettingCloser = mouseEvent && this._contentHoverWidget.isMouseGettingCloser(mouseEvent.event.posx, mouseEvent.event.posy); const isHoverStickyAndIsMouseGettingCloser = isHoverSticky && isMouseGettingCloser; // The mouse is getting closer to the hover, so we will keep the hover untouched // But we will kick off a hover update at the new anchor, insisting on keeping the hover visible. if (isHoverStickyAndIsMouseGettingCloser) { if (anchor) { this._startHoverOperationIfNecessary(anchor, mode, source, focus, true); } return true; } // If mouse is not getting closer and anchor not defined, hide the hover if (!anchor) { this._setCurrentResult(null); return false; } // If mouse if not getting closer and anchor is defined, and the new anchor is the same as the previous anchor const currentAnchorEqualsPreviousAnchor = this._currentResult.anchor.equals(anchor); if (currentAnchorEqualsPreviousAnchor) { return true; } // If mouse if not getting closer and anchor is defined, and the new anchor is not compatible with the previous anchor const currentAnchorCompatibleWithPreviousAnchor = anchor.canAdoptVisibleHover(this._currentResult.anchor, this._contentHoverWidget.position); if (!currentAnchorCompatibleWithPreviousAnchor) { this._setCurrentResult(null); this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); return true; } // We aren't getting any closer to the hover, so we will filter existing results // and keep those which also apply to the new anchor. this._setCurrentResult(this._currentResult.filter(anchor)); this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); return true; } _startHoverOperationIfNecessary(anchor, mode, source, focus, insistOnKeepingHoverVisible) { const currentAnchorEqualToPreviousHover = this._computer.anchor && this._computer.anchor.equals(anchor); if (currentAnchorEqualToPreviousHover) { return; } this._hoverOperation.cancel(); this._computer.anchor = anchor; this._computer.shouldFocus = focus; this._computer.source = source; this._computer.insistOnKeepingHoverVisible = insistOnKeepingHoverVisible; this._hoverOperation.start(mode); } _setCurrentResult(hoverResult) { let currentHoverResult = hoverResult; const currentResultEqualToPreviousResult = this._currentResult === currentHoverResult; if (currentResultEqualToPreviousResult) { return; } const currentHoverResultIsEmpty = currentHoverResult && currentHoverResult.hoverParts.length === 0; if (currentHoverResultIsEmpty) { currentHoverResult = null; } this._currentResult = currentHoverResult; if (this._currentResult) { this._showHover(this._currentResult); } else { this._hideHover(); } } _addLoadingMessage(result) { if (!this._computer.anchor) { return result; } for (const participant of this._participants) { if (!participant.createLoadingMessage) { continue; } const loadingMessage = participant.createLoadingMessage(this._computer.anchor); if (!loadingMessage) { continue; } return result.slice(0).concat([loadingMessage]); } return result; } _withResult(hoverResult) { const previousHoverIsVisibleWithCompleteResult = this._contentHoverWidget.position && this._currentResult && this._currentResult.isComplete; if (!previousHoverIsVisibleWithCompleteResult) { this._setCurrentResult(hoverResult); } // The hover is visible with a previous complete result. const isCurrentHoverResultComplete = hoverResult.isComplete; if (!isCurrentHoverResultComplete) { // Instead of rendering the new partial result, we wait for the result to be complete. return; } const currentHoverResultIsEmpty = hoverResult.hoverParts.length === 0; const insistOnKeepingPreviousHoverVisible = this._computer.insistOnKeepingHoverVisible; const shouldKeepPreviousHoverVisible = currentHoverResultIsEmpty && insistOnKeepingPreviousHoverVisible; if (shouldKeepPreviousHoverVisible) { // The hover would now hide normally, so we'll keep the previous messages return; } this._setCurrentResult(hoverResult); } _showHover(hoverResult) { const context = this._getHoverContext(); this._renderedContentHover = new RenderedContentHover(this._editor, hoverResult, this._participants, this._computer, context, this._keybindingService); if (this._renderedContentHover.domNodeHasChildren) { this._contentHoverWidget.show(this._renderedContentHover); } else { this._renderedContentHover.dispose(); } } _hideHover() { this._contentHoverWidget.hide(); } _getHoverContext() { const hide = () => { this.hide(); }; const onContentsChanged = () => { this._onContentsChanged.fire(); this._contentHoverWidget.onContentsChanged(); }; const setMinimumDimensions = (dimensions) => { this._contentHoverWidget.setMinimumDimensions(dimensions); }; return { hide, onContentsChanged, setMinimumDimensions }; } showsOrWillShow(mouseEvent) { const isContentWidgetResizing = this._contentHoverWidget.isResizing; if (isContentWidgetResizing) { return true; } const anchorCandidates = this._findHoverAnchorCandidates(mouseEvent); const anchorCandidatesExist = anchorCandidates.length > 0; if (!anchorCandidatesExist) { return this._startShowingOrUpdateHover(null, 0 /* HoverStartMode.Delayed */, 0 /* HoverStartSource.Mouse */, false, mouseEvent); } const anchor = anchorCandidates[0]; return this._startShowingOrUpdateHover(anchor, 0 /* HoverStartMode.Delayed */, 0 /* HoverStartSource.Mouse */, false, mouseEvent); } _findHoverAnchorCandidates(mouseEvent) { const anchorCandidates = []; for (const participant of this._participants) { if (!participant.suggestHoverAnchor) { continue; } const anchor = participant.suggestHoverAnchor(mouseEvent); if (!anchor) { continue; } anchorCandidates.push(anchor); } const target = mouseEvent.target; switch (target.type) { case 6 /* MouseTargetType.CONTENT_TEXT */: { anchorCandidates.push(new HoverRangeAnchor(0, target.range, mouseEvent.event.posx, mouseEvent.event.posy)); break; } case 7 /* MouseTargetType.CONTENT_EMPTY */: { const epsilon = this._editor.getOption(50 /* EditorOption.fontInfo */).typicalHalfwidthCharacterWidth / 2; // Let hover kick in even when the mouse is technically in the empty area after a line, given the distance is small enough const mouseIsWithinLinesAndCloseToHover = !target.detail.isAfterLines && typeof target.detail.horizontalDistanceToText === 'number' && target.detail.horizontalDistanceToText < epsilon; if (!mouseIsWithinLinesAndCloseToHover) { break; } anchorCandidates.push(new HoverRangeAnchor(0, target.range, mouseEvent.event.posx, mouseEvent.event.posy)); break; } } anchorCandidates.sort((a, b) => b.priority - a.priority); return anchorCandidates; } _onMouseLeave(e) { const editorDomNode = this._editor.getDomNode(); const isMousePositionOutsideOfEditor = !editorDomNode || !isMousePositionWithinElement(editorDomNode, e.x, e.y); if (isMousePositionOutsideOfEditor) { this.hide(); } } startShowingAtRange(range, mode, source, focus) { this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null); } async updateHoverVerbosityLevel(action, index, focus) { this._renderedContentHover?.updateHoverVerbosityLevel(action, index, focus); } focusedHoverPartIndex() { return this._renderedContentHover?.focusedHoverPartIndex ?? -1; } containsNode(node) { return (node ? this._contentHoverWidget.getDomNode().contains(node) : false); } focus() { this._contentHoverWidget.focus(); } scrollUp() { this._contentHoverWidget.scrollUp(); } scrollDown() { this._contentHoverWidget.scrollDown(); } scrollLeft() { this._contentHoverWidget.scrollLeft(); } scrollRight() { this._contentHoverWidget.scrollRight(); } pageUp() { this._contentHoverWidget.pageUp(); } pageDown() { this._contentHoverWidget.pageDown(); } goToTop() { this._contentHoverWidget.goToTop(); } goToBottom() { this._contentHoverWidget.goToBottom(); } hide() { this._computer.anchor = null; this._hoverOperation.cancel(); this._setCurrentResult(null); } getDomNode() { return this._contentHoverWidget.getDomNode(); } get isColorPickerVisible() { return this._renderedContentHover?.isColorPickerVisible() ?? false; } get isVisibleFromKeyboard() { return this._contentHoverWidget.isVisibleFromKeyboard; } get isVisible() { return this._contentHoverWidget.isVisible; } get isFocused() { return this._contentHoverWidget.isFocused; } get isResizing() { return this._contentHoverWidget.isResizing; } get widget() { return this._contentHoverWidget; } }; ContentHoverWidgetWrapper = __decorate([ __param(1, IInstantiationService), __param(2, IKeybindingService) ], ContentHoverWidgetWrapper); export { ContentHoverWidgetWrapper };