~james/core

core/web/src/lib/components/lines/diff.ts -rw-r--r-- 3.1 KiB
452f17b7 — james chore(import): snapshot james.dad.town/core at f70c71c7696a 15 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import type { FileDiffMetadata, SelectionSide } from "@pierre/diffs";
import { findMatches } from "#lib/components/lines/search.ts";

export interface DiffRow {
	deletion?: number;
	addition?: number;
	text: string;
	gap: boolean;
}

export interface DiffMatch {
	id: string;
	row: number;
	line: number;
	side: SelectionSide;
	start: number;
	end: number;
}

const clean = (line: string | undefined): string => (line ?? "").replace(/\r?\n$/, "");

const firstLine = (start: number, count: number): number => (count === 0 ? start + 1 : start);

const buildRows = (diff: FileDiffMetadata): DiffRow[] => {
	const rows: DiffRow[] = [];
	const full = !diff.isPartial;
	const unchanged = (deletion: number, addition: number, count: number) => {
		for (let k = 0; k < count; k++) {
			rows.push({
				deletion: deletion + k,
				addition: addition + k,
				text: clean(diff.additionLines[addition + k - 1]),
				gap: true
			});
		}
	};
	let deletion = 1;
	let addition = 1;
	for (const hunk of diff.hunks) {
		deletion = firstLine(hunk.deletionStart, hunk.deletionCount);
		addition = firstLine(hunk.additionStart, hunk.additionCount);
		if (full) {
			unchanged(
				deletion - hunk.collapsedBefore,
				addition - hunk.collapsedBefore,
				hunk.collapsedBefore
			);
		}
		for (const content of hunk.hunkContent) {
			if (content.type === "context") {
				for (let k = 0; k < content.lines; k++) {
					rows.push({
						deletion: deletion + k,
						addition: addition + k,
						text: clean(diff.additionLines[content.additionLineIndex + k]),
						gap: false
					});
				}
				deletion += content.lines;
				addition += content.lines;
				continue;
			}
			for (let k = 0; k < content.deletions; k++) {
				rows.push({
					deletion: deletion + k,
					text: clean(diff.deletionLines[content.deletionLineIndex + k]),
					gap: false
				});
			}
			for (let k = 0; k < content.additions; k++) {
				rows.push({
					addition: addition + k,
					text: clean(diff.additionLines[content.additionLineIndex + k]),
					gap: false
				});
			}
			deletion += content.deletions;
			addition += content.additions;
		}
	}
	if (full) unchanged(deletion, addition, diff.additionLines.length - addition + 1);
	return rows;
};

const built = new WeakMap<FileDiffMetadata, DiffRow[]>();

export const diffRows = (diff: FileDiffMetadata): DiffRow[] => {
	let rows = built.get(diff);
	if (!rows) {
		rows = buildRows(diff);
		built.set(diff, rows);
	}
	return rows;
};

export const rowLine = (row: DiffRow, side: SelectionSide): number | undefined =>
	side === "deletions" ? row.deletion : row.addition;

export const rowSide = (row: DiffRow): SelectionSide =>
	row.addition === undefined ? "deletions" : "additions";

export const findDiffMatches = (
	id: string,
	rows: readonly DiffRow[],
	query: string,
	limit: number
): DiffMatch[] => {
	const visible = rows.flatMap((row, index) => (row.gap ? [] : [{ row, index }]));
	return findMatches(
		visible.map(({ row }) => row.text),
		query,
		limit
	).map((match) => {
		const { row, index } = visible[match.line - 1];
		const side = rowSide(row);
		return {
			id,
			row: index,
			line: rowLine(row, side)!,
			side,
			start: match.start,
			end: match.end
		};
	});
};