Skip to content

Commit 69ea40c

Browse files
committed
fix(diff): monkey-patch Monaco's combined-overview-ruler width 30 → 8 px
The previous "unified column on the modified pane" approach worked mechanically but only showed marks from the modified side — a per-side overview ruler is structurally incapable of rendering an AGGREGATED (original + modified) heatmap. So we're going back to Monaco's combined diff overview ruler (renderOverviewRuler: true) but solving the width problem properly. Research (see commit history + agent thread): In Monaco 0.43.x the combined ruler's width is hardcoded: esm/vs/editor/browser/widget/diffEditorWidget.js:1125 DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH = 30 It's a static class field, not an editor option. There's no theme key, no CSS variable, no constructor parameter for it. BUT Monaco re-reads the static on every layout() call inside _layoutOverviewRulers() and _doLayout(), so monkey-patching the field on the constructor + calling layout() re-flows the editor widths, viewport padding, and per-canvas ruler sizes consistently at the new value. Implementation: const cls: any = (this.monacoDiffEditor as any).constructor if (cls) { cls.ENTIRE_DIFF_OVERVIEW_WIDTH = 8 } // Also patch widget v2's OverviewRulerPart defensively const v2Part = (monaco as any)?.editor?.OverviewRulerPart if (v2Part) { v2Part.ONE_OVERVIEW_WIDTH = 4 v2Part.ENTIRE_DIFF_OVERVIEW_WIDTH = 8 } this.monacoDiffEditor.layout() The combined ruler is now 8 px wide and serves as the only vertical chrome on the diff page. Click + drag still work because Monaco's delegateVerticalScrollbarPointerDown delegates pointer events to the modified editor's underlying vertical scrollbar by SCREEN-Y coordinates, not ruler X-width — so the drag affordance survives both the visual scrollbar being hidden AND the ruler being narrowed to 8 px. Both per-side scrollbars set to vertical: 'hidden' since the combined heatmap is the single scroll affordance. Per-side overview rulers off (overviewRulerLanes: 0) so the combined heatmap is the only mark column. README rewritten to describe the constant-patching technique and the screen-Y-based click+drag delegation. Sources for the technique: https://github.com/microsoft/monaco-editor/blob/main/src/vs/editor/browser/widget/diffEditor/components/overviewRulerPart.ts https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IDiffEditorBaseOptions.html
1 parent e340347 commit 69ea40c

2 files changed

Lines changed: 88 additions & 57 deletions

File tree

README.md

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -68,23 +68,27 @@ Live at:
6868

6969
### Diff viewer (`/diff`)
7070

71-
- **Side-by-side diff** with the **change heatmap unified into the
72-
modified pane's scrollbar column.** Monaco's editor renders the
73-
per-side overview ruler and the vertical scrollbar in the same
74-
column whose width tracks `verticalScrollbarSize` — so by enabling
75-
`overviewRulerLanes: 3` on the modified pane while keeping its
76-
8-px scrollbar visible (`verticalScrollbarSize: 8`), the diff
77-
marks and the scroll slider read as a single 8-px chrome strip
78-
on the right edge of the diff. The original pane's scrollbar
79-
AND overview ruler are both off (`overviewRulerLanes: 0`,
80-
`scrollbar.vertical: 'hidden'`) since intra-diff sync drives
81-
that side from the modified scrollbar anyway. The diff editor's
82-
OWN combined overview ruler (which Monaco normally renders as a
83-
separate wider column with a chunky viewport-position slider) is
84-
disabled (`renderOverviewRuler: false`) so the unified per-side
85-
column is the only vertical chrome. Scroll affordances: spin the
86-
mousewheel inside either pane, drag the scroll slider on the
87-
right edge, or click on a change mark to jump to that line.
71+
- **Side-by-side diff** with the **combined change heatmap doubling
72+
as the only scrollbar.** Monaco's diff editor renders a unified
73+
overview ruler at the rightmost column that aggregates added /
74+
removed line marks from BOTH sides into one heatmap (a per-side
75+
overview ruler can only show one side's marks, so this is the
76+
only path to a true combined view). The column's width is
77+
hardcoded to 30 px in Monaco 0.43.x via the static
78+
`DiffEditorWidget.ENTIRE_DIFF_OVERVIEW_WIDTH` — we **monkey-patch
79+
it down to 8 px** at editor construction time by writing to the
80+
static field on the constructor (Monaco re-reads it on every
81+
`layout()` pass, so we patch then immediately call `layout()` to
82+
re-flow widths + canvas sizes). Both per-side scrollbars are
83+
hidden (`vertical: 'hidden'`, `verticalScrollbarSize: 0`); the
84+
combined heatmap is the only visible vertical chrome on the
85+
page. Scroll affordances all still work: the mousewheel scrolls
86+
either pane (intra-diff sync keeps them locked), click on a
87+
change mark jumps to that line, and click + drag on the heatmap
88+
scrolls — Monaco's `delegateVerticalScrollbarPointerDown` routes
89+
pointer events into the modified editor's underlying vertical
90+
scrollbar by **screen-Y coordinates**, so the affordance survives
91+
the visual scrollbar being hidden AND the ruler being narrowed.
8892
- **Editable pane labels** above the diff — rename either side and
8993
the URL hash is regenerated on the fly via `history.replaceState`,
9094
so the next *Copy link* picks up the new names.

pages/diff.vue

Lines changed: 67 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -284,57 +284,84 @@ export default Vue.extend({
284284
* when the container resizes, which is exactly the
285285
* flex-grow behaviour we have on the shell. */
286286
automaticLayout: true,
287-
/* Disable the diff editor's OWN combined overview ruler
288-
* (Monaco renders this as a separate ~14-px column to
289-
* the right of the modified pane, with the viewport
290-
* slider as a chunky gray rounded-rect — that's what was
291-
* appearing as an unhidable scrollbar). The heatmap +
292-
* scroll affordance is moved to the modified pane's
293-
* per-side overview ruler below, which Monaco renders
294-
* at the same width as the scrollbar (8 px here) and
295-
* fully wires for click-to-jump + drag-to-scroll. */
287+
/* Combined overview ruler ON — this is the rightmost
288+
* column inside the diff editor that shows added /
289+
* removed line marks aggregated across BOTH sides
290+
* (a per-side overview ruler can only show its own
291+
* side's marks, so this is the only way to render a
292+
* unified heatmap). The width is monkey-patched below
293+
* from Monaco's hardcoded 30 px → 8 px via the static
294+
* `ENTIRE_DIFF_OVERVIEW_WIDTH` field on the
295+
* DiffEditorWidget constructor (Monaco re-reads this
296+
* constant on every layout pass). The combined ruler's
297+
* click + drag is delegated to the modified editor's
298+
* vertical scrollbar by screen-Y coordinates inside
299+
* Monaco's `delegateVerticalScrollbarPointerDown`, so
300+
* scroll-by-drag stays wired up at the narrower width.
301+
* */
296302
overviewRulerBorder: false,
297303
renderLineHighlight: 'none',
298-
renderOverviewRuler: false,
304+
renderOverviewRuler: true,
299305
}
300306
) as any
301307
if (this.monacoDiffEditor) {
302-
/* Dedupe per-pane scrollbars: hide the original pane's
303-
* vertical scrollbar entirely and keep a slim 8px
304-
* scrollbar on the modified pane. Both panes scroll in
305-
* lock-step thanks to Monaco's intra-diff sync, so a
306-
* single bar drives both. */
308+
/* Patch Monaco's hardcoded combined-overview width from
309+
* 30 px → 8 px so the heatmap column matches the slim
310+
* scrollbars used elsewhere. The constant is a static
311+
* class field on DiffEditorWidget; Monaco re-reads it
312+
* inside `_layoutOverviewRulers()` and `_doLayout()` on
313+
* every layout() call, so patching after construction +
314+
* calling layout() re-flows the editor widths, viewport
315+
* padding, and canvas sizes consistently. The legacy
316+
* widget exposes the static under the constructor (which
317+
* we reach via the instance's .constructor); widget v2's
318+
* `OverviewRulerPart` is also patched defensively for
319+
* future bundle versions even though 0.43.x defaults to
320+
* the legacy widget. */
307321
try {
308-
/* Original pane: scrollbar + overview ruler BOTH off.
309-
* Intra-diff sync drives this pane from the modified
310-
* side's scrollbar, so a separate scroll affordance
311-
* here would only confuse. */
322+
const TARGET_WIDTH = 8
323+
const cls: any = (this.monacoDiffEditor as any).constructor
324+
if (cls) {
325+
cls.ENTIRE_DIFF_OVERVIEW_WIDTH = TARGET_WIDTH
326+
}
327+
const v2Part = (monaco as any)?.editor?.OverviewRulerPart
328+
if (v2Part) {
329+
v2Part.ONE_OVERVIEW_WIDTH = TARGET_WIDTH / 2
330+
v2Part.ENTIRE_DIFF_OVERVIEW_WIDTH = TARGET_WIDTH
331+
}
332+
/* Force a layout pass so the editor widths + ruler
333+
* canvas dimensions pick up the new constant. */
334+
this.monacoDiffEditor.layout()
335+
} catch (_e) {
336+
/* Patch failed — overview ruler stays at default 30 px.
337+
* Visually wider than the slim chrome elsewhere but not
338+
* broken. */
339+
}
340+
try {
341+
/* Both per-side scrollbars hidden. The combined diff
342+
* overview ruler (now 8 px wide) is the single visible
343+
* vertical affordance: it shows aggregated change marks
344+
* AND functions as the scrollbar — Monaco's
345+
* `delegateVerticalScrollbarPointerDown` routes pointer
346+
* events into the modified editor's underlying vertical
347+
* scrollbar by screen-Y coordinates, so click + drag
348+
* still work even though the modified scrollbar is
349+
* visually hidden. Per-side overview rulers also off
350+
* (lanes: 0) so the combined heatmap is the only mark
351+
* column. */
352+
const hiddenScroll = {
353+
vertical: 'hidden' as const,
354+
verticalScrollbarSize: 0,
355+
verticalSliderSize: 0,
356+
}
312357
this.monacoDiffEditor.getOriginalEditor().updateOptions({
313-
scrollbar: {
314-
vertical: 'hidden',
315-
verticalScrollbarSize: 0,
316-
verticalSliderSize: 0,
317-
},
358+
scrollbar: hiddenScroll,
318359
overviewRulerLanes: 0,
319360
hideCursorInOverviewRuler: true,
320361
})
321-
/* Modified pane: slim 8-px scrollbar visible AND its
322-
* per-side overview ruler enabled with 3 lanes for
323-
* diff-mark rendering. Monaco draws the overview ruler
324-
* and the scrollbar in the SAME column (width =
325-
* verticalScrollbarSize), so the heatmap and the
326-
* scroll slider read as one unified 8-px chrome strip
327-
* on the right edge of the modified pane. Click + drag
328-
* on the slider scrolls; click on a change mark jumps
329-
* to that line. */
330362
this.monacoDiffEditor.getModifiedEditor().updateOptions({
331-
scrollbar: {
332-
vertical: 'auto',
333-
verticalScrollbarSize: 8,
334-
verticalSliderSize: 8,
335-
useShadows: false,
336-
},
337-
overviewRulerLanes: 3,
363+
scrollbar: hiddenScroll,
364+
overviewRulerLanes: 0,
338365
hideCursorInOverviewRuler: true,
339366
})
340367
} catch (_e) {

0 commit comments

Comments
 (0)