Skip to content

Commit 38b3b31

Browse files
authored
Don't dismiss hover by mouse movement if it is triggered from the keyboard (microsoft#164446)
Fixes microsoft#156729: Don't dismiss hover by mouse movement if it is triggered from the keyboard
1 parent f3b1ddb commit 38b3b31

File tree

5 files changed

+47
-20
lines changed

5 files changed

+47
-20
lines changed

src/vs/editor/contrib/colorPicker/browser/colorContributions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon';
1111
import { ColorDecorationInjectedTextMarker } from 'vs/editor/contrib/colorPicker/browser/colorDetector';
1212
import { ColorHoverParticipant } from 'vs/editor/contrib/colorPicker/browser/colorHoverParticipant';
1313
import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover';
14-
import { HoverStartMode } from 'vs/editor/contrib/hover/browser/hoverOperation';
14+
import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
1515
import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes';
1616

1717
export class ColorContribution extends Disposable implements IEditorContribution {
@@ -55,7 +55,7 @@ export class ColorContribution extends Disposable implements IEditorContribution
5555
}
5656
if (!hoverController.isColorPickerVisible()) {
5757
const range = new Range(target.range.startLineNumber, target.range.startColumn + 1, target.range.endLineNumber, target.range.endColumn + 1);
58-
hoverController.showContentHover(range, HoverStartMode.Immediate, false);
58+
hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false);
5959
}
6060
}
6161
}

src/vs/editor/contrib/hover/browser/contentHover.ts

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import { Range } from 'vs/editor/common/core/range';
1616
import { IModelDecoration, PositionAffinity } from 'vs/editor/common/model';
1717
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
1818
import { TokenizationRegistry } from 'vs/editor/common/languages';
19-
import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation';
19+
import { HoverOperation, HoverStartMode, HoverStartSource, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation';
2020
import { HoverAnchor, HoverAnchorType, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverColorPickerWidget, IEditorHoverAction, IEditorHoverParticipant, IEditorHoverRenderContext, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes';
2121
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
2222
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -104,25 +104,25 @@ export class ContentHoverController extends Disposable {
104104
}
105105

106106
if (anchorCandidates.length === 0) {
107-
return this._startShowingOrUpdateHover(null, HoverStartMode.Delayed, false, mouseEvent);
107+
return this._startShowingOrUpdateHover(null, HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent);
108108
}
109109

110110
anchorCandidates.sort((a, b) => b.priority - a.priority);
111-
return this._startShowingOrUpdateHover(anchorCandidates[0], HoverStartMode.Delayed, false, mouseEvent);
111+
return this._startShowingOrUpdateHover(anchorCandidates[0], HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent);
112112
}
113113

114-
public startShowingAtRange(range: Range, mode: HoverStartMode, focus: boolean): void {
115-
this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, focus, null);
114+
public startShowingAtRange(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void {
115+
this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null);
116116
}
117117

118118
/**
119119
* Returns true if the hover shows now or will show.
120120
*/
121-
private _startShowingOrUpdateHover(anchor: HoverAnchor | null, mode: HoverStartMode, focus: boolean, mouseEvent: IEditorMouseEvent | null): boolean {
121+
private _startShowingOrUpdateHover(anchor: HoverAnchor | null, mode: HoverStartMode, source: HoverStartSource, focus: boolean, mouseEvent: IEditorMouseEvent | null): boolean {
122122
if (!this._widget.position || !this._currentResult) {
123123
// The hover is not visible
124124
if (anchor) {
125-
this._startHoverOperationIfNecessary(anchor, mode, focus, false);
125+
this._startHoverOperationIfNecessary(anchor, mode, source, focus, false);
126126
return true;
127127
}
128128
return false;
@@ -135,7 +135,7 @@ export class ContentHoverController extends Disposable {
135135
// The mouse is getting closer to the hover, so we will keep the hover untouched
136136
// But we will kick off a hover update at the new anchor, insisting on keeping the hover visible.
137137
if (anchor) {
138-
this._startHoverOperationIfNecessary(anchor, mode, focus, true);
138+
this._startHoverOperationIfNecessary(anchor, mode, source, focus, true);
139139
}
140140
return true;
141141
}
@@ -153,18 +153,18 @@ export class ContentHoverController extends Disposable {
153153
if (!anchor.canAdoptVisibleHover(this._currentResult.anchor, this._widget.position)) {
154154
// The new anchor is not compatible with the previous anchor
155155
this._setCurrentResult(null);
156-
this._startHoverOperationIfNecessary(anchor, mode, focus, false);
156+
this._startHoverOperationIfNecessary(anchor, mode, source, focus, false);
157157
return true;
158158
}
159159

160160
// We aren't getting any closer to the hover, so we will filter existing results
161161
// and keep those which also apply to the new anchor.
162162
this._setCurrentResult(this._currentResult.filter(anchor));
163-
this._startHoverOperationIfNecessary(anchor, mode, focus, false);
163+
this._startHoverOperationIfNecessary(anchor, mode, source, focus, false);
164164
return true;
165165
}
166166

167-
private _startHoverOperationIfNecessary(anchor: HoverAnchor, mode: HoverStartMode, focus: boolean, insistOnKeepingHoverVisible: boolean): void {
167+
private _startHoverOperationIfNecessary(anchor: HoverAnchor, mode: HoverStartMode, source: HoverStartSource, focus: boolean, insistOnKeepingHoverVisible: boolean): void {
168168
if (this._computer.anchor && this._computer.anchor.equals(anchor)) {
169169
// We have to start a hover operation at the exact same anchor as before, so no work is needed
170170
return;
@@ -173,6 +173,7 @@ export class ContentHoverController extends Disposable {
173173
this._hoverOperation.cancel();
174174
this._computer.anchor = anchor;
175175
this._computer.shouldFocus = focus;
176+
this._computer.source = source;
176177
this._computer.insistOnKeepingHoverVisible = insistOnKeepingHoverVisible;
177178
this._hoverOperation.start(mode);
178179
}
@@ -203,6 +204,10 @@ export class ContentHoverController extends Disposable {
203204
return this._widget.isColorPickerVisible;
204205
}
205206

207+
public isVisibleFromKeyboard(): boolean {
208+
return this._widget.isVisibleFromKeyboard;
209+
}
210+
206211
public containsNode(node: Node): boolean {
207212
return this._widget.getDomNode().contains(node);
208213
}
@@ -286,6 +291,7 @@ export class ContentHoverController extends Disposable {
286291
showAtSecondaryPosition,
287292
this._editor.getOption(EditorOption.hover).above,
288293
this._computer.shouldFocus,
294+
this._computer.source,
289295
isBeforeContent,
290296
anchor.initialMousePosX,
291297
anchor.initialMousePosY,
@@ -379,6 +385,7 @@ class ContentHoverVisibleData {
379385
public readonly showAtSecondaryPosition: Position,
380386
public readonly preferAbove: boolean,
381387
public readonly stoleFocus: boolean,
388+
public readonly source: HoverStartSource,
382389
public readonly isBeforeContent: boolean,
383390
public initialMousePosX: number | undefined,
384391
public initialMousePosY: number | undefined,
@@ -408,6 +415,10 @@ export class ContentHoverWidget extends Disposable implements IContentWidget {
408415
return Boolean(this._visibleData?.colorPicker);
409416
}
410417

418+
public get isVisibleFromKeyboard(): boolean {
419+
return (this._visibleData?.source === HoverStartSource.Keyboard);
420+
}
421+
411422
constructor(
412423
private readonly _editor: ICodeEditor,
413424
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@@ -613,6 +624,10 @@ class ContentHoverComputer implements IHoverComputer<IHoverPart> {
613624
public get shouldFocus(): boolean { return this._shouldFocus; }
614625
public set shouldFocus(value: boolean) { this._shouldFocus = value; }
615626

627+
private _source: HoverStartSource = HoverStartSource.Mouse;
628+
public get source(): HoverStartSource { return this._source; }
629+
public set source(value: HoverStartSource) { this._source = value; }
630+
616631
private _insistOnKeepingHoverVisible: boolean = false;
617632
public get insistOnKeepingHoverVisible(): boolean { return this._insistOnKeepingHoverVisible; }
618633
public set insistOnKeepingHoverVisible(value: boolean) { this._insistOnKeepingHoverVisible = value; }

src/vs/editor/contrib/hover/browser/hover.ts

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { IEditorContribution, IScrollEvent } from 'vs/editor/common/editorCommon
1414
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
1515
import { ILanguageService } from 'vs/editor/common/languages/language';
1616
import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition';
17-
import { HoverStartMode } from 'vs/editor/contrib/hover/browser/hoverOperation';
17+
import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
1818
import { ContentHoverWidget, ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHover';
1919
import { MarginHoverWidget } from 'vs/editor/contrib/hover/browser/marginHover';
2020
import * as nls from 'vs/nls';
@@ -172,6 +172,13 @@ export class ModesHoverController implements IEditorContribution {
172172
}
173173

174174
const contentWidget = this._getOrCreateContentWidget();
175+
176+
if (this._isHoverSticky && contentWidget.isVisibleFromKeyboard()) {
177+
// Sticky mode is on and the hover has been shown via keyboard
178+
// so moving the mouse has no effect
179+
return;
180+
}
181+
175182
if (contentWidget.maybeShowAt(mouseEvent)) {
176183
this._glyphWidget?.hide();
177184
return;
@@ -217,8 +224,8 @@ export class ModesHoverController implements IEditorContribution {
217224
return this._contentWidget?.isColorPickerVisible() || false;
218225
}
219226

220-
public showContentHover(range: Range, mode: HoverStartMode, focus: boolean): void {
221-
this._getOrCreateContentWidget().startShowingAtRange(range, mode, focus);
227+
public showContentHover(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void {
228+
this._getOrCreateContentWidget().startShowingAtRange(range, mode, source, focus);
222229
}
223230

224231
public dispose(): void {
@@ -263,7 +270,7 @@ class ShowHoverAction extends EditorAction {
263270
const position = editor.getPosition();
264271
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
265272
const focus = editor.getOption(EditorOption.accessibilitySupport) === AccessibilitySupport.Enabled;
266-
controller.showContentHover(range, HoverStartMode.Immediate, focus);
273+
controller.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Keyboard, focus);
267274
}
268275
}
269276

@@ -302,7 +309,7 @@ class ShowDefinitionPreviewHoverAction extends EditorAction {
302309
}
303310
const promise = goto.startFindDefinitionFromCursor(position);
304311
promise.then(() => {
305-
controller.showContentHover(range, HoverStartMode.Immediate, true);
312+
controller.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Keyboard, true);
306313
});
307314
}
308315
}

src/vs/editor/contrib/hover/browser/hoverOperation.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ export const enum HoverStartMode {
3535
Immediate = 1
3636
}
3737

38+
export const enum HoverStartSource {
39+
Mouse = 0,
40+
Keyboard = 1
41+
}
42+
3843
export class HoverResult<T> {
3944
constructor(
4045
public readonly value: T[],

src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
3636
import { EditOperation } from 'vs/editor/common/core/editOperation';
3737
import { basename } from 'vs/base/common/path';
3838
import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover';
39-
import { HoverStartMode } from 'vs/editor/contrib/hover/browser/hoverOperation';
39+
import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation';
4040
import { IHostService } from 'vs/workbench/services/host/browser/host';
4141
import { Event } from 'vs/base/common/event';
4242
import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity';
@@ -349,7 +349,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
349349
// If the debug hover was visible immediately show the editor hover for the alt transition to be smooth
350350
const hoverController = this.editor.getContribution<ModesHoverController>(ModesHoverController.ID);
351351
const range = new Range(this.hoverPosition.lineNumber, this.hoverPosition.column, this.hoverPosition.lineNumber, this.hoverPosition.column);
352-
hoverController?.showContentHover(range, HoverStartMode.Immediate, false);
352+
hoverController?.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false);
353353
}
354354

355355
const onKeyUp = new DomEmitter(document, 'keyup');

0 commit comments

Comments
 (0)