Skip to content

Commit

Permalink
Support dedicated lane for breakpoint decorations (#180013)
Browse files Browse the repository at this point in the history
* Add fast path for margin decoration access

* Support glyph margin decoration lanes

* Do not center extension-contributed decorations

* Show breakpoints in dedicated right lane

* Only recompute lane count if glyph margin was affected

* Add explicit type for className / zIndex pair and fix problems in other consumers of `DedupOverlay`

* Avoid arrays of arrays

* Figure out if two lanes are needed in O(N)

---------

Co-authored-by: Alex Dima <[email protected]>
  • Loading branch information
joyceerhl and alexdima authored Apr 19, 2023
1 parent 0724039 commit b5e90fd
Show file tree
Hide file tree
Showing 19 changed files with 264 additions and 45 deletions.
12 changes: 11 additions & 1 deletion src/vs/editor/browser/config/editorConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat
private _viewLineCount: number = 1;
private _lineNumbersDigitCount: number = 1;
private _reservedHeight: number = 0;
private _glyphMarginDecorationLaneCount: number = 1;

private readonly _computeOptionsMemory: ComputeOptionsMemory = new ComputeOptionsMemory();
/**
Expand Down Expand Up @@ -117,7 +118,8 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat
emptySelectionClipboard: partialEnv.emptySelectionClipboard,
pixelRatio: partialEnv.pixelRatio,
tabFocusMode: TabFocus.getTabFocusMode(TabFocusContext.Editor),
accessibilitySupport: partialEnv.accessibilitySupport
accessibilitySupport: partialEnv.accessibilitySupport,
glyphMarginDecorationLaneCount: this._glyphMarginDecorationLaneCount
};
return EditorOptionsUtil.computeOptions(this._validatedOptions, env);
}
Expand Down Expand Up @@ -193,6 +195,14 @@ export class EditorConfiguration extends Disposable implements IEditorConfigurat
this._reservedHeight = reservedHeight;
this._recomputeOptions();
}

public setGlyphMarginDecorationLaneCount(decorationLaneCount: number): void {
if (this._glyphMarginDecorationLaneCount === decorationLaneCount) {
return;
}
this._glyphMarginDecorationLaneCount = decorationLaneCount;
this._recomputeOptions();
}
}

function digitCount(n: number): number {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ export const _CSS_MAP: { [prop: string]: string } = {
cursor: 'cursor:{0};',
letterSpacing: 'letter-spacing:{0};',

gutterIconPath: 'background:{0} center center no-repeat;',
gutterIconPath: 'background:{0} no-repeat;',
gutterIconSize: 'background-size:{0};',

contentText: 'content:\'{0}\';',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,4 @@
position: absolute;
display: flex;
align-items: center;
justify-content: center;
}
91 changes: 71 additions & 20 deletions src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,60 @@ export class DecorationToRender {
public endLineNumber: number;
public className: string;
public readonly zIndex: number;
public readonly decorationLane: number;

constructor(startLineNumber: number, endLineNumber: number, className: string, zIndex?: number) {
constructor(startLineNumber: number, endLineNumber: number, className: string, zIndex?: number, decorationLane?: number) {
this.startLineNumber = +startLineNumber;
this.endLineNumber = +endLineNumber;
this.className = String(className);
this.zIndex = zIndex ?? 0;
this.decorationLane = decorationLane ?? 1;
}
}

export class RenderedDecoration {
constructor(
public readonly className: string,
public readonly zIndex: number,
) { }
}

export class LineRenderedDecorations {

private readonly lanes: RenderedDecoration[][] = [];

public add(lane: number, decoration: RenderedDecoration) {
while (lane >= this.lanes.length) {
this.lanes.push([]);
}
this.lanes[lane].push(decoration);
}

public getLaneDecorations(laneIndex: number): RenderedDecoration[] {
if (laneIndex < this.lanes.length) {
return this.lanes[laneIndex];
}
return [];
}

public isEmpty(): boolean {
for (const lane of this.lanes) {
if (lane.length > 0) {
return false;
}
}
return true;
}
}

export abstract class DedupOverlay extends DynamicViewOverlay {

protected _render(visibleStartLineNumber: number, visibleEndLineNumber: number, decorations: DecorationToRender[]): [string, number][][] {
protected _render(visibleStartLineNumber: number, visibleEndLineNumber: number, decorations: DecorationToRender[], decorationLaneCount: number): LineRenderedDecorations[] {

const output: [string, number][][] = [];
const output: LineRenderedDecorations[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
output[lineIndex] = [];
output[lineIndex] = new LineRenderedDecorations();
}

if (decorations.length === 0) {
Expand All @@ -59,6 +96,7 @@ export abstract class DedupOverlay extends DynamicViewOverlay {
const zIndex = d.zIndex;
let startLineIndex = Math.max(d.startLineNumber, visibleStartLineNumber) - visibleStartLineNumber;
const endLineIndex = Math.min(d.endLineNumber, visibleEndLineNumber) - visibleStartLineNumber;
const lane = Math.min(d.decorationLane, decorationLaneCount);

if (prevClassName === className) {
startLineIndex = Math.max(prevEndLineIndex + 1, startLineIndex);
Expand All @@ -69,7 +107,7 @@ export abstract class DedupOverlay extends DynamicViewOverlay {
}

for (let i = startLineIndex; i <= prevEndLineIndex; i++) {
output[i].push([className, zIndex]);
output[i].add(lane, new RenderedDecoration(className, zIndex));
}
}

Expand All @@ -84,6 +122,7 @@ export class GlyphMarginOverlay extends DedupOverlay {
private _glyphMargin: boolean;
private _glyphMarginLeft: number;
private _glyphMarginWidth: number;
private _glyphMarginDecorationLaneCount: number;
private _renderResult: string[] | null;

constructor(context: ViewContext) {
Expand All @@ -97,6 +136,7 @@ export class GlyphMarginOverlay extends DedupOverlay {
this._glyphMargin = options.get(EditorOption.glyphMargin);
this._glyphMarginLeft = layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = layoutInfo.glyphMarginWidth;
this._glyphMarginDecorationLaneCount = layoutInfo.glyphMarginDecorationLaneCount;
this._renderResult = null;
this._context.addEventHandler(this);
}
Expand All @@ -117,6 +157,7 @@ export class GlyphMarginOverlay extends DedupOverlay {
this._glyphMargin = options.get(EditorOption.glyphMargin);
this._glyphMarginLeft = layoutInfo.glyphMarginLeft;
this._glyphMarginWidth = layoutInfo.glyphMarginWidth;
this._glyphMarginDecorationLaneCount = layoutInfo.glyphMarginDecorationLaneCount;
return true;
}
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
Expand Down Expand Up @@ -151,8 +192,9 @@ export class GlyphMarginOverlay extends DedupOverlay {
const d = decorations[i];
const glyphMarginClassName = d.options.glyphMarginClassName;
const zIndex = d.options.zIndex;
const lane = d.options.glyphMargin?.position;
if (glyphMarginClassName) {
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName, zIndex);
r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName, zIndex, lane);
}
}
return r;
Expand All @@ -167,31 +209,40 @@ export class GlyphMarginOverlay extends DedupOverlay {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const decorationsToRender = this._getDecorations(ctx);
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, decorationsToRender);
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, decorationsToRender, this._glyphMarginDecorationLaneCount);

const lineHeight = this._lineHeight.toString();
const left = this._glyphMarginLeft.toString();
const width = this._glyphMarginWidth.toString();
const common = '" style="left:' + left + 'px;width:' + width + 'px' + ';height:' + lineHeight + 'px;"></div>';
const common = '" style="width:' + width + 'px' + ';height:' + lineHeight + 'px;';

const output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const renderInfo = toRender[lineIndex];

if (renderInfo.length === 0) {
if (renderInfo.isEmpty()) {
output[lineIndex] = '';
} else {
// Sort decorations to render in descending order by zIndex
renderInfo.sort(([_, aIndex], [__, bIndex]) => {
return bIndex - aIndex;
});

output[lineIndex] = (
'<div class="cgmr codicon '
+ renderInfo[0][0]
+ common
);
let css = '';
for (let lane = 1; lane <= this._glyphMarginDecorationLaneCount; lane += 1) {
const decorations = renderInfo.getLaneDecorations(lane);
if (decorations.length === 0) {
continue;
}
decorations.sort((a, b) => {
// Sort decorations to render in descending order by zIndex
return b.zIndex - a.zIndex;
});
const winningDecoration: RenderedDecoration = decorations[0];
const left = (this._glyphMarginLeft + (lane - 1) * this._lineHeight).toString();
css += (
'<div class="cgmr codicon '
+ winningDecoration.className // TODO@joyceerhl Implement overflow for remaining decorations
+ common
+ 'left:' + left + 'px;"></div>'
);
}
output[lineIndex] = css;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class LinesDecorationsOverlay extends DedupOverlay {
public prepareRender(ctx: RenderingContext): void {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx), 1);

const left = this._decorationsLeft.toString();
const width = this._decorationsWidth.toString();
Expand All @@ -100,10 +100,10 @@ export class LinesDecorationsOverlay extends DedupOverlay {
const output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const classNames = toRender[lineIndex];
const decorations = toRender[lineIndex].getLaneDecorations(1); // there is only one lane, see _render call above
let lineOutput = '';
for (let i = 0, len = classNames.length; i < len; i++) {
lineOutput += '<div class="cldr ' + classNames[i][0] + common;
for (const decoration of decorations) {
lineOutput += '<div class="cldr ' + decoration.className + common;
}
output[lineIndex] = lineOutput;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,15 @@ export class MarginViewLineDecorationsOverlay extends DedupOverlay {
public prepareRender(ctx: RenderingContext): void {
const visibleStartLineNumber = ctx.visibleRange.startLineNumber;
const visibleEndLineNumber = ctx.visibleRange.endLineNumber;
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx));
const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx), 1);

const output: string[] = [];
for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) {
const lineIndex = lineNumber - visibleStartLineNumber;
const classNames = toRender[lineIndex];
const decorations = toRender[lineIndex].getLaneDecorations(1); // there is only one lane, see _render call above
let lineOutput = '';
for (let i = 0, len = classNames.length; i < len; i++) {
lineOutput += '<div class="cmdr ' + classNames[i][0] + '" style=""></div>';
for (const decoration of decorations) {
lineOutput += '<div class="cmdr ' + decoration.className + '" style=""></div>';
}
output[lineIndex] = lineOutput;
}
Expand Down
4 changes: 4 additions & 0 deletions src/vs/editor/common/config/editorConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,8 @@ export interface IEditorConfiguration extends IDisposable {
* Set reserved height above.
*/
setReservedHeight(reservedHeight: number): void;
/**
* Set the number of decoration lanes to be rendered in the glyph margin.
*/
setGlyphMarginDecorationLaneCount(decorationLaneCount: number): void;
}
13 changes: 11 additions & 2 deletions src/vs/editor/common/config/editorOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ export interface IEnvironmentalOptions {
readonly pixelRatio: number;
readonly tabFocusMode: boolean;
readonly accessibilitySupport: AccessibilitySupport;
readonly glyphMarginDecorationLaneCount: number;
}

/**
Expand Down Expand Up @@ -2070,6 +2071,11 @@ export interface EditorLayoutInfo {
*/
readonly glyphMarginWidth: number;

/**
* The number of decoration lanes to render in the glyph margin.
*/
readonly glyphMarginDecorationLaneCount: number;

/**
* Left position for the line numbers.
*/
Expand Down Expand Up @@ -2157,6 +2163,7 @@ export interface EditorLayoutInfoComputerEnv {
readonly typicalHalfwidthCharacterWidth: number;
readonly maxDigitWidth: number;
readonly pixelRatio: number;
readonly glyphMarginDecorationLaneCount: number;
}

/**
Expand Down Expand Up @@ -2224,7 +2231,8 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
lineNumbersDigitCount: env.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth,
maxDigitWidth: env.fontInfo.maxDigitWidth,
pixelRatio: env.pixelRatio
pixelRatio: env.pixelRatio,
glyphMarginDecorationLaneCount: env.glyphMarginDecorationLaneCount
});
}

Expand Down Expand Up @@ -2470,7 +2478,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.

let glyphMarginWidth = 0;
if (showGlyphMargin) {
glyphMarginWidth = lineHeight;
glyphMarginWidth = lineHeight * env.glyphMarginDecorationLaneCount;
}

let glyphMarginLeft = 0;
Expand Down Expand Up @@ -2538,6 +2546,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.

glyphMarginLeft: glyphMarginLeft,
glyphMarginWidth: glyphMarginWidth,
glyphMarginDecorationLaneCount: env.glyphMarginDecorationLaneCount,

lineNumbersLeft: lineNumbersLeft,
lineNumbersWidth: lineNumbersWidth,
Expand Down
24 changes: 22 additions & 2 deletions src/vs/editor/common/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ export enum OverviewRulerLane {
Full = 7
}

/**
* Vertical Lane in the glyph margin of the editor.
*/
export enum GlyphMarginLane {
Left = 1,
Right = 2
}

/**
* Position in the minimap to render the decoration.
*/
Expand All @@ -55,6 +63,13 @@ export interface IDecorationOptions {
darkColor?: string | ThemeColor;
}

export interface IModelDecorationGlyphMarginOptions {
/**
* The position in the glyph margin.
*/
position: GlyphMarginLane;
}

/**
* Options for rendering a model decoration in the overview ruler.
*/
Expand All @@ -66,11 +81,11 @@ export interface IModelDecorationOverviewRulerOptions extends IDecorationOptions
}

/**
* Options for rendering a model decoration in the overview ruler.
* Options for rendering a model decoration in the minimap.
*/
export interface IModelDecorationMinimapOptions extends IDecorationOptions {
/**
* The position in the overview ruler.
* The position in the minimap.
*/
position: MinimapPosition;
}
Expand Down Expand Up @@ -141,6 +156,11 @@ export interface IModelDecorationOptions {
* If set, the decoration will be rendered in the glyph margin with this CSS class name.
*/
glyphMarginClassName?: string | null;
/**
* If set and the decoration has {@link glyphMarginClassName} set, render this decoration
* with the specified {@link IModelDecorationGlyphMarginOptions} in the glyph margin.
*/
glyphMargin?: IModelDecorationGlyphMarginOptions | null;
/**
* If set, the decoration will be rendered in the lines decorations with this CSS class name.
*/
Expand Down
Loading

0 comments on commit b5e90fd

Please sign in to comment.