monaco-editor-core
Version:
A browser based code editor
341 lines (340 loc) • 15.7 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); }
};
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 };