Skip to content

Commit

Permalink
editor: include lane in GUTTER_GLYPH_MARGIN hover events
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
connor4312 committed Jan 10, 2024
1 parent 24963b3 commit 709db78
Show file tree
Hide file tree
Showing 13 changed files with 98 additions and 46 deletions.
6 changes: 5 additions & 1 deletion src/vs/editor/browser/controller/mouseTarget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<IMouseTargetMarginData> = {
isAfterLines: res.isAfterLines,
glyphMarginLeft: ctx.layoutInfo.glyphMarginLeft,
glyphMarginWidth: ctx.layoutInfo.glyphMarginWidth,
Expand All @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/vs/editor/browser/editorBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
48 changes: 23 additions & 25 deletions src/vs/editor/browser/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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 {
Expand Down Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
13 changes: 3 additions & 10 deletions src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -124,7 +123,6 @@ export class GlyphMarginWidgets extends ViewPart {

public domNode: FastDomNode<HTMLElement>;

private _lanesModel: IGlyphMarginLanesModel;
private _lineHeight: number;
private _glyphMargin: boolean;
private _glyphMarginLeft: number;
Expand All @@ -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);
Expand Down Expand Up @@ -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 {
Expand All @@ -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));
}
}
Expand All @@ -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));
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

/**
Expand Down
4 changes: 3 additions & 1 deletion src/vs/editor/common/viewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -29,6 +29,8 @@ export interface IViewModel extends ICursorSimpleModel {

readonly cursorConfig: CursorConfiguration;

readonly glyphLanes: IGlyphMarginLanesModel;

addViewEventHandler(eventHandler: ViewEventHandler): void;
removeViewEventHandler(eventHandler: ViewEventHandler): void;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,28 @@ 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

constructor(maxLine: number) {
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));
Expand Down
5 changes: 4 additions & 1 deletion src/vs/editor/common/viewModel/viewModelImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;

Expand All @@ -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,
Expand All @@ -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()) {

Expand Down
4 changes: 2 additions & 2 deletions src/vs/editor/contrib/hover/browser/hover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
20 changes: 18 additions & 2 deletions src/vs/editor/contrib/hover/browser/marginHover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.$;

Expand Down Expand Up @@ -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;
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -176,6 +178,7 @@ export class MarginHoverWidget extends Disposable implements IOverlayWidget {
class MarginHoverComputer implements IHoverComputer<IHoverMessage> {

private _lineNumber: number = -1;
private _lane = GlyphMarginLane.Center;

public get lineNumber(): number {
return this._lineNumber;
Expand All @@ -185,6 +188,14 @@ class MarginHoverComputer implements IHoverComputer<IHoverMessage> {
this._lineNumber = value;
}

public get lane(): number {
return this._lane;
}

public set lane(value: GlyphMarginLane) {
this._lane = value;
}

constructor(
private readonly _editor: ICodeEditor
) {
Expand All @@ -210,6 +221,11 @@ class MarginHoverComputer implements IHoverComputer<IHoverMessage> {
continue;
}

const lane = d.options.glyphMargin?.position ?? GlyphMarginLane.Center;
if (lane !== this._lane) {
continue;
}

const hoverMessage = d.options.glyphMarginHoverMessage;
if (!hoverMessage || isEmptyMarkdownString(hoverMessage)) {
continue;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
Loading

0 comments on commit 709db78

Please sign in to comment.