Path: blob/main/src/vs/editor/contrib/hover/browser/glyphHoverController.ts
4779 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';6import { isModifierKey } from '../../../../base/common/keyCodes.js';7import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';8import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from '../../../browser/editorBrowser.js';9import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js';10import { IEditorContribution, IScrollEvent } from '../../../common/editorCommon.js';11import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';12import { IHoverWidget } from './hoverTypes.js';13import { RunOnceScheduler } from '../../../../base/common/async.js';14import { isMousePositionWithinElement, shouldShowHover } from './hoverUtils.js';15import './hover.css';16import { GlyphHoverWidget } from './glyphHoverWidget.js';1718// sticky hover widget which doesn't disappear on focus out and such19const _sticky = false20// || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this21;2223interface IHoverSettings {24readonly enabled: 'on' | 'off' | 'onKeyboardModifier';25readonly sticky: boolean;26readonly hidingDelay: number;27}2829interface IHoverState {30mouseDown: boolean;31}3233export class GlyphHoverController extends Disposable implements IEditorContribution {3435public static readonly ID = 'editor.contrib.marginHover';3637public shouldKeepOpenOnEditorMouseMoveOrLeave: boolean = false;3839private readonly _listenersStore = new DisposableStore();4041private _glyphWidget: GlyphHoverWidget | undefined;42private _mouseMoveEvent: IEditorMouseEvent | undefined;43private _reactToEditorMouseMoveRunner: RunOnceScheduler;4445private _hoverSettings!: IHoverSettings;46private _hoverState: IHoverState = {47mouseDown: false48};4950constructor(51private readonly _editor: ICodeEditor,52@IInstantiationService private readonly _instantiationService: IInstantiationService53) {54super();55this._reactToEditorMouseMoveRunner = this._register(56new RunOnceScheduler(57() => this._reactToEditorMouseMove(this._mouseMoveEvent), 058)59);60this._hookListeners();61this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {62if (e.hasChanged(EditorOption.hover)) {63this._unhookListeners();64this._hookListeners();65}66}));67}6869static get(editor: ICodeEditor): GlyphHoverController | null {70return editor.getContribution<GlyphHoverController>(GlyphHoverController.ID);71}7273private _hookListeners(): void {7475const hoverOpts = this._editor.getOption(EditorOption.hover);76this._hoverSettings = {77enabled: hoverOpts.enabled,78sticky: hoverOpts.sticky,79hidingDelay: hoverOpts.hidingDelay80};8182if (hoverOpts.enabled !== 'off') {83this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));84this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp()));85this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e)));86this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e)));87} else {88this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e)));89this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e)));90}9192this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e)));93this._listenersStore.add(this._editor.onDidChangeModel(() => {94this._cancelScheduler();95this.hideGlyphHover();96}));97this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler()));98this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e)));99}100101private _unhookListeners(): void {102this._listenersStore.clear();103}104105private _cancelScheduler() {106this._mouseMoveEvent = undefined;107this._reactToEditorMouseMoveRunner.cancel();108}109110private _onEditorScrollChanged(e: IScrollEvent): void {111if (e.scrollTopChanged || e.scrollLeftChanged) {112this.hideGlyphHover();113}114}115116private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {117this._hoverState.mouseDown = true;118const shouldNotHideCurrentHoverWidget = this._isMouseOnGlyphHoverWidget(mouseEvent);119if (shouldNotHideCurrentHoverWidget) {120return;121}122this.hideGlyphHover();123}124125private _isMouseOnGlyphHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean {126const glyphHoverWidgetNode = this._glyphWidget?.getDomNode();127if (glyphHoverWidgetNode) {128return isMousePositionWithinElement(glyphHoverWidgetNode, mouseEvent.event.posx, mouseEvent.event.posy);129}130return false;131}132133private _onEditorMouseUp(): void {134this._hoverState.mouseDown = false;135}136137private _onEditorMouseLeave(mouseEvent: IPartialEditorMouseEvent): void {138if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) {139return;140}141142this._cancelScheduler();143const shouldNotHideCurrentHoverWidget = this._isMouseOnGlyphHoverWidget(mouseEvent);144if (shouldNotHideCurrentHoverWidget) {145return;146}147if (_sticky) {148return;149}150this.hideGlyphHover();151}152153private _shouldNotRecomputeCurrentHoverWidget(mouseEvent: IEditorMouseEvent): boolean {154const isHoverSticky = this._hoverSettings.sticky;155const isMouseOnGlyphHoverWidget = this._isMouseOnGlyphHoverWidget(mouseEvent);156return isHoverSticky && isMouseOnGlyphHoverWidget;157}158159private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void {160if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) {161return;162}163164this._mouseMoveEvent = mouseEvent;165const shouldNotRecomputeCurrentHoverWidget = this._shouldNotRecomputeCurrentHoverWidget(mouseEvent);166if (shouldNotRecomputeCurrentHoverWidget) {167this._reactToEditorMouseMoveRunner.cancel();168return;169}170this._reactToEditorMouseMove(mouseEvent);171}172173private _reactToEditorMouseMove(mouseEvent: IEditorMouseEvent | undefined): void {174175if (!mouseEvent) {176return;177}178if (!shouldShowHover(179this._hoverSettings.enabled,180this._editor.getOption(EditorOption.multiCursorModifier),181mouseEvent182)) {183if (_sticky) {184return;185}186this.hideGlyphHover();187return;188}189const glyphWidgetShowsOrWillShow = this._tryShowHoverWidget(mouseEvent);190if (glyphWidgetShowsOrWillShow) {191return;192}193if (_sticky) {194return;195}196this.hideGlyphHover();197}198199private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean {200const glyphWidget: IHoverWidget = this._getOrCreateGlyphWidget();201return glyphWidget.showsOrWillShow(mouseEvent);202}203204private _onKeyDown(e: IKeyboardEvent): void {205if (!this._editor.hasModel()) {206return;207}208if (isModifierKey(e.keyCode)) {209// Do not hide hover when a modifier key is pressed210return;211}212this.hideGlyphHover();213}214215public hideGlyphHover(): void {216if (_sticky) {217return;218}219this._glyphWidget?.hide();220}221222private _getOrCreateGlyphWidget(): GlyphHoverWidget {223if (!this._glyphWidget) {224this._glyphWidget = this._instantiationService.createInstance(GlyphHoverWidget, this._editor);225}226return this._glyphWidget;227}228229public override dispose(): void {230super.dispose();231this._unhookListeners();232this._listenersStore.dispose();233this._glyphWidget?.dispose();234}235}236237238