Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/hover/browser/glyphHoverController.ts
4779 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { IKeyboardEvent } from '../../../../base/browser/keyboardEvent.js';
7
import { isModifierKey } from '../../../../base/common/keyCodes.js';
8
import { Disposable, DisposableStore } from '../../../../base/common/lifecycle.js';
9
import { ICodeEditor, IEditorMouseEvent, IPartialEditorMouseEvent } from '../../../browser/editorBrowser.js';
10
import { ConfigurationChangedEvent, EditorOption } from '../../../common/config/editorOptions.js';
11
import { IEditorContribution, IScrollEvent } from '../../../common/editorCommon.js';
12
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
13
import { IHoverWidget } from './hoverTypes.js';
14
import { RunOnceScheduler } from '../../../../base/common/async.js';
15
import { isMousePositionWithinElement, shouldShowHover } from './hoverUtils.js';
16
import './hover.css';
17
import { GlyphHoverWidget } from './glyphHoverWidget.js';
18
19
// sticky hover widget which doesn't disappear on focus out and such
20
const _sticky = false
21
// || Boolean("true") // done "weirdly" so that a lint warning prevents you from pushing this
22
;
23
24
interface IHoverSettings {
25
readonly enabled: 'on' | 'off' | 'onKeyboardModifier';
26
readonly sticky: boolean;
27
readonly hidingDelay: number;
28
}
29
30
interface IHoverState {
31
mouseDown: boolean;
32
}
33
34
export class GlyphHoverController extends Disposable implements IEditorContribution {
35
36
public static readonly ID = 'editor.contrib.marginHover';
37
38
public shouldKeepOpenOnEditorMouseMoveOrLeave: boolean = false;
39
40
private readonly _listenersStore = new DisposableStore();
41
42
private _glyphWidget: GlyphHoverWidget | undefined;
43
private _mouseMoveEvent: IEditorMouseEvent | undefined;
44
private _reactToEditorMouseMoveRunner: RunOnceScheduler;
45
46
private _hoverSettings!: IHoverSettings;
47
private _hoverState: IHoverState = {
48
mouseDown: false
49
};
50
51
constructor(
52
private readonly _editor: ICodeEditor,
53
@IInstantiationService private readonly _instantiationService: IInstantiationService
54
) {
55
super();
56
this._reactToEditorMouseMoveRunner = this._register(
57
new RunOnceScheduler(
58
() => this._reactToEditorMouseMove(this._mouseMoveEvent), 0
59
)
60
);
61
this._hookListeners();
62
this._register(this._editor.onDidChangeConfiguration((e: ConfigurationChangedEvent) => {
63
if (e.hasChanged(EditorOption.hover)) {
64
this._unhookListeners();
65
this._hookListeners();
66
}
67
}));
68
}
69
70
static get(editor: ICodeEditor): GlyphHoverController | null {
71
return editor.getContribution<GlyphHoverController>(GlyphHoverController.ID);
72
}
73
74
private _hookListeners(): void {
75
76
const hoverOpts = this._editor.getOption(EditorOption.hover);
77
this._hoverSettings = {
78
enabled: hoverOpts.enabled,
79
sticky: hoverOpts.sticky,
80
hidingDelay: hoverOpts.hidingDelay
81
};
82
83
if (hoverOpts.enabled !== 'off') {
84
this._listenersStore.add(this._editor.onMouseDown((e: IEditorMouseEvent) => this._onEditorMouseDown(e)));
85
this._listenersStore.add(this._editor.onMouseUp(() => this._onEditorMouseUp()));
86
this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e)));
87
this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e)));
88
} else {
89
this._listenersStore.add(this._editor.onMouseMove((e: IEditorMouseEvent) => this._onEditorMouseMove(e)));
90
this._listenersStore.add(this._editor.onKeyDown((e: IKeyboardEvent) => this._onKeyDown(e)));
91
}
92
93
this._listenersStore.add(this._editor.onMouseLeave((e) => this._onEditorMouseLeave(e)));
94
this._listenersStore.add(this._editor.onDidChangeModel(() => {
95
this._cancelScheduler();
96
this.hideGlyphHover();
97
}));
98
this._listenersStore.add(this._editor.onDidChangeModelContent(() => this._cancelScheduler()));
99
this._listenersStore.add(this._editor.onDidScrollChange((e: IScrollEvent) => this._onEditorScrollChanged(e)));
100
}
101
102
private _unhookListeners(): void {
103
this._listenersStore.clear();
104
}
105
106
private _cancelScheduler() {
107
this._mouseMoveEvent = undefined;
108
this._reactToEditorMouseMoveRunner.cancel();
109
}
110
111
private _onEditorScrollChanged(e: IScrollEvent): void {
112
if (e.scrollTopChanged || e.scrollLeftChanged) {
113
this.hideGlyphHover();
114
}
115
}
116
117
private _onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
118
this._hoverState.mouseDown = true;
119
const shouldNotHideCurrentHoverWidget = this._isMouseOnGlyphHoverWidget(mouseEvent);
120
if (shouldNotHideCurrentHoverWidget) {
121
return;
122
}
123
this.hideGlyphHover();
124
}
125
126
private _isMouseOnGlyphHoverWidget(mouseEvent: IPartialEditorMouseEvent): boolean {
127
const glyphHoverWidgetNode = this._glyphWidget?.getDomNode();
128
if (glyphHoverWidgetNode) {
129
return isMousePositionWithinElement(glyphHoverWidgetNode, mouseEvent.event.posx, mouseEvent.event.posy);
130
}
131
return false;
132
}
133
134
private _onEditorMouseUp(): void {
135
this._hoverState.mouseDown = false;
136
}
137
138
private _onEditorMouseLeave(mouseEvent: IPartialEditorMouseEvent): void {
139
if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) {
140
return;
141
}
142
143
this._cancelScheduler();
144
const shouldNotHideCurrentHoverWidget = this._isMouseOnGlyphHoverWidget(mouseEvent);
145
if (shouldNotHideCurrentHoverWidget) {
146
return;
147
}
148
if (_sticky) {
149
return;
150
}
151
this.hideGlyphHover();
152
}
153
154
private _shouldNotRecomputeCurrentHoverWidget(mouseEvent: IEditorMouseEvent): boolean {
155
const isHoverSticky = this._hoverSettings.sticky;
156
const isMouseOnGlyphHoverWidget = this._isMouseOnGlyphHoverWidget(mouseEvent);
157
return isHoverSticky && isMouseOnGlyphHoverWidget;
158
}
159
160
private _onEditorMouseMove(mouseEvent: IEditorMouseEvent): void {
161
if (this.shouldKeepOpenOnEditorMouseMoveOrLeave) {
162
return;
163
}
164
165
this._mouseMoveEvent = mouseEvent;
166
const shouldNotRecomputeCurrentHoverWidget = this._shouldNotRecomputeCurrentHoverWidget(mouseEvent);
167
if (shouldNotRecomputeCurrentHoverWidget) {
168
this._reactToEditorMouseMoveRunner.cancel();
169
return;
170
}
171
this._reactToEditorMouseMove(mouseEvent);
172
}
173
174
private _reactToEditorMouseMove(mouseEvent: IEditorMouseEvent | undefined): void {
175
176
if (!mouseEvent) {
177
return;
178
}
179
if (!shouldShowHover(
180
this._hoverSettings.enabled,
181
this._editor.getOption(EditorOption.multiCursorModifier),
182
mouseEvent
183
)) {
184
if (_sticky) {
185
return;
186
}
187
this.hideGlyphHover();
188
return;
189
}
190
const glyphWidgetShowsOrWillShow = this._tryShowHoverWidget(mouseEvent);
191
if (glyphWidgetShowsOrWillShow) {
192
return;
193
}
194
if (_sticky) {
195
return;
196
}
197
this.hideGlyphHover();
198
}
199
200
private _tryShowHoverWidget(mouseEvent: IEditorMouseEvent): boolean {
201
const glyphWidget: IHoverWidget = this._getOrCreateGlyphWidget();
202
return glyphWidget.showsOrWillShow(mouseEvent);
203
}
204
205
private _onKeyDown(e: IKeyboardEvent): void {
206
if (!this._editor.hasModel()) {
207
return;
208
}
209
if (isModifierKey(e.keyCode)) {
210
// Do not hide hover when a modifier key is pressed
211
return;
212
}
213
this.hideGlyphHover();
214
}
215
216
public hideGlyphHover(): void {
217
if (_sticky) {
218
return;
219
}
220
this._glyphWidget?.hide();
221
}
222
223
private _getOrCreateGlyphWidget(): GlyphHoverWidget {
224
if (!this._glyphWidget) {
225
this._glyphWidget = this._instantiationService.createInstance(GlyphHoverWidget, this._editor);
226
}
227
return this._glyphWidget;
228
}
229
230
public override dispose(): void {
231
super.dispose();
232
this._unhookListeners();
233
this._listenersStore.dispose();
234
this._glyphWidget?.dispose();
235
}
236
}
237
238