diff --git a/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts b/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts index c6a4071877f11..585c705984ea2 100644 --- a/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts +++ b/src/vs/editor/contrib/colorPicker/browser/colorContributions.ts @@ -11,7 +11,7 @@ import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ColorDecorationInjectedTextMarker } from 'vs/editor/contrib/colorPicker/browser/colorDetector'; import { ColorHoverParticipant } from 'vs/editor/contrib/colorPicker/browser/colorHoverParticipant'; import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; -import { HoverStartMode } from 'vs/editor/contrib/hover/browser/hoverOperation'; +import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { HoverParticipantRegistry } from 'vs/editor/contrib/hover/browser/hoverTypes'; export class ColorContribution extends Disposable implements IEditorContribution { @@ -55,7 +55,7 @@ export class ColorContribution extends Disposable implements IEditorContribution } if (!hoverController.isColorPickerVisible()) { const range = new Range(target.range.startLineNumber, target.range.startColumn + 1, target.range.endLineNumber, target.range.endColumn + 1); - hoverController.showContentHover(range, HoverStartMode.Immediate, false); + hoverController.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false); } } } diff --git a/src/vs/editor/contrib/hover/browser/contentHover.ts b/src/vs/editor/contrib/hover/browser/contentHover.ts index 6929f34486cf2..a4834e5745841 100644 --- a/src/vs/editor/contrib/hover/browser/contentHover.ts +++ b/src/vs/editor/contrib/hover/browser/contentHover.ts @@ -16,7 +16,7 @@ import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration, PositionAffinity } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { TokenizationRegistry } from 'vs/editor/common/languages'; -import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; +import { HoverOperation, HoverStartMode, HoverStartSource, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { HoverAnchor, HoverAnchorType, HoverParticipantRegistry, HoverRangeAnchor, IEditorHoverColorPickerWidget, IEditorHoverAction, IEditorHoverParticipant, IEditorHoverRenderContext, IEditorHoverStatusBar, IHoverPart } from 'vs/editor/contrib/hover/browser/hoverTypes'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -104,25 +104,25 @@ export class ContentHoverController extends Disposable { } if (anchorCandidates.length === 0) { - return this._startShowingOrUpdateHover(null, HoverStartMode.Delayed, false, mouseEvent); + return this._startShowingOrUpdateHover(null, HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent); } anchorCandidates.sort((a, b) => b.priority - a.priority); - return this._startShowingOrUpdateHover(anchorCandidates[0], HoverStartMode.Delayed, false, mouseEvent); + return this._startShowingOrUpdateHover(anchorCandidates[0], HoverStartMode.Delayed, HoverStartSource.Mouse, false, mouseEvent); } - public startShowingAtRange(range: Range, mode: HoverStartMode, focus: boolean): void { - this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, focus, null); + public startShowingAtRange(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void { + this._startShowingOrUpdateHover(new HoverRangeAnchor(0, range, undefined, undefined), mode, source, focus, null); } /** * Returns true if the hover shows now or will show. */ - private _startShowingOrUpdateHover(anchor: HoverAnchor | null, mode: HoverStartMode, focus: boolean, mouseEvent: IEditorMouseEvent | null): boolean { + private _startShowingOrUpdateHover(anchor: HoverAnchor | null, mode: HoverStartMode, source: HoverStartSource, focus: boolean, mouseEvent: IEditorMouseEvent | null): boolean { if (!this._widget.position || !this._currentResult) { // The hover is not visible if (anchor) { - this._startHoverOperationIfNecessary(anchor, mode, focus, false); + this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); return true; } return false; @@ -135,7 +135,7 @@ export class ContentHoverController extends Disposable { // 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 (anchor) { - this._startHoverOperationIfNecessary(anchor, mode, focus, true); + this._startHoverOperationIfNecessary(anchor, mode, source, focus, true); } return true; } @@ -153,18 +153,18 @@ export class ContentHoverController extends Disposable { if (!anchor.canAdoptVisibleHover(this._currentResult.anchor, this._widget.position)) { // The new anchor is not compatible with the previous anchor this._setCurrentResult(null); - this._startHoverOperationIfNecessary(anchor, mode, focus, false); + 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, focus, false); + this._startHoverOperationIfNecessary(anchor, mode, source, focus, false); return true; } - private _startHoverOperationIfNecessary(anchor: HoverAnchor, mode: HoverStartMode, focus: boolean, insistOnKeepingHoverVisible: boolean): void { + private _startHoverOperationIfNecessary(anchor: HoverAnchor, mode: HoverStartMode, source: HoverStartSource, focus: boolean, insistOnKeepingHoverVisible: boolean): void { if (this._computer.anchor && this._computer.anchor.equals(anchor)) { // We have to start a hover operation at the exact same anchor as before, so no work is needed return; @@ -173,6 +173,7 @@ export class ContentHoverController extends Disposable { this._hoverOperation.cancel(); this._computer.anchor = anchor; this._computer.shouldFocus = focus; + this._computer.source = source; this._computer.insistOnKeepingHoverVisible = insistOnKeepingHoverVisible; this._hoverOperation.start(mode); } @@ -203,6 +204,10 @@ export class ContentHoverController extends Disposable { return this._widget.isColorPickerVisible; } + public isVisibleFromKeyboard(): boolean { + return this._widget.isVisibleFromKeyboard; + } + public containsNode(node: Node): boolean { return this._widget.getDomNode().contains(node); } @@ -286,6 +291,7 @@ export class ContentHoverController extends Disposable { showAtSecondaryPosition, this._editor.getOption(EditorOption.hover).above, this._computer.shouldFocus, + this._computer.source, isBeforeContent, anchor.initialMousePosX, anchor.initialMousePosY, @@ -379,6 +385,7 @@ class ContentHoverVisibleData { public readonly showAtSecondaryPosition: Position, public readonly preferAbove: boolean, public readonly stoleFocus: boolean, + public readonly source: HoverStartSource, public readonly isBeforeContent: boolean, public initialMousePosX: number | undefined, public initialMousePosY: number | undefined, @@ -408,6 +415,10 @@ export class ContentHoverWidget extends Disposable implements IContentWidget { return Boolean(this._visibleData?.colorPicker); } + public get isVisibleFromKeyboard(): boolean { + return (this._visibleData?.source === HoverStartSource.Keyboard); + } + constructor( private readonly _editor: ICodeEditor, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -613,6 +624,10 @@ class ContentHoverComputer implements IHoverComputer { public get shouldFocus(): boolean { return this._shouldFocus; } public set shouldFocus(value: boolean) { this._shouldFocus = value; } + private _source: HoverStartSource = HoverStartSource.Mouse; + public get source(): HoverStartSource { return this._source; } + public set source(value: HoverStartSource) { this._source = value; } + private _insistOnKeepingHoverVisible: boolean = false; public get insistOnKeepingHoverVisible(): boolean { return this._insistOnKeepingHoverVisible; } public set insistOnKeepingHoverVisible(value: boolean) { this._insistOnKeepingHoverVisible = value; } diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index adcbfcc35b615..ee21462d46dff 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -14,7 +14,7 @@ import { IEditorContribution, IScrollEvent } from 'vs/editor/common/editorCommon import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ILanguageService } from 'vs/editor/common/languages/language'; import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition'; -import { HoverStartMode } from 'vs/editor/contrib/hover/browser/hoverOperation'; +import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { ContentHoverWidget, ContentHoverController } from 'vs/editor/contrib/hover/browser/contentHover'; import { MarginHoverWidget } from 'vs/editor/contrib/hover/browser/marginHover'; import * as nls from 'vs/nls'; @@ -172,6 +172,13 @@ export class ModesHoverController implements IEditorContribution { } const contentWidget = this._getOrCreateContentWidget(); + + if (this._isHoverSticky && contentWidget.isVisibleFromKeyboard()) { + // Sticky mode is on and the hover has been shown via keyboard + // so moving the mouse has no effect + return; + } + if (contentWidget.maybeShowAt(mouseEvent)) { this._glyphWidget?.hide(); return; @@ -217,8 +224,8 @@ export class ModesHoverController implements IEditorContribution { return this._contentWidget?.isColorPickerVisible() || false; } - public showContentHover(range: Range, mode: HoverStartMode, focus: boolean): void { - this._getOrCreateContentWidget().startShowingAtRange(range, mode, focus); + public showContentHover(range: Range, mode: HoverStartMode, source: HoverStartSource, focus: boolean): void { + this._getOrCreateContentWidget().startShowingAtRange(range, mode, source, focus); } public dispose(): void { @@ -263,7 +270,7 @@ class ShowHoverAction extends EditorAction { const position = editor.getPosition(); const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); const focus = editor.getOption(EditorOption.accessibilitySupport) === AccessibilitySupport.Enabled; - controller.showContentHover(range, HoverStartMode.Immediate, focus); + controller.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Keyboard, focus); } } @@ -302,7 +309,7 @@ class ShowDefinitionPreviewHoverAction extends EditorAction { } const promise = goto.startFindDefinitionFromCursor(position); promise.then(() => { - controller.showContentHover(range, HoverStartMode.Immediate, true); + controller.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Keyboard, true); }); } } diff --git a/src/vs/editor/contrib/hover/browser/hoverOperation.ts b/src/vs/editor/contrib/hover/browser/hoverOperation.ts index d270dea670dd9..79c56ade663e3 100644 --- a/src/vs/editor/contrib/hover/browser/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/browser/hoverOperation.ts @@ -35,6 +35,11 @@ export const enum HoverStartMode { Immediate = 1 } +export const enum HoverStartSource { + Mouse = 0, + Keyboard = 1 +} + export class HoverResult { constructor( public readonly value: T[], diff --git a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index 423a4d430e6d0..3ea12d46cd55b 100644 --- a/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -36,7 +36,7 @@ import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { basename } from 'vs/base/common/path'; import { ModesHoverController } from 'vs/editor/contrib/hover/browser/hover'; -import { HoverStartMode } from 'vs/editor/contrib/hover/browser/hoverOperation'; +import { HoverStartMode, HoverStartSource } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Event } from 'vs/base/common/event'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; @@ -349,7 +349,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { // If the debug hover was visible immediately show the editor hover for the alt transition to be smooth const hoverController = this.editor.getContribution(ModesHoverController.ID); const range = new Range(this.hoverPosition.lineNumber, this.hoverPosition.column, this.hoverPosition.lineNumber, this.hoverPosition.column); - hoverController?.showContentHover(range, HoverStartMode.Immediate, false); + hoverController?.showContentHover(range, HoverStartMode.Immediate, HoverStartSource.Mouse, false); } const onKeyUp = new DomEmitter(document, 'keyup');