From 709db7827fe89642ecc02c596f7831ce3887d931 Mon Sep 17 00:00:00 2001 From: Connor Peet Date: Wed, 10 Jan 2024 12:28:03 -0800 Subject: [PATCH] editor: include lane in GUTTER_GLYPH_MARGIN hover events Also fixes handling of testing clicks and margin hovers which were not aware of multiple lanes. Hover messages are now shown correctly and tests no longer run if e.g. a breakpoint decoration on the same line is clicked. Note sure if this was the best way to do this, let me know. --- .../editor/browser/controller/mouseTarget.ts | 6 ++- src/vs/editor/browser/editorBrowser.ts | 1 + src/vs/editor/browser/view.ts | 48 +++++++++---------- .../viewParts/glyphMargin/glyphMargin.ts | 13 ++--- src/vs/editor/common/model.ts | 12 +++++ src/vs/editor/common/viewModel.ts | 4 +- .../viewModel}/glyphLanesModel.ts | 13 ++++- .../editor/common/viewModel/viewModelImpl.ts | 5 +- src/vs/editor/contrib/hover/browser/hover.ts | 4 +- .../contrib/hover/browser/marginHover.ts | 20 +++++++- .../viewModel}/glyphLanesModel.test.ts | 2 +- src/vs/monaco.d.ts | 11 +++++ .../testing/browser/testingDecorations.ts | 5 +- 13 files changed, 98 insertions(+), 46 deletions(-) rename src/vs/editor/{browser/viewParts/glyphMargin => common/viewModel}/glyphLanesModel.ts (87%) rename src/vs/editor/test/{browser/view => common/viewModel}/glyphLanesModel.test.ts (96%) diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index b6bb7881850a2..8598d9140b96b 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -20,6 +20,7 @@ import * as dom from 'vs/base/browser/dom'; import { AtomicTabMoveOperations, Direction } from 'vs/editor/common/cursor/cursorAtomicMoveOperations'; import { PositionAffinity } from 'vs/editor/common/model'; import { InjectedText } from 'vs/editor/common/modelLineProjectionData'; +import { Mutable } from 'vs/base/common/types'; const enum HitTestResultType { Unknown, @@ -672,7 +673,7 @@ export class MouseTargetFactory { const res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset); const pos = res.range.getStartPosition(); let offset = Math.abs(request.relativePos.x); - const detail: IMouseTargetMarginData = { + const detail: Mutable = { isAfterLines: res.isAfterLines, glyphMarginLeft: ctx.layoutInfo.glyphMarginLeft, glyphMarginWidth: ctx.layoutInfo.glyphMarginWidth, @@ -684,6 +685,9 @@ export class MouseTargetFactory { if (offset <= ctx.layoutInfo.glyphMarginWidth) { // On the glyph margin + const modelCoordinate = ctx.viewModel.coordinatesConverter.convertViewPositionToModelPosition(res.range.getStartPosition()); + const lanes = ctx.viewModel.glyphLanes.getLanesAtLine(modelCoordinate.lineNumber); + detail.glyphMarginLane = lanes[Math.floor(offset / ctx.lineHeight)]; return request.fulfillMargin(MouseTargetType.GUTTER_GLYPH_MARGIN, pos, res.range, detail); } offset -= ctx.layoutInfo.glyphMarginWidth; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 0b8fc09755a33..9fe0301b8ce1c 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -405,6 +405,7 @@ export interface IMouseTargetMarginData { readonly isAfterLines: boolean; readonly glyphMarginLeft: number; readonly glyphMarginWidth: number; + readonly glyphMarginLane?: GlyphMarginLane; readonly lineNumbersWidth: number; readonly offsetX: number; } diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index c8dc8cefa2766..ba0b500876097 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -4,23 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { Selection } from 'vs/editor/common/core/selection'; -import { Range } from 'vs/editor/common/core/range'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; +import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; +import { inputLatency } from 'vs/base/browser/performance'; +import { CodeWindow } from 'vs/base/browser/window'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; +import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; import { IVisibleRangeProvider, TextAreaHandler } from 'vs/editor/browser/controller/textAreaHandler'; -import { IContentWidget, IContentWidgetPosition, IOverlayWidget, IOverlayWidgetPosition, IMouseTarget, IViewZoneChangeAccessor, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browser/editorBrowser'; +import { IContentWidget, IContentWidgetPosition, IEditorAriaOptions, IGlyphMarginWidget, IGlyphMarginWidgetPosition, IMouseTarget, IOverlayWidget, IOverlayWidgetPosition, IViewZoneChangeAccessor } from 'vs/editor/browser/editorBrowser'; +import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ICommandDelegate, ViewController } from 'vs/editor/browser/view/viewController'; -import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; import { ContentViewOverlays, MarginViewOverlays } from 'vs/editor/browser/view/viewOverlays'; import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; +import { ViewUserInputEvents } from 'vs/editor/browser/view/viewUserInputEvents'; +import { BlockDecorations } from 'vs/editor/browser/viewParts/blockDecorations/blockDecorations'; import { ViewContentWidgets } from 'vs/editor/browser/viewParts/contentWidgets/contentWidgets'; import { CurrentLineHighlightOverlay, CurrentLineMarginHighlightOverlay } from 'vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight'; import { DecorationsOverlay } from 'vs/editor/browser/viewParts/decorations/decorations'; import { EditorScrollbar } from 'vs/editor/browser/viewParts/editorScrollbar/editorScrollbar'; +import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; import { IndentGuidesOverlay } from 'vs/editor/browser/viewParts/indentGuides/indentGuides'; import { LineNumbersOverlay } from 'vs/editor/browser/viewParts/lineNumbers/lineNumbers'; import { ViewLines } from 'vs/editor/browser/viewParts/lines/viewLines'; @@ -36,27 +41,21 @@ import { ScrollDecorationViewPart } from 'vs/editor/browser/viewParts/scrollDeco import { SelectionsOverlay } from 'vs/editor/browser/viewParts/selections/selections'; import { ViewCursors } from 'vs/editor/browser/viewParts/viewCursors/viewCursors'; import { ViewZones } from 'vs/editor/browser/viewParts/viewZones/viewZones'; +import { WhitespaceOverlay } from 'vs/editor/browser/viewParts/whitespace/whitespace'; +import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; +import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; -import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; +import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; +import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; -import { ViewEventHandler } from 'vs/editor/common/viewEventHandler'; import { IViewModel } from 'vs/editor/common/viewModel'; -import { getThemeTypeSelector, IColorTheme } from 'vs/platform/theme/common/themeService'; -import { EditorOption } from 'vs/editor/common/config/editorOptions'; -import { PointerHandlerLastRenderData } from 'vs/editor/browser/controller/mouseTarget'; -import { BlockDecorations } from 'vs/editor/browser/viewParts/blockDecorations/blockDecorations'; -import { inputLatency } from 'vs/base/browser/performance'; -import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; -import { WhitespaceOverlay } from 'vs/editor/browser/viewParts/whitespace/whitespace'; -import { GlyphMarginWidgets } from 'vs/editor/browser/viewParts/glyphMargin/glyphMargin'; -import { GlyphMarginLane } from 'vs/editor/common/model'; +import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { CodeWindow } from 'vs/base/browser/window'; -import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; +import { IColorTheme, getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; export interface IContentWidgetData { @@ -244,8 +243,9 @@ export class View extends ViewEventHandler { this._pointerHandler = this._register(new PointerHandler(this._context, viewController, this._createPointerHandlerHelper())); } - private _computeGlyphMarginLanes(): GlyphMarginLanesModel { + private _computeGlyphMarginLanes(): IGlyphMarginLanesModel { const model = this._context.viewModel.model; + const laneModel = this._context.viewModel.glyphLanes; type Glyph = { range: Range; lane: GlyphMarginLane; persist?: boolean }; let glyphs: Glyph[] = []; let maxLineNumber = 0; @@ -267,14 +267,12 @@ export class View extends ViewEventHandler { // Sorted by their start position glyphs.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); - const lanes = new GlyphMarginLanesModel(maxLineNumber); + laneModel.reset(maxLineNumber); for (const glyph of glyphs) { - lanes.push(glyph.lane, glyph.range, glyph.persist); + laneModel.push(glyph.lane, glyph.range, glyph.persist); } - this._glyphMarginWidgets.updateLanesModel(lanes); - - return lanes; + return laneModel; } private _createPointerHandlerHelper(): IPointerHandlerHelper { diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 5625e837871b5..48c79a783ead7 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -10,11 +10,10 @@ import { IGlyphMarginWidget, IGlyphMarginWidgetPosition } from 'vs/editor/browse import { DynamicViewOverlay } from 'vs/editor/browser/view/dynamicViewOverlay'; import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; -import { GlyphMarginLanesModel } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model'; +import { GlyphMarginLane } from 'vs/editor/common/model'; import * as viewEvents from 'vs/editor/common/viewEvents'; import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; @@ -124,7 +123,6 @@ export class GlyphMarginWidgets extends ViewPart { public domNode: FastDomNode; - private _lanesModel: IGlyphMarginLanesModel; private _lineHeight: number; private _glyphMargin: boolean; private _glyphMarginLeft: number; @@ -139,7 +137,6 @@ export class GlyphMarginWidgets extends ViewPart { constructor(context: ViewContext) { super(context); this._context = context; - this._lanesModel = new GlyphMarginLanesModel(0); const options = this._context.configuration.options; const layoutInfo = options.get(EditorOption.layoutInfo); @@ -251,10 +248,6 @@ export class GlyphMarginWidgets extends ViewPart { } } - public updateLanesModel(model: GlyphMarginLanesModel) { - this._lanesModel = model; - } - // --- end widget management private _collectDecorationBasedGlyphRenderRequest(ctx: RenderingContext, requests: GlyphRenderRequest[]): void { @@ -275,7 +268,7 @@ export class GlyphMarginWidgets extends ViewPart { for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber, 0)); - const laneIndex = this._lanesModel.getLanesAtLine(modelPosition.lineNumber).indexOf(lane); + const laneIndex = this._context.viewModel.glyphLanes.getLanesAtLine(modelPosition.lineNumber).indexOf(lane); requests.push(new DecorationBasedGlyphRenderRequest(lineNumber, laneIndex, zIndex, glyphMarginClassName)); } } @@ -296,7 +289,7 @@ export class GlyphMarginWidgets extends ViewPart { // The widget is in the viewport, find a good line for it const widgetLineNumber = Math.max(startLineNumber, visibleStartLineNumber); const modelPosition = this._context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(new Position(widgetLineNumber, 0)); - const laneIndex = this._lanesModel.getLanesAtLine(modelPosition.lineNumber).indexOf(widget.preference.lane); + const laneIndex = this._context.viewModel.glyphLanes.getLanesAtLine(modelPosition.lineNumber).indexOf(widget.preference.lane); requests.push(new WidgetBasedGlyphRenderRequest(widgetLineNumber, laneIndex, widget.preference.zIndex, widget)); } } diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index dabe64051a320..3d84860632f6c 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -53,6 +53,18 @@ export interface IGlyphMarginLanesModel { * Gets the lanes that should be rendered starting at a given line number. */ getLanesAtLine(lineNumber: number): GlyphMarginLane[]; + + /** + * Resets the model and ensures it can contain at least `maxLine` lines. + */ + reset(maxLine: number): void; + + /** + * Registers that a lane should be visible at the Range in the model. + * @param persist - if true, notes that the lane should always be visible, + * even on lines where there's no specific request for that lane. + */ + push(lane: GlyphMarginLane, range: Range, persist?: boolean): void; } /** diff --git a/src/vs/editor/common/viewModel.ts b/src/vs/editor/common/viewModel.ts index 3c3f6af54adc6..adf6cd307c6e8 100644 --- a/src/vs/editor/common/viewModel.ts +++ b/src/vs/editor/common/viewModel.ts @@ -12,7 +12,7 @@ import { CursorConfiguration, CursorState, EditOperationType, IColumnSelectData, import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; import { INewScrollPosition, ScrollType } from 'vs/editor/common/editorCommon'; import { EditorTheme } from 'vs/editor/common/editorTheme'; -import { EndOfLinePreference, IModelDecorationOptions, ITextModel, PositionAffinity } from 'vs/editor/common/model'; +import { EndOfLinePreference, IGlyphMarginLanesModel, IModelDecorationOptions, ITextModel, PositionAffinity } from 'vs/editor/common/model'; import { ILineBreaksComputer, InjectedText } from 'vs/editor/common/modelLineProjectionData'; import { BracketGuideOptions, IActiveIndentGuideInfo, IndentGuide } from 'vs/editor/common/textModelGuides'; import { IViewLineTokens } from 'vs/editor/common/tokens/lineTokens'; @@ -29,6 +29,8 @@ export interface IViewModel extends ICursorSimpleModel { readonly cursorConfig: CursorConfiguration; + readonly glyphLanes: IGlyphMarginLanesModel; + addViewEventHandler(eventHandler: ViewEventHandler): void; removeViewEventHandler(eventHandler: ViewEventHandler): void; diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts b/src/vs/editor/common/viewModel/glyphLanesModel.ts similarity index 87% rename from src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts rename to src/vs/editor/common/viewModel/glyphLanesModel.ts index 177391a85b842..c63c9b24f8d11 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphLanesModel.ts +++ b/src/vs/editor/common/viewModel/glyphLanesModel.ts @@ -10,7 +10,7 @@ import { GlyphMarginLane, IGlyphMarginLanesModel } from 'vs/editor/common/model' const MAX_LANE = GlyphMarginLane.Right; export class GlyphMarginLanesModel implements IGlyphMarginLanesModel { - private readonly lanes: Uint8Array; + private lanes: Uint8Array; private persist = 0; private _requiredLanes = 1; // always render at least one lane @@ -18,11 +18,20 @@ export class GlyphMarginLanesModel implements IGlyphMarginLanesModel { this.lanes = new Uint8Array(Math.ceil(((maxLine + 1) * MAX_LANE) / 8)); } + public reset(maxLine: number) { + const bytes = Math.ceil(((maxLine + 1) * MAX_LANE) / 8); + if (this.lanes.length < bytes) { + this.lanes = new Uint8Array(bytes); + } else { + this.lanes.fill(0); + } + this._requiredLanes = 1; + } + public get requiredLanes() { return this._requiredLanes; } - /** Adds a new range to the model. Assumes ranges are added in ascending order of line number. */ public push(lane: GlyphMarginLane, range: Range, persist?: boolean): void { if (persist) { this.persist |= (1 << (lane - 1)); diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ff5f4eef6f635..bf152209d83bd 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -19,7 +19,7 @@ import { Range } from 'vs/editor/common/core/range'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; import { ICommand, ICursorState, IViewState, ScrollType } from 'vs/editor/common/editorCommon'; import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration'; -import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, IAttachedView, ICursorStateComputer, IGlyphMarginLanesModel, IIdentifiedSingleEditOperation, ITextModel, PositionAffinity, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IActiveIndentGuideInfo, BracketGuideOptions, IndentGuide } from 'vs/editor/common/textModelGuides'; import { ModelDecorationMinimapOptions, ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/textModelEvents'; @@ -39,6 +39,7 @@ import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecora import { FocusChangedEvent, HiddenAreasChangedEvent, ModelContentChangedEvent, ModelDecorationsChangedEvent, ModelLanguageChangedEvent, ModelLanguageConfigurationChangedEvent, ModelOptionsChangedEvent, ModelTokensChangedEvent, OutgoingViewModelEvent, ReadOnlyEditAttemptEvent, ScrollChangedEvent, ViewModelEventDispatcher, ViewModelEventsCollector, ViewZonesChangedEvent } from 'vs/editor/common/viewModelEventDispatcher'; import { IViewModelLines, ViewModelLinesFromModelAsIs, ViewModelLinesFromProjectedModel } from 'vs/editor/common/viewModel/viewModelLines'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { GlyphMarginLanesModel } from 'vs/editor/common/viewModel/glyphLanesModel'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -58,6 +59,7 @@ export class ViewModel extends Disposable implements IViewModel { public readonly viewLayout: ViewLayout; private readonly _cursor: CursorsController; private readonly _decorations: ViewModelDecorations; + public readonly glyphLanes: IGlyphMarginLanesModel; constructor( editorId: number, @@ -81,6 +83,7 @@ export class ViewModel extends Disposable implements IViewModel { this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0)); this._hasFocus = false; this._viewportStart = ViewportStart.create(this.model); + this.glyphLanes = new GlyphMarginLanesModel(0); if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) { diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index 06964b7da094a..08b2a7bb68d2c 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -294,10 +294,10 @@ export class HoverController extends Disposable implements IEditorContribution { return; } - if (target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && target.position) { + if (target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && target.position && target.detail.glyphMarginLane) { this._contentWidget?.hide(); const glyphWidget = this._getOrCreateGlyphWidget(); - glyphWidget.startShowingAt(target.position.lineNumber); + glyphWidget.startShowingAt(target.position.lineNumber, target.detail.glyphMarginLane); return; } if (_sticky) { diff --git a/src/vs/editor/contrib/hover/browser/marginHover.ts b/src/vs/editor/contrib/hover/browser/marginHover.ts index 803fc30fd39b4..98af0a06cdac7 100644 --- a/src/vs/editor/contrib/hover/browser/marginHover.ts +++ b/src/vs/editor/contrib/hover/browser/marginHover.ts @@ -14,6 +14,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/browser/hoverOperation'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { HoverWidget } from 'vs/base/browser/ui/hover/hoverWidget'; +import { GlyphMarginLane } from 'vs/editor/common/model'; const $ = dom.$; @@ -98,8 +99,8 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { } } - public startShowingAt(lineNumber: number): void { - if (this._computer.lineNumber === lineNumber) { + public startShowingAt(lineNumber: number, lane: GlyphMarginLane): void { + if (this._computer.lineNumber === lineNumber && this._computer.lane === lane) { // We have to show the widget at the exact same line number as before, so no work is needed return; } @@ -109,6 +110,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { this.hide(); this._computer.lineNumber = lineNumber; + this._computer.lane = lane; this._hoverOperation.start(HoverStartMode.Delayed); } @@ -176,6 +178,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget { class MarginHoverComputer implements IHoverComputer { private _lineNumber: number = -1; + private _lane = GlyphMarginLane.Center; public get lineNumber(): number { return this._lineNumber; @@ -185,6 +188,14 @@ class MarginHoverComputer implements IHoverComputer { this._lineNumber = value; } + public get lane(): number { + return this._lane; + } + + public set lane(value: GlyphMarginLane) { + this._lane = value; + } + constructor( private readonly _editor: ICodeEditor ) { @@ -210,6 +221,11 @@ class MarginHoverComputer implements IHoverComputer { continue; } + const lane = d.options.glyphMargin?.position ?? GlyphMarginLane.Center; + if (lane !== this._lane) { + continue; + } + const hoverMessage = d.options.glyphMarginHoverMessage; if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) { continue; diff --git a/src/vs/editor/test/browser/view/glyphLanesModel.test.ts b/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts similarity index 96% rename from src/vs/editor/test/browser/view/glyphLanesModel.test.ts rename to src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts index 1055c5f1a5f2f..7c0522a84f0d5 100644 --- a/src/vs/editor/test/browser/view/glyphLanesModel.test.ts +++ b/src/vs/editor/test/common/viewModel/glyphLanesModel.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; -import { GlyphMarginLanesModel, } from 'vs/editor/browser/viewParts/glyphMargin/glyphLanesModel'; +import { GlyphMarginLanesModel, } from 'vs/editor/common/viewModel/glyphLanesModel'; import { Range } from 'vs/editor/common/core/range'; import { GlyphMarginLane } from 'vs/editor/common/model'; diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index ef0d28b496eef..ac4a31bd48eaa 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1593,6 +1593,16 @@ declare namespace monaco.editor { * Gets the lanes that should be rendered starting at a given line number. */ getLanesAtLine(lineNumber: number): GlyphMarginLane[]; + /** + * Resets the model and ensures it can contain at least `maxLine` lines. + */ + reset(maxLine: number): void; + /** + * Registers that a lane should be visible at the Range in the model. + * @param persist - if true, notes that the lane should always be visible, + * even on lines where there's no specific request for that lane. + */ + push(lane: GlyphMarginLane, range: Range, persist?: boolean): void; } /** @@ -5464,6 +5474,7 @@ declare namespace monaco.editor { readonly isAfterLines: boolean; readonly glyphMarginLeft: number; readonly glyphMarginWidth: number; + readonly glyphMarginLane?: GlyphMarginLane; readonly lineNumbersWidth: number; readonly offsetX: number; } diff --git a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts index a062c3d79d26f..6083c9af89665 100644 --- a/src/vs/workbench/contrib/testing/browser/testingDecorations.ts +++ b/src/vs/workbench/contrib/testing/browser/testingDecorations.ts @@ -25,7 +25,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { overviewRulerError, overviewRulerInfo } from 'vs/editor/common/core/editorColorRegistry'; import { IRange } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { GlyphMarginLane, IModelDeltaDecoration, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/model'; import { localize } from 'vs/nls'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; @@ -56,6 +56,7 @@ import { TestUriType, buildTestUri, parseTestUri } from 'vs/workbench/contrib/te const MAX_INLINE_MESSAGE_LENGTH = 128; const MAX_TESTS_IN_SUBMENU = 30; +const GLYPH_MARGIN_LANE = GlyphMarginLane.Center; function isOriginalInDiffEditor(codeEditorService: ICodeEditorService, codeEditor: ICodeEditor): boolean { const diffEditors = codeEditorService.listDiffEditors(); @@ -586,6 +587,7 @@ const createRunTestDecoration = (tests: readonly IncrementalTestCollectionItem[] return hoverMessage; }, + glyphMargin: { position: GLYPH_MARGIN_LANE }, glyphMarginClassName, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, zIndex: 10000, @@ -741,6 +743,7 @@ abstract class RunTestDecoration { /** @inheritdoc */ public click(e: IEditorMouseEvent): boolean { if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN + || e.target.detail.glyphMarginLane !== GLYPH_MARGIN_LANE // handled by editor gutter context menu || e.event.rightButton || isMacintosh && e.event.leftButton && e.event.ctrlKey