Skip to content

Commit 57b6edd

Browse files
committed
Implement Editor Canvas Padding via editor.padding.maxEditorCanvasWidth. Addresses Issue #334959
1 parent d9637b3 commit 57b6edd

13 files changed

Lines changed: 275 additions & 26 deletions

File tree

src/vs/editor/browser/gpu/gpuUtils.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@
55

66
import { BugIndicatingError } from '../../../base/common/errors.js';
77
import { toDisposable, type IDisposable } from '../../../base/common/lifecycle.js';
8+
import type { EditorLayoutInfo } from '../../common/config/editorOptions.js';
9+
10+
export function getContentScissorRect(layout: EditorLayoutInfo, devicePixelRatio: number, canvasWidth: number, canvasHeight: number, constrainWidth: boolean): [number, number, number, number] {
11+
const left = Math.min(canvasWidth, Math.max(0, Math.ceil(layout.contentLeft * devicePixelRatio)));
12+
const right = constrainWidth
13+
? Math.min(canvasWidth, Math.floor((layout.contentLeft + layout.contentWidth - layout.verticalScrollbarWidth) * devicePixelRatio))
14+
: canvasWidth;
15+
return [left, 0, Math.max(0, right - left), canvasHeight];
16+
}
817

918
export const quadVertices = new Float32Array([
1019
1, 0,

src/vs/editor/browser/gpu/rectangleRenderer.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import type { ViewScrollChangedEvent } from '../../common/viewEvents.js';
1313
import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js';
1414
import type { ViewContext } from '../../common/viewModel/viewContext.js';
1515
import { GPULifecycle } from './gpuDisposable.js';
16-
import { observeDevicePixelDimensions, quadVertices } from './gpuUtils.js';
16+
import { getContentScissorRect, observeDevicePixelDimensions, quadVertices } from './gpuUtils.js';
1717
import { createObjectCollectionBuffer, type IObjectCollectionBuffer, type IObjectCollectionBufferEntry } from './objectCollectionBuffer.js';
1818
import { RectangleRendererBindingId, rectangleRendererWgsl } from './rectangleRenderer.wgsl.js';
1919

@@ -57,7 +57,6 @@ export class RectangleRenderer extends ViewEventHandler {
5757

5858
constructor(
5959
private readonly _context: ViewContext,
60-
private readonly _contentLeft: IObservable<number>,
6160
private readonly _devicePixelRatio: IObservable<number>,
6261
private readonly _canvas: HTMLCanvasElement,
6362
private readonly _ctx: GPUCanvasContext,
@@ -285,8 +284,12 @@ export class RectangleRenderer extends ViewEventHandler {
285284
pass.setBindGroup(0, this._bindGroup);
286285

287286
// Only draw the content area
288-
const contentLeft = Math.ceil(this._contentLeft.get() * this._devicePixelRatio.get());
289-
pass.setScissorRect(contentLeft, 0, this._canvas.width - contentLeft, this._canvas.height);
287+
pass.setScissorRect(...getContentScissorRect(
288+
this._context.configuration.options.get(EditorOption.layoutInfo),
289+
this._devicePixelRatio.get(),
290+
this._canvas.width, this._canvas.height,
291+
this._context.configuration.options.get(EditorOption.padding).maxEditorCanvasWidth > 0
292+
));
290293

291294
pass.draw(quadVertices.length / 2, this._shapeCollection.entryCount);
292295
pass.end();

src/vs/editor/browser/gpu/viewGpuContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ export class ViewGpuContext extends Disposable {
150150
}));
151151
this.contentLeft = contentLeft;
152152

153-
this.rectangleRenderer = this._register(this._instantiationService.createInstance(RectangleRenderer, context, this.contentLeft, this.devicePixelRatio, this.canvas.domNode, this.ctx, ViewGpuContext.device));
153+
this.rectangleRenderer = this._register(this._instantiationService.createInstance(RectangleRenderer, context, this.devicePixelRatio, this.canvas.domNode, this.ctx, ViewGpuContext.device));
154154
}
155155

156156
/**

src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,7 @@ export class EditorScrollbar extends ViewPart {
112112

113113
this.scrollbarDomNode.setLeft(layoutInfo.contentLeft);
114114

115-
const minimap = options.get(EditorOption.minimap);
116-
const side = minimap.side;
117-
if (side === 'right') {
118-
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth + layoutInfo.minimap.minimapWidth);
119-
} else {
120-
this.scrollbarDomNode.setWidth(layoutInfo.contentWidth);
121-
}
115+
this.scrollbarDomNode.setWidth(layoutInfo.width - layoutInfo.contentLeft);
122116
this.scrollbarDomNode.setHeight(layoutInfo.height);
123117
}
124118

src/vs/editor/browser/viewParts/viewLines/viewLines.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@
5858
white-space: nowrap;
5959
}
6060

61+
.monaco-editor .lines-content > :not(.contentWidgets) {
62+
clip-path: var(--editor-canvas-clip, none);
63+
}
64+
6165
.monaco-editor .view-line {
6266
box-sizing: border-box;
6367
position: absolute;

src/vs/editor/browser/viewParts/viewLines/viewLines.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,13 @@ export class ViewLines extends ViewPart implements IViewLines {
674674
const adjustedScrollTop = this._context.viewLayout.getCurrentScrollTop() - viewportData.bigNumbersDelta;
675675
this._linesContent.setTop(-adjustedScrollTop);
676676
this._linesContent.setLeft(-this._context.viewLayout.getCurrentScrollLeft());
677+
const options = this._context.configuration.options;
678+
const layoutInfo = options.get(EditorOption.layoutInfo);
679+
const scrollLeft = this._context.viewLayout.getCurrentScrollLeft();
680+
const clip = options.get(EditorOption.padding).maxEditorCanvasWidth > 0
681+
? `inset(${adjustedScrollTop}px calc(100% - ${scrollLeft + Math.max(0, layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth)}px) calc(100% - ${adjustedScrollTop + layoutInfo.height}px) ${scrollLeft}px)`
682+
: 'none';
683+
this._linesContent.domNode.style.setProperty('--editor-canvas-clip', clip);
677684
}
678685

679686
// --- width

src/vs/editor/browser/viewParts/viewLinesGpu/viewLinesGpu.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import type { ViewContext } from '../../../common/viewModel/viewContext.js';
1616
import { TextureAtlasPage } from '../../gpu/atlas/textureAtlasPage.js';
1717
import { BindingId, type IGpuRenderStrategy } from '../../gpu/gpu.js';
1818
import { GPULifecycle } from '../../gpu/gpuDisposable.js';
19-
import { quadVertices } from '../../gpu/gpuUtils.js';
19+
import { getContentScissorRect, quadVertices } from '../../gpu/gpuUtils.js';
2020
import { ViewGpuContext } from '../../gpu/viewGpuContext.js';
2121
import { FloatHorizontalRange, HorizontalPosition, HorizontalRange, IViewLines, LineVisibleRanges, RenderingContext, RestrictedRenderingContext, VisibleRanges } from '../../view/renderingContext.js';
2222
import { ViewPart } from '../../view/viewPart.js';
@@ -498,8 +498,12 @@ export class ViewLinesGpu extends ViewPart implements IViewLines {
498498
pass.setVertexBuffer(0, this._vertexBuffer);
499499

500500
// Only draw the content area
501-
const contentLeft = Math.ceil(this._viewGpuContext.contentLeft.get() * this._viewGpuContext.devicePixelRatio.get());
502-
pass.setScissorRect(contentLeft, 0, this.canvas.width - contentLeft, this.canvas.height);
501+
pass.setScissorRect(...getContentScissorRect(
502+
this._context.configuration.options.get(EditorOption.layoutInfo),
503+
this._viewGpuContext.devicePixelRatio.get(),
504+
this.canvas.width, this.canvas.height,
505+
this._context.configuration.options.get(EditorOption.padding).maxEditorCanvasWidth > 0
506+
));
503507

504508
pass.setBindGroup(0, this._bindGroup);
505509

src/vs/editor/browser/widget/codeEditor/codeEditorWidget.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1675,7 +1675,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
16751675
const layoutInfo = options.get(EditorOption.layoutInfo);
16761676

16771677
const top = CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, position.lineNumber, position.column) - this.getScrollTop();
1678-
const left = this._modelData.view.getOffsetForColumn(position.lineNumber, position.column) + layoutInfo.glyphMarginWidth + layoutInfo.lineNumbersWidth + layoutInfo.decorationsWidth - this.getScrollLeft();
1678+
const left = this._modelData.view.getOffsetForColumn(position.lineNumber, position.column) + layoutInfo.contentLeft - this.getScrollLeft();
16791679
const height = this.getLineHeightForPosition(position);
16801680
return {
16811681
top: top,

src/vs/editor/common/config/editorOptions.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2974,7 +2974,11 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
29742974
decorationsLeft += minimapLayout.minimapWidth;
29752975
contentLeft += minimapLayout.minimapWidth;
29762976
}
2977-
const contentWidth = remainingWidth - minimapLayout.minimapWidth;
2977+
const availableCanvasWidth = remainingWidth - minimapLayout.minimapWidth;
2978+
const contentWidth = padding.maxEditorCanvasWidth > 0
2979+
? Math.min(availableCanvasWidth, padding.maxEditorCanvasWidth + verticalScrollbarWidth)
2980+
: availableCanvasWidth;
2981+
contentLeft += Math.floor(Math.max(0, availableCanvasWidth - contentWidth) / 2);
29782982

29792983
// (leaving 2px for the cursor to have space after the last character)
29802984
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth));
@@ -3608,6 +3612,11 @@ export interface IEditorPaddingOptions {
36083612
* Spacing between bottom edge of editor and last line.
36093613
*/
36103614
bottom?: number;
3615+
/**
3616+
* Maximum text viewport width in CSS pixels, excluding gutters, minimap, and scrollbar.
3617+
* Extra space is split evenly on either side of the text. Defaults to 0 (no limit).
3618+
*/
3619+
maxEditorCanvasWidth?: number;
36113620
}
36123621

36133622
/**
@@ -3619,7 +3628,7 @@ class EditorPadding extends BaseEditorOption<EditorOption.padding, IEditorPaddin
36193628

36203629
constructor() {
36213630
super(
3622-
EditorOption.padding, 'padding', { top: 0, bottom: 0 },
3631+
EditorOption.padding, 'padding', { top: 0, bottom: 0, maxEditorCanvasWidth: 0 },
36233632
{
36243633
'editor.padding.top': {
36253634
type: 'number',
@@ -3634,6 +3643,13 @@ class EditorPadding extends BaseEditorOption<EditorOption.padding, IEditorPaddin
36343643
minimum: 0,
36353644
maximum: 1000,
36363645
description: nls.localize('padding.bottom', "Controls the amount of space between the bottom edge of the editor and the last line.")
3646+
},
3647+
'editor.padding.maxEditorCanvasWidth': {
3648+
type: 'number',
3649+
default: 0,
3650+
minimum: 0,
3651+
maximum: 10000,
3652+
description: nls.localize('padding.maxEditorCanvasWidth', "Controls the maximum width of the text area in pixels, excluding gutters, minimap, and scrollbar. Extra space is split evenly on either side of the text. Set to 0 to use the full available width.")
36373653
}
36383654
}
36393655
);
@@ -3647,7 +3663,8 @@ class EditorPadding extends BaseEditorOption<EditorOption.padding, IEditorPaddin
36473663

36483664
return {
36493665
top: EditorIntOption.clampedInt(input.top, 0, 0, 1000),
3650-
bottom: EditorIntOption.clampedInt(input.bottom, 0, 0, 1000)
3666+
bottom: EditorIntOption.clampedInt(input.bottom, 0, 0, 1000),
3667+
maxEditorCanvasWidth: EditorIntOption.clampedInt(input.maxEditorCanvasWidth, 0, 0, 10000)
36513668
};
36523669
}
36533670
}

src/vs/editor/contrib/stickyScroll/browser/stickyScrollWidget.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
100100
if (e.hasChanged(EditorOption.stickyScroll)) {
101101
updateScrollLeftPosition();
102102
}
103+
if (e.hasChanged(EditorOption.padding)) {
104+
this._updateWidgetWidth();
105+
}
103106
}));
104107
this._register(this._editor.onDidScrollChange((e) => {
105108
if (e.scrollLeftChanged) {
@@ -189,6 +192,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
189192
const layoutInfo = this._editor.getLayoutInfo();
190193
const lineNumbersWidth = layoutInfo.contentLeft;
191194
this._lineNumbersDomNode.style.width = `${lineNumbersWidth}px`;
195+
this._linesDomNodeScrollable.style.maxWidth = this._editor.getOption(EditorOption.padding).maxEditorCanvasWidth > 0
196+
? `${Math.max(0, layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth)}px`
197+
: '';
192198
this._linesDomNodeScrollable.style.setProperty('--vscode-editorStickyScroll-scrollableWidth', `${this._editor.getScrollWidth() - layoutInfo.verticalScrollbarWidth}px`);
193199
this._rootDomNode.style.width = `${layoutInfo.width - layoutInfo.verticalScrollbarWidth}px`;
194200
}

0 commit comments

Comments
 (0)