Skip to content
60 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Docs: a workspace knowledge base people and agents write together1/**
2 * What changed between two versions of a page: a line diff of their
3 * Markdown (longest common subsequence), for history. Pure.
4 */
5import type { DocDiffLine } from "@g1t/contracts";
6
7/** Past this many lines on both sides, the middle is compared as a whole block. */
8const MAX_CELLS = 4_000_000;
9
10export function diffLines(before: string, after: string): DocDiffLine[] {
11 const a = before ? before.replace(/\n$/, "").split("\n") : [];
12 const b = after ? after.replace(/\n$/, "").split("\n") : [];
13 // Common head and tail first: most edits touch a few lines.
14 let head = 0;
15 while (head < a.length && head < b.length && a[head] === b[head]) head++;
16 let tail = 0;
17 while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail++;
18 const midA = a.slice(head, a.length - tail);
19 const midB = b.slice(head, b.length - tail);
20 const out: DocDiffLine[] = a.slice(0, head).map((text) => ({ op: "same" as const, text }));
21 if (midA.length * midB.length > MAX_CELLS) {
22 for (const text of midA) out.push({ op: "del", text });
23 for (const text of midB) out.push({ op: "add", text });
24 } else {
25 const n = midA.length;
26 const m = midB.length;
27 // lcs[i][j]: the longest common subsequence of midA[i..] and midB[j..].
28 const lcs = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
29 for (let i = n - 1; i >= 0; i--) {
30 for (let j = m - 1; j >= 0; j--) {
31 lcs[i]![j] = midA[i] === midB[j] ? lcs[i + 1]![j + 1]! + 1 : Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!);
32 }
33 }
34 let i = 0;
35 let j = 0;
36 while (i < n && j < m) {
37 if (midA[i] === midB[j]) {
38 out.push({ op: "same", text: midA[i]! });
39 i++;
40 j++;
41 } else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) out.push({ op: "del", text: midA[i++]! });
42 else out.push({ op: "add", text: midB[j++]! });
43 }
44 while (i < n) out.push({ op: "del", text: midA[i++]! });
45 while (j < m) out.push({ op: "add", text: midB[j++]! });
46 }
47 for (const text of a.slice(a.length - tail)) out.push({ op: "same", text });
48 return out;
49}
50
51/** How many lines were added and removed. */
52export function diffStats(lines: DocDiffLine[]): { added: number; removed: number } {
53 let added = 0;
54 let removed = 0;
55 for (const l of lines) {
56 if (l.op === "add") added++;
57 if (l.op === "del") removed++;
58 }
59 return { added, removed };
60}