diff --git a/src/vs/editor/browser/config/editorConfiguration.ts b/src/vs/editor/browser/config/editorConfiguration.ts index 7e99e40ce4799..fc197da1d5726 100644 --- a/src/vs/editor/browser/config/editorConfiguration.ts +++ b/src/vs/editor/browser/config/editorConfiguration.ts @@ -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(); /** @@ -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); } @@ -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 { diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 62db35d5a0ccf..0225250768c40 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -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}\';', diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css index 17f7d5defd5b5..ee86a567698a1 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.css @@ -16,5 +16,4 @@ position: absolute; display: flex; align-items: center; - justify-content: center; } diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index f7ef3e47d64cc..0af60f7571f6b 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -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) { @@ -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); @@ -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)); } } @@ -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) { @@ -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); } @@ -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 { @@ -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; @@ -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;">'; + 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] = ( - '
' + ); + } + output[lineIndex] = css; } } diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index b0e9e91912833..9ed72e6acf0bf 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -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(); @@ -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 += '
'; + for (const decoration of decorations) { + lineOutput += '
'; } output[lineIndex] = lineOutput; } diff --git a/src/vs/editor/common/config/editorConfiguration.ts b/src/vs/editor/common/config/editorConfiguration.ts index 454d81cb089ec..415f8532d8526 100644 --- a/src/vs/editor/common/config/editorConfiguration.ts +++ b/src/vs/editor/common/config/editorConfiguration.ts @@ -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; } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 12efdc4b3b4b6..52f6dcd515324 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -834,6 +834,7 @@ export interface IEnvironmentalOptions { readonly pixelRatio: number; readonly tabFocusMode: boolean; readonly accessibilitySupport: AccessibilitySupport; + readonly glyphMarginDecorationLaneCount: number; } /** @@ -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. */ @@ -2157,6 +2163,7 @@ export interface EditorLayoutInfoComputerEnv { readonly typicalHalfwidthCharacterWidth: number; readonly maxDigitWidth: number; readonly pixelRatio: number; + readonly glyphMarginDecorationLaneCount: number; } /** @@ -2224,7 +2231,8 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption | null = null; + private _affectsGlyphMargin: boolean; constructor(private readonly handleBeforeFire: (affectedInjectedTextLines: Set | null) => void) { super(); @@ -2351,6 +2362,7 @@ class DidChangeDecorationsEmitter extends Disposable { this._shouldFireDeferred = false; this._affectsMinimap = false; this._affectsOverviewRuler = false; + this._affectsGlyphMargin = false; } hasListeners(): boolean { @@ -2387,12 +2399,16 @@ class DidChangeDecorationsEmitter extends Disposable { if (!this._affectsOverviewRuler) { this._affectsOverviewRuler = options.overviewRuler && options.overviewRuler.color ? true : false; } + if (!this._affectsGlyphMargin) { + this._affectsGlyphMargin = options.glyphMarginClassName ? true : false; + } this.tryFire(); } public fire(): void { this._affectsMinimap = true; this._affectsOverviewRuler = true; + this._affectsGlyphMargin = true; this.tryFire(); } @@ -2409,11 +2425,13 @@ class DidChangeDecorationsEmitter extends Disposable { const event: IModelDecorationsChangedEvent = { affectsMinimap: this._affectsMinimap, - affectsOverviewRuler: this._affectsOverviewRuler + affectsOverviewRuler: this._affectsOverviewRuler, + affectsGlyphMargin: this._affectsGlyphMargin }; this._shouldFireDeferred = false; this._affectsMinimap = false; this._affectsOverviewRuler = false; + this._affectsGlyphMargin = false; this._actual.fire(event); } } diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index c4c38394c3242..4552f43e07224 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -349,6 +349,14 @@ export enum EndOfLineSequence { CRLF = 1 } +/** + * Vertical Lane in the glyph margin of the editor. + */ +export enum GlyphMarginLane { + Left = 1, + Right = 2 +} + /** * Describes what to do with the indentation when pressing Enter. */ diff --git a/src/vs/editor/common/textModelEvents.ts b/src/vs/editor/common/textModelEvents.ts index c078ae0ec4d90..8c25e06776d81 100644 --- a/src/vs/editor/common/textModelEvents.ts +++ b/src/vs/editor/common/textModelEvents.ts @@ -90,6 +90,7 @@ export interface IModelContentChangedEvent { export interface IModelDecorationsChangedEvent { readonly affectsMinimap: boolean; readonly affectsOverviewRuler: boolean; + readonly affectsGlyphMargin: boolean; } /** diff --git a/src/vs/editor/common/viewEvents.ts b/src/vs/editor/common/viewEvents.ts index 2182d17b7f8cd..0e6cb0fa16670 100644 --- a/src/vs/editor/common/viewEvents.ts +++ b/src/vs/editor/common/viewEvents.ts @@ -75,14 +75,17 @@ export class ViewDecorationsChangedEvent { readonly affectsMinimap: boolean; readonly affectsOverviewRuler: boolean; + readonly affectsGlyphMargin: boolean; constructor(source: IModelDecorationsChangedEvent | null) { if (source) { this.affectsMinimap = source.affectsMinimap; this.affectsOverviewRuler = source.affectsOverviewRuler; + this.affectsGlyphMargin = source.affectsGlyphMargin; } else { this.affectsMinimap = true; this.affectsOverviewRuler = true; + this.affectsGlyphMargin = true; } } } diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index eb18dced3c1f6..cf61c0bac20b5 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, GlyphMarginLane, IAttachedView, ICursorStateComputer, 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'; @@ -459,6 +459,54 @@ export class ViewModel extends Disposable implements IViewModel { this._register(this.model.onDidChangeDecorations((e) => { this._decorations.onModelDecorationsChanged(); + + // Determine whether we need to resize the glyph margin + if (e.affectsGlyphMargin) { + const decorations = this.model.getAllMarginDecorations(); + + let hasTwoLanes = false; + + // Decorations are already sorted by their start position, but protect against future changes + decorations.sort((a, b) => Range.compareRangesUsingStarts(a.range, b.range)); + + let leftDecRange: Range | null = null; + let rightDecRange: Range | null = null; + for (const decoration of decorations) { + const position = decoration.options.glyphMargin?.position ?? GlyphMarginLane.Left; + + if (position === GlyphMarginLane.Left && (!leftDecRange || Range.compareRangesUsingEnds(leftDecRange, decoration.range) < 0)) { + // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane + leftDecRange = decoration.range; + } + + if (position === GlyphMarginLane.Right && (!rightDecRange || Range.compareRangesUsingEnds(rightDecRange, decoration.range) < 0)) { + // assign only if the range of `decoration` ends after, which means it has a higher chance to overlap with the other lane + rightDecRange = decoration.range; + } + + if (leftDecRange && rightDecRange) { + + if (leftDecRange.endLineNumber < rightDecRange.startLineNumber) { + // there's no chance for `leftDecRange` to ever intersect something going further + leftDecRange = null; + continue; + } + + if (rightDecRange.endLineNumber < leftDecRange.startLineNumber) { + // there's no chance for `rightDecRange` to ever intersect something going further + rightDecRange = null; + continue; + } + + // leftDecRange and rightDecRange are intersecting or touching => we need two lanes + hasTwoLanes = true; + break; + } + } + + this._configuration.setGlyphMarginDecorationLaneCount(hasTwoLanes ? 2 : 1); + } + this._eventDispatcher.emitSingleViewEvent(new viewEvents.ViewDecorationsChangedEvent(e)); this._eventDispatcher.emitOutgoingEvent(new ModelDecorationsChangedEvent(e)); })); diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 56bda1f9ac9f8..09a1bb51898b2 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -560,6 +560,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { MouseTargetType: standaloneEnums.MouseTargetType, OverlayWidgetPositionPreference: standaloneEnums.OverlayWidgetPositionPreference, OverviewRulerLane: standaloneEnums.OverviewRulerLane, + GlyphMarginLane: standaloneEnums.GlyphMarginLane, RenderLineNumbersType: standaloneEnums.RenderLineNumbersType, RenderMinimap: standaloneEnums.RenderMinimap, ScrollbarVisibility: standaloneEnums.ScrollbarVisibility, diff --git a/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts b/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts index b8c1d0d459276..87882eeb24b41 100644 --- a/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts +++ b/src/vs/editor/test/browser/config/editorLayoutProvider.test.ts @@ -96,6 +96,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth, maxDigitWidth: input.maxDigitWidth, pixelRatio: input.pixelRatio, + glyphMarginDecorationLaneCount: 1, }); assert.deepStrictEqual(actual, expected); } @@ -127,6 +128,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -195,6 +197,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -263,6 +266,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -331,6 +335,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -399,6 +404,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -467,6 +473,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 50, @@ -535,6 +542,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 50, @@ -603,6 +611,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 60, @@ -671,6 +680,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 30, @@ -739,6 +749,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 30, @@ -807,6 +818,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -875,6 +887,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -943,6 +956,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -1011,6 +1025,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 55, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 55, lineNumbersWidth: 0, @@ -1081,6 +1096,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -1151,6 +1167,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -1221,6 +1238,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -1291,6 +1309,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 0, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 0, lineNumbersWidth: 0, @@ -1359,6 +1378,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => { glyphMarginLeft: 0, glyphMarginWidth: 30, + glyphMarginDecorationLaneCount: 1, lineNumbersLeft: 30, lineNumbersWidth: 36, diff --git a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts index 68fa314066d3c..2ca90ed4e329c 100644 --- a/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts +++ b/src/vs/editor/test/browser/services/decorationRenderOptions.test.ts @@ -41,7 +41,7 @@ suite('Decoration Render Options', () => { const styleSheet = s.globalStyleSheet; s.registerDecorationType('test', 'example', options); const sheet = readStyleSheet(styleSheet); - assert(sheet.indexOf(`{background:url('https://github.com/microsoft/vscode/blob/main/resources/linux/code.png') center center no-repeat;background-size:contain;}`) >= 0); + assert(sheet.indexOf(`{background:url('https://github.com/microsoft/vscode/blob/main/resources/linux/code.png') no-repeat;background-size:contain;}`) >= 0); assert(sheet.indexOf(`{background-color:red;border-color:yellow;box-sizing: border-box;}`) >= 0); }); @@ -108,14 +108,14 @@ suite('Decoration Render Options', () => { // URI, only minimal encoding s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('data:image/svg+xml;base64,PHN2ZyB4b+') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') center center no-repeat;}`) > 0); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('data:image/svg+xml;base64,PHN2ZyB4b+') no-repeat;}`) > 0); s.removeDecorationType('example'); function assertBackground(url1: string, url2: string) { const actual = readStyleSheet(styleSheet); assert( - actual.indexOf(`{background:url('${url1}') center center no-repeat;}`) > 0 - || actual.indexOf(`{background:url('${url2}') center center no-repeat;}`) > 0 + actual.indexOf(`{background:url('${url1}') no-repeat;}`) > 0 + || actual.indexOf(`{background:url('${url2}') no-repeat;}`) > 0 ); } @@ -142,7 +142,7 @@ suite('Decoration Render Options', () => { } s.registerDecorationType('test', 'example', { gutterIconPath: URI.parse('http://test/pa\'th') }); - assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa%27th') center center no-repeat;}`) > 0); + assert(readStyleSheet(styleSheet).indexOf(`{background:url('http://test/pa%27th') no-repeat;}`) > 0); s.removeDecorationType('example'); }); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 6e8779a8bb536..24a7930ef5385 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -1529,6 +1529,14 @@ declare namespace monaco.editor { 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. */ @@ -1550,6 +1558,13 @@ declare namespace monaco.editor { darkColor?: string | ThemeColor; } + export interface IModelDecorationGlyphMarginOptions { + /** + * The position in the glyph margin. + */ + position: GlyphMarginLane; + } + /** * Options for rendering a model decoration in the overview ruler. */ @@ -1561,11 +1576,11 @@ declare namespace monaco.editor { } /** - * 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; } @@ -1625,6 +1640,11 @@ declare namespace monaco.editor { * 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. */ @@ -2951,6 +2971,7 @@ declare namespace monaco.editor { export interface IModelDecorationsChangedEvent { readonly affectsMinimap: boolean; readonly affectsOverviewRuler: boolean; + readonly affectsGlyphMargin: boolean; } export interface IModelOptionsChangedEvent { @@ -4038,6 +4059,10 @@ declare namespace monaco.editor { * The width of the glyph margin. */ readonly glyphMarginWidth: number; + /** + * The number of decoration lanes to render in the glyph margin. + */ + readonly glyphMarginDecorationLaneCount: number; /** * Left position for the line numbers. */ diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 53e9e2fff5939..33dbb1b2c4dad 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -26,7 +26,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ILanguageService } from 'vs/editor/common/languages/language'; -import { IModelDecorationOptions, IModelDecorationOverviewRulerOptions, IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { GlyphMarginLane, IModelDecorationOptions, IModelDecorationOverviewRulerOptions, IModelDecorationsChangeAccessor, ITextModel, OverviewRulerLane, TrackedRangeStickiness } from 'vs/editor/common/model'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -54,6 +54,7 @@ interface IBreakpointDecoration { const breakpointHelperDecoration: IModelDecorationOptions = { description: 'breakpoint-helper-decoration', glyphMarginClassName: ThemeIcon.asClassName(icons.debugBreakpointHint), + glyphMargin: { position: GlyphMarginLane.Right }, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }; @@ -133,6 +134,7 @@ function getBreakpointDecorationOptions(accessor: ServicesAccessor, model: IText const renderInline = breakpoint.column && (hasOtherBreakpointsOnLine || breakpoint.column > model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber)); return { description: 'breakpoint-decoration', + glyphMargin: { position: GlyphMarginLane.Right }, glyphMarginClassName: ThemeIcon.asClassName(icon), glyphMarginHoverMessage, stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,