Skip to content

Commit bee8669

Browse files
committed
feat(diff): represent expanded context in row geometry
Model folded context as explicit diff rows before render planning so line gutters, hunk anchors, row windowing, and section geometry all share the same structure.
1 parent fdbec32 commit bee8669

10 files changed

Lines changed: 1111 additions & 49 deletions

src/ui/diff/codeColumns.test.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, expect, test } from "bun:test";
22
import type { DiffFile } from "../../core/types";
3-
import { maxFileCodeLineWidth } from "./codeColumns";
3+
import { findMaxLineNumberInRows, maxFileCodeLineWidth } from "./codeColumns";
4+
import type { DiffRow } from "./pierre";
45

56
/** Generate a large diff metadata fixture without checking a huge file into the repo. */
67
function createLargeLineFixture(lineCount: number, widestLine: string): DiffFile {
@@ -29,3 +30,51 @@ describe("code column measurement", () => {
2930
expect(maxFileCodeLineWidth(file)).toBe("the widest generated line".length);
3031
});
3132
});
33+
34+
describe("findMaxLineNumberInRows", () => {
35+
test("accounts for collapsed gap ranges that can later expand", () => {
36+
const rows: DiffRow[] = [
37+
{
38+
type: "split-line",
39+
key: "file:line:0",
40+
fileId: "file",
41+
hunkIndex: 0,
42+
left: { kind: "deletion", sign: "-", lineNumber: 5, spans: [{ text: "old" }] },
43+
right: { kind: "addition", sign: "+", lineNumber: 5, spans: [{ text: "new" }] },
44+
},
45+
{
46+
type: "collapsed",
47+
key: "file:collapsed:trailing",
48+
fileId: "file",
49+
hunkIndex: 0,
50+
oldRange: [6, 1000],
51+
newRange: [6, 1000],
52+
position: "trailing",
53+
text: "995 unchanged lines",
54+
},
55+
];
56+
57+
expect(findMaxLineNumberInRows(rows)).toBe(1000);
58+
});
59+
60+
test("accounts for synthesized stack expansion rows", () => {
61+
const rows: DiffRow[] = [
62+
{
63+
type: "stack-line",
64+
key: "file:expanded:trailing:0:0",
65+
fileId: "file",
66+
hunkIndex: 0,
67+
isExpansionRow: true,
68+
cell: {
69+
kind: "context",
70+
sign: " ",
71+
oldLineNumber: 998,
72+
newLineNumber: 1002,
73+
spans: [{ text: "context" }],
74+
},
75+
},
76+
];
77+
78+
expect(findMaxLineNumberInRows(rows, 9)).toBe(1002);
79+
});
80+
});

src/ui/diff/codeColumns.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { DiffFile, LayoutMode } from "../../core/types";
2+
import type { DiffRow } from "./pierre";
23

34
export const DIFF_CODE_TAB_WIDTH = 2;
45
export const DIFF_RAIL_PREFIX_WIDTH = 1;
@@ -47,6 +48,29 @@ export function findMaxLineNumber(file: DiffFile) {
4748
return Math.max(highest, 1);
4849
}
4950

51+
/** Find the widest line-number gutter needed for an already-expanded row stream. */
52+
export function findMaxLineNumberInRows(rows: Iterable<DiffRow>, fallback = 1) {
53+
let highest = fallback;
54+
55+
for (const row of rows) {
56+
if (row.type === "collapsed") {
57+
highest = Math.max(highest, row.oldRange[1], row.newRange[1]);
58+
continue;
59+
}
60+
61+
if (row.type === "split-line") {
62+
highest = Math.max(highest, row.left.lineNumber ?? 0, row.right.lineNumber ?? 0);
63+
continue;
64+
}
65+
66+
if (row.type === "stack-line") {
67+
highest = Math.max(highest, row.cell.oldLineNumber ?? 0, row.cell.newLineNumber ?? 0);
68+
}
69+
}
70+
71+
return Math.max(highest, 1);
72+
}
73+
5074
/** Split-view panes reserve one rail column on the left and one separator column in the middle. */
5175
export function resolveSplitPaneWidths(width: number) {
5276
const usableWidth = Math.max(0, width - DIFF_RAIL_PREFIX_WIDTH - DIFF_SPLIT_SEPARATOR_WIDTH);

0 commit comments

Comments
 (0)