/** * @file Geometry for the JetBrains-style side-by-side diff, shared by the * Changes panel's review list (LaneDiffView) and its file editor (DiffEditor): * the curved band that joins a changed block on the left to its counterpart * on the right across the middle gutter, and the line mapping that keeps two * unpadded panes scrolled to the same place. Pure functions, no DOM. * @author Nguyễn Ngọc Trí Vĩ */ /** A changed block's lines on each side, 0-based and half-open: `a0 === a1` * is a block with no lines on the left (a pure insertion), and likewise b. */ export type Span = { a0: number; a1: number; b0: number; b1: number }; /** * SVG path of the band joining the left block (`y0`–`y1`) to the right one * (`z0`–`z1`) across a gutter `w` wide: an S-curve along each edge, flat where * a side is empty (`y0 === y1`), so a pure insertion reads as a wedge. */ export function bandPath(w: number, y0: number, y1: number, z0: number, z1: number): string { const c = w / 2; return `M0,${y0} C${c},${y0} ${c},${z0} ${w},${z0} L${w},${z1} C${c},${z1} ${c},${y1} 0,${y1} Z`; } /** * The line on the other side that `line` (fractional, 0-based) sits beside: * outside a block 1:1, shifted by the blocks above it; inside one, at the same * fraction of the other side's block — which collapses to that block's point * when the other side is empty. `spans` are in document order. */ export function mapLine(line: number, spans: readonly Span[], fromA: boolean): number { let ours = 0; let theirs = 0; for (const s of spans) { const [s0, s1, t0, t1] = fromA ? [s.a0, s.a1, s.b0, s.b1] : [s.b0, s.b1, s.a0, s.a1]; if (line < s0) break; if (line < s1) return t0 + ((line - s0) / (s1 - s0)) * (t1 - t0); ours = s1; theirs = t1; } return theirs + (line - ours); }