@aparte/plugin-diff 0.0.0-stage → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,523 @@
1
+ import { aparteGlobalConfig } from "@aparte/core";
2
+ const MAX_COST = 2e3;
3
+ const ANCHOR_FROM = MAX_COST;
4
+ const DEFAULT_CONTEXT = 3;
5
+ function diffLines(a, b) {
6
+ const edits = [];
7
+ const tasks = [{ a0: 0, a1: a.length, b0: 0, b1: b.length }];
8
+ while (tasks.length) {
9
+ const task = tasks.pop();
10
+ if (typeof task === "number") {
11
+ for (let k = 0; k < task; k++) edits.push("eq");
12
+ continue;
13
+ }
14
+ let { a0, a1, b0, b1 } = task;
15
+ while (a0 < a1 && b0 < b1 && a[a0] === b[b0]) {
16
+ edits.push("eq");
17
+ a0++;
18
+ b0++;
19
+ }
20
+ let shared = 0;
21
+ while (a1 > a0 && b1 > b0 && a[a1 - 1] === b[b1 - 1]) {
22
+ a1--;
23
+ b1--;
24
+ shared++;
25
+ }
26
+ if (shared) tasks.push(shared);
27
+ if (a1 - a0 + (b1 - b0) > ANCHOR_FROM) {
28
+ const anchors = anchorsOf(a, b, a0, a1, b0, b1);
29
+ if (anchors.length) {
30
+ const parts = [];
31
+ let pa = a0;
32
+ let pb = b0;
33
+ for (const [i, j] of anchors) {
34
+ parts.push({ a0: pa, a1: i, b0: pb, b1: j }, 1);
35
+ pa = i + 1;
36
+ pb = j + 1;
37
+ }
38
+ parts.push({ a0: pa, a1, b0: pb, b1 });
39
+ for (let k = parts.length - 1; k >= 0; k--) tasks.push(parts[k]);
40
+ continue;
41
+ }
42
+ }
43
+ for (const edit of middle(a, b, a0, a1, b0, b1)) edits.push(edit);
44
+ }
45
+ return removalsFirst(edits);
46
+ }
47
+ function anchorsOf(a, b, a0, a1, b0, b1) {
48
+ const seen = /* @__PURE__ */ new Map();
49
+ for (let i = a0; i < a1; i++) {
50
+ const entry = seen.get(a[i]);
51
+ if (entry) entry[0]++;
52
+ else seen.set(a[i], [1, 0, -1]);
53
+ }
54
+ for (let j = b0; j < b1; j++) {
55
+ const entry = seen.get(b[j]);
56
+ if (!entry) continue;
57
+ entry[1]++;
58
+ entry[2] = j;
59
+ }
60
+ const pairs = [];
61
+ for (let i = a0; i < a1; i++) {
62
+ const entry = seen.get(a[i]);
63
+ if (entry[0] === 1 && entry[1] === 1) pairs.push([i, entry[2]]);
64
+ }
65
+ if (!pairs.length) return pairs;
66
+ const tails = [];
67
+ const before = new Int32Array(pairs.length).fill(-1);
68
+ for (let p = 0; p < pairs.length; p++) {
69
+ const j = pairs[p][1];
70
+ let lo = 0;
71
+ let hi = tails.length;
72
+ while (lo < hi) {
73
+ const mid = lo + hi >> 1;
74
+ if (pairs[tails[mid]][1] < j) lo = mid + 1;
75
+ else hi = mid;
76
+ }
77
+ if (lo > 0) before[p] = tails[lo - 1];
78
+ tails[lo] = p;
79
+ }
80
+ const run = [];
81
+ for (let p = tails[tails.length - 1]; p >= 0; p = before[p]) run.push(pairs[p]);
82
+ return run.reverse();
83
+ }
84
+ function middle(a, b, a0, a1, b0, b1) {
85
+ const n = a1 - a0;
86
+ const m = b1 - b0;
87
+ if (n === 0) return new Array(m).fill("add");
88
+ if (m === 0) return new Array(n).fill("del");
89
+ const max = n + m;
90
+ const offset = max + 1;
91
+ const v = new Int32Array(2 * max + 3);
92
+ const trace = [];
93
+ let reached = -1;
94
+ for (let d = 0; d <= Math.min(max, MAX_COST); d++) {
95
+ trace.push(v.slice(offset - d - 1, offset + d + 2));
96
+ for (let k = -d; k <= d; k += 2) {
97
+ let x2 = k === -d || k !== d && v[offset + k - 1] < v[offset + k + 1] ? v[offset + k + 1] : v[offset + k - 1] + 1;
98
+ let y2 = x2 - k;
99
+ while (x2 < n && y2 < m && a[a0 + x2] === b[b0 + y2]) {
100
+ x2++;
101
+ y2++;
102
+ }
103
+ v[offset + k] = x2;
104
+ if (x2 >= n && y2 >= m) {
105
+ reached = d;
106
+ break;
107
+ }
108
+ }
109
+ if (reached >= 0) break;
110
+ }
111
+ if (reached < 0) return [...new Array(n).fill("del"), ...new Array(m).fill("add")];
112
+ const out = [];
113
+ let x = n;
114
+ let y = m;
115
+ for (let d = reached; d >= 0; d--) {
116
+ const snapshot = trace[d];
117
+ const at = (k2) => snapshot[k2 + d + 1];
118
+ const k = x - y;
119
+ const down = k === -d || k !== d && at(k - 1) < at(k + 1);
120
+ const prevK = down ? k + 1 : k - 1;
121
+ const prevX = at(prevK);
122
+ const prevY = prevX - prevK;
123
+ while (x > prevX && y > prevY) {
124
+ out.push("eq");
125
+ x--;
126
+ y--;
127
+ }
128
+ if (d > 0) out.push(down ? "add" : "del");
129
+ x = prevX;
130
+ y = prevY;
131
+ }
132
+ return out.reverse();
133
+ }
134
+ function removalsFirst(edits) {
135
+ const out = [];
136
+ let dels = 0;
137
+ let adds = 0;
138
+ const flush = () => {
139
+ for (; dels > 0; dels--) out.push("del");
140
+ for (; adds > 0; adds--) out.push("add");
141
+ };
142
+ for (const edit of edits) {
143
+ if (edit === "del") dels++;
144
+ else if (edit === "add") adds++;
145
+ else {
146
+ flush();
147
+ out.push("eq");
148
+ }
149
+ }
150
+ flush();
151
+ return out;
152
+ }
153
+ function splitLines(text) {
154
+ if (text === "") return { lines: [], endsWithNewline: true };
155
+ const lines = text.split(/\r?\n/);
156
+ const endsWithNewline = lines[lines.length - 1] === "";
157
+ if (endsWithNewline) lines.pop();
158
+ return { lines, endsWithNewline };
159
+ }
160
+ function diffTexts(before, after, options = {}) {
161
+ const context = Math.max(0, Math.floor(options.context ?? DEFAULT_CONTEXT));
162
+ const old = splitLines(before ?? "");
163
+ const next = splitLines(after ?? "");
164
+ const keys = (side) => side.lines.map((line, i) => i === side.lines.length - 1 && !side.endsWithNewline ? `${line}\0` : line);
165
+ const edits = diffLines(keys(old), keys(next));
166
+ const lines = [];
167
+ let o = 0;
168
+ let n = 0;
169
+ for (const edit of edits) {
170
+ const line = edit === "eq" ? { kind: "context", text: next.lines[n], oldLine: o + 1, newLine: n + 1 } : edit === "del" ? { kind: "del", text: old.lines[o], oldLine: o + 1 } : { kind: "add", text: next.lines[n], newLine: n + 1 };
171
+ const lastOld = edit !== "add" && o === old.lines.length - 1 && !old.endsWithNewline;
172
+ const lastNew = edit !== "del" && n === next.lines.length - 1 && !next.endsWithNewline;
173
+ if (lastOld || lastNew) line.noNewline = true;
174
+ if (edit !== "add") o++;
175
+ if (edit !== "del") n++;
176
+ lines.push(line);
177
+ }
178
+ const file = {
179
+ path: options.path ?? "",
180
+ status: "modified",
181
+ added: lines.filter((l) => l.kind === "add").length,
182
+ removed: lines.filter((l) => l.kind === "del").length,
183
+ hunks: hunksOf(lines, context)
184
+ };
185
+ if (before === null && after !== null) file.status = "added";
186
+ else if (after === null && before !== null) file.status = "deleted";
187
+ else if (options.oldPath !== void 0 && options.oldPath !== file.path) {
188
+ file.oldPath = options.oldPath;
189
+ file.status = "renamed";
190
+ }
191
+ return file;
192
+ }
193
+ function hunksOf(lines, context) {
194
+ const changed = [];
195
+ lines.forEach((line, i2) => {
196
+ if (line.kind !== "context") changed.push(i2);
197
+ });
198
+ const hunks = [];
199
+ let i = 0;
200
+ while (i < changed.length) {
201
+ let last = changed[i];
202
+ let j = i + 1;
203
+ while (j < changed.length && changed[j] - last - 1 <= 2 * context) last = changed[j++];
204
+ const from = Math.max(0, changed[i] - context);
205
+ const to = Math.min(lines.length - 1, last + context);
206
+ hunks.push(hunkOf(lines, from, to));
207
+ i = j;
208
+ }
209
+ return hunks;
210
+ }
211
+ function hunkOf(lines, from, to) {
212
+ const own = lines.slice(from, to + 1);
213
+ const oldCount = own.filter((l) => l.kind !== "add").length;
214
+ const newCount = own.filter((l) => l.kind !== "del").length;
215
+ let oldBefore = 0;
216
+ let newBefore = 0;
217
+ for (let k = from - 1; k >= 0; k--) {
218
+ const line = lines[k];
219
+ if (oldBefore === 0 && line.oldLine !== void 0) oldBefore = line.oldLine;
220
+ if (newBefore === 0 && line.newLine !== void 0) newBefore = line.newLine;
221
+ if (oldBefore && newBefore) break;
222
+ }
223
+ return {
224
+ oldStart: oldCount > 0 ? oldBefore + 1 : oldBefore,
225
+ oldCount,
226
+ newStart: newCount > 0 ? newBefore + 1 : newBefore,
227
+ newCount,
228
+ lines: own.map((line) => ({ ...line }))
229
+ };
230
+ }
231
+ const HUNK = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@ ?(.*)$/;
232
+ const NO_NEWLINE = "\\";
233
+ const FILE_MODE = "100644";
234
+ function parseUnifiedDiff(text) {
235
+ const lines = text.split(/\r?\n/);
236
+ const files = [];
237
+ let file = null;
238
+ let fromGit = null;
239
+ let renameFrom = null;
240
+ let renameTo = null;
241
+ let minus = null;
242
+ let plus = null;
243
+ let headers = false;
244
+ let similar = false;
245
+ let copied = false;
246
+ const close = () => {
247
+ if (!file) return;
248
+ const prefixed = fromGit !== null || (minus === null || minus.startsWith("a/")) && (plus === null || plus.startsWith("b/"));
249
+ const side = (path) => path !== null && prefixed ? stripSide(path) : path;
250
+ const oldPath = renameFrom ?? (minus === null ? fromGit?.[0] : side(minus));
251
+ const newPath = renameTo ?? (plus === null ? fromGit?.[1] : side(plus));
252
+ if (file.status !== "deleted") file.path = newPath ?? oldPath ?? "";
253
+ else file.path = oldPath ?? newPath ?? "";
254
+ const renamed = renameFrom !== null || renameTo !== null || similar && !copied && fromGit !== null && fromGit[0] !== fromGit[1];
255
+ if (file.status === "modified" && renamed && oldPath && newPath && oldPath !== newPath) {
256
+ file.status = "renamed";
257
+ file.oldPath = oldPath;
258
+ }
259
+ file.added = file.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.kind === "add").length, 0);
260
+ file.removed = file.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.kind === "del").length, 0);
261
+ files.push(file);
262
+ file = null;
263
+ fromGit = null;
264
+ renameFrom = renameTo = minus = plus = null;
265
+ headers = similar = copied = false;
266
+ };
267
+ const open = () => {
268
+ close();
269
+ file = { path: "", status: "modified", added: 0, removed: 0, hunks: [] };
270
+ return file;
271
+ };
272
+ let i = 0;
273
+ while (i < lines.length) {
274
+ const line = lines[i];
275
+ if (line.startsWith("diff --git ")) {
276
+ open();
277
+ fromGit = gitPaths(line.slice("diff --git ".length));
278
+ i++;
279
+ continue;
280
+ }
281
+ if (line.startsWith("--- ") && lines[i + 1]?.startsWith("+++ ")) {
282
+ const current2 = file;
283
+ if (!current2 || current2.hunks.length > 0 || headers) open();
284
+ const target = file;
285
+ minus = headerPath(line.slice(4));
286
+ plus = headerPath(lines[i + 1].slice(4));
287
+ headers = true;
288
+ if (minus === null) target.status = "added";
289
+ if (plus === null) target.status = "deleted";
290
+ i += 2;
291
+ continue;
292
+ }
293
+ const current = file;
294
+ if (current) {
295
+ const hunk = HUNK.exec(line);
296
+ if (hunk) {
297
+ i = readHunk(lines, i, hunk, current);
298
+ continue;
299
+ }
300
+ if (line.startsWith("new file mode")) current.status = "added";
301
+ else if (line.startsWith("deleted file mode")) current.status = "deleted";
302
+ else if (line.startsWith("rename from ")) renameFrom = unquote(line.slice("rename from ".length));
303
+ else if (line.startsWith("rename to ")) renameTo = unquote(line.slice("rename to ".length));
304
+ else if (line.startsWith("similarity index ")) similar = true;
305
+ else if (line.startsWith("copy from ") || line.startsWith("copy to ")) copied = true;
306
+ else if (line.startsWith("Binary files ") || line === "GIT binary patch") current.binary = true;
307
+ }
308
+ i++;
309
+ }
310
+ close();
311
+ return files;
312
+ }
313
+ function readHunk(lines, at, header, file) {
314
+ const hunk = {
315
+ oldStart: Number(header[1]),
316
+ oldCount: header[2] === void 0 ? 1 : Number(header[2]),
317
+ newStart: Number(header[3]),
318
+ newCount: header[4] === void 0 ? 1 : Number(header[4]),
319
+ lines: []
320
+ };
321
+ const section = header[5]?.trim();
322
+ if (section) hunk.section = section;
323
+ let oldLeft = hunk.oldCount;
324
+ let newLeft = hunk.newCount;
325
+ let oldLine = hunk.oldCount === 0 ? hunk.oldStart + 1 : hunk.oldStart;
326
+ let newLine = hunk.newCount === 0 ? hunk.newStart + 1 : hunk.newStart;
327
+ let i = at + 1;
328
+ for (; i < lines.length; i++) {
329
+ const raw = lines[i];
330
+ if (raw.startsWith(NO_NEWLINE)) {
331
+ const last = hunk.lines[hunk.lines.length - 1];
332
+ if (last) last.noNewline = true;
333
+ continue;
334
+ }
335
+ if (oldLeft <= 0 && newLeft <= 0) break;
336
+ const sign = raw[0];
337
+ let line;
338
+ if (sign === "-" && oldLeft > 0) {
339
+ line = { kind: "del", text: raw.slice(1), oldLine: oldLine++ };
340
+ oldLeft--;
341
+ } else if (sign === "+" && newLeft > 0) {
342
+ line = { kind: "add", text: raw.slice(1), newLine: newLine++ };
343
+ newLeft--;
344
+ } else if ((sign === " " || raw === "") && oldLeft > 0 && newLeft > 0) {
345
+ line = { kind: "context", text: raw.slice(1), oldLine: oldLine++, newLine: newLine++ };
346
+ oldLeft--;
347
+ newLeft--;
348
+ } else break;
349
+ hunk.lines.push(line);
350
+ }
351
+ file.hunks.push(hunk);
352
+ return i;
353
+ }
354
+ function headerPath(value) {
355
+ let path = value.includes(" ") ? value.slice(0, value.indexOf(" ")) : value;
356
+ path = unquote(path.trimEnd());
357
+ return path === "/dev/null" ? null : path;
358
+ }
359
+ function stripSide(path) {
360
+ return /^[ab]\//.test(path) ? path.slice(2) : path;
361
+ }
362
+ function gitPaths(rest) {
363
+ if (rest.startsWith('"')) {
364
+ const first = quotedEnd(rest);
365
+ const a = unquote(rest.slice(0, first));
366
+ const b = unquote(rest.slice(first).trim());
367
+ return [stripSide(a), stripSide(b)];
368
+ }
369
+ const half = (rest.length - 1) / 2;
370
+ if (Number.isInteger(half) && rest[half] === " " && rest.slice(2, half) === rest.slice(half + 3)) {
371
+ return [stripSide(rest.slice(0, half)), stripSide(rest.slice(half + 1))];
372
+ }
373
+ const at = rest.lastIndexOf(" b/");
374
+ if (at > 0) return [stripSide(rest.slice(0, at)), stripSide(rest.slice(at + 1))];
375
+ return null;
376
+ }
377
+ function quotedEnd(text) {
378
+ for (let i = 1; i < text.length; i++) {
379
+ if (text[i] === "\\") i++;
380
+ else if (text[i] === '"') return i + 1;
381
+ }
382
+ return text.length;
383
+ }
384
+ function unquote(value) {
385
+ if (value.length < 2 || !value.startsWith('"') || !value.endsWith('"')) return value;
386
+ const body = value.slice(1, -1);
387
+ const bytes = [];
388
+ const named = { n: 10, t: 9, r: 13, '"': 34, "\\": 92, a: 7, b: 8, f: 12, v: 11 };
389
+ for (let i = 0; i < body.length; i++) {
390
+ const ch = body[i];
391
+ if (ch !== "\\") {
392
+ for (const byte of new TextEncoder().encode(ch)) bytes.push(byte);
393
+ continue;
394
+ }
395
+ const next = body[i + 1] ?? "";
396
+ const octal = /^[0-7]{3}/.exec(body.slice(i + 1));
397
+ if (octal) {
398
+ bytes.push(parseInt(octal[0], 8));
399
+ i += 3;
400
+ } else {
401
+ bytes.push(named[next] ?? next.charCodeAt(0));
402
+ i += 1;
403
+ }
404
+ }
405
+ return new TextDecoder().decode(new Uint8Array(bytes));
406
+ }
407
+ function quote(path) {
408
+ if (!/["\\\u0000-\u001f\u007f]/.test(path)) return path;
409
+ return `"${path.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t")}"`;
410
+ }
411
+ function range(start, count) {
412
+ return count === 1 ? `${start}` : `${start},${count}`;
413
+ }
414
+ function formatUnifiedDiff(files) {
415
+ const out = [];
416
+ for (const file of Array.isArray(files) ? files : [files]) {
417
+ const status = file.status ?? (file.oldPath && file.oldPath !== file.path ? "renamed" : "modified");
418
+ const oldPath = file.oldPath ?? file.path;
419
+ out.push(`diff --git ${quote(`a/${oldPath}`)} ${quote(`b/${file.path}`)}`);
420
+ if (status === "added") out.push(`new file mode ${FILE_MODE}`);
421
+ else if (status === "deleted") out.push(`deleted file mode ${FILE_MODE}`);
422
+ else if (status === "renamed") out.push(`rename from ${quote(oldPath)}`, `rename to ${quote(file.path)}`);
423
+ if (file.binary) {
424
+ out.push(`Binary files ${status === "added" ? "/dev/null" : `a/${oldPath}`} and ${status === "deleted" ? "/dev/null" : `b/${file.path}`} differ`);
425
+ continue;
426
+ }
427
+ if (file.hunks.length === 0) continue;
428
+ out.push(`--- ${status === "added" ? "/dev/null" : quote(`a/${oldPath}`)}`);
429
+ out.push(`+++ ${status === "deleted" ? "/dev/null" : quote(`b/${file.path}`)}`);
430
+ for (const hunk of file.hunks) {
431
+ out.push(`@@ -${range(hunk.oldStart, hunk.oldCount)} +${range(hunk.newStart, hunk.newCount)} @@${hunk.section ? ` ${hunk.section}` : ""}`);
432
+ for (const line of hunk.lines) {
433
+ out.push(`${line.kind === "add" ? "+" : line.kind === "del" ? "-" : " "}${line.text}`);
434
+ if (line.noNewline) out.push("\");
435
+ }
436
+ }
437
+ }
438
+ return out.length ? `${out.join("\n")}
439
+ ` : "";
440
+ }
441
+ function parseResult(segment) {
442
+ if (segment.structuredResult !== void 0) return segment.structuredResult;
443
+ const text = segment.result;
444
+ if (typeof text !== "string") return void 0;
445
+ try {
446
+ return JSON.parse(text);
447
+ } catch {
448
+ return text;
449
+ }
450
+ }
451
+ function fingerprint(value) {
452
+ try {
453
+ return JSON.stringify(value) ?? "";
454
+ } catch {
455
+ return "";
456
+ }
457
+ }
458
+ const isSide = (side) => typeof side === "string" || side === null;
459
+ function drawable(value) {
460
+ const texts = (item) => {
461
+ const { before, after } = item;
462
+ return isSide(before) && isSide(after) && (before !== null || after !== null);
463
+ };
464
+ const one = (item) => typeof item === "object" && item !== null && (texts(item) || Array.isArray(item.hunks));
465
+ if (typeof value === "string") return parseUnifiedDiff(value).length > 0;
466
+ if (Array.isArray(value)) return value.length > 0 && value.every(one);
467
+ return one(value);
468
+ }
469
+ function setupDiff(options, config = aparteGlobalConfig) {
470
+ const matchers = Array.isArray(options.tool) ? options.tool : [options.tool];
471
+ const drawnFrom = /* @__PURE__ */ new WeakMap();
472
+ let warned = false;
473
+ const details = (segment, held) => {
474
+ if (segment.status !== "resolved" || typeof document === "undefined") return "";
475
+ const raw = parseResult(segment);
476
+ const from = fingerprint(raw);
477
+ if (held && from && drawnFrom.get(held) === from) return held;
478
+ let input;
479
+ try {
480
+ input = options.map ? options.map(raw, segment.toolCall?.input ?? {}, segment) : raw;
481
+ } catch (error) {
482
+ console.warn("[aparte-diff] the map of setupDiff threw:", error);
483
+ return "";
484
+ }
485
+ if (input === null || input === void 0) return "";
486
+ if (!drawable(input)) {
487
+ if (!warned) {
488
+ warned = true;
489
+ console.warn(`[aparte-diff] the result of "${segment.toolCall?.name}" is not a diff it can show — check the map of setupDiff.`);
490
+ }
491
+ return "";
492
+ }
493
+ const element = document.createElement("aparte-diff");
494
+ element.diff = input;
495
+ if (options.commentable) element.setAttribute("commentable", "");
496
+ drawnFrom.set(element, from);
497
+ return element;
498
+ };
499
+ const own = { details, open: options.open ?? true };
500
+ const restores = matchers.map((match) => {
501
+ const before = typeof match === "string" ? config.getToolDisplay(match) : void 0;
502
+ config.registerToolDisplay(match, { ...before, ...own });
503
+ return () => {
504
+ if (typeof match === "string" && config.getToolDisplay(match)?.details !== details) return;
505
+ if (before) config.registerToolDisplay(match, { details: before.details, open: before.open });
506
+ else config.unregisterToolDisplay(match);
507
+ };
508
+ });
509
+ let stopped = false;
510
+ return () => {
511
+ if (stopped) return;
512
+ stopped = true;
513
+ for (const restore of restores) restore();
514
+ };
515
+ }
516
+ export {
517
+ splitLines as a,
518
+ diffTexts as d,
519
+ formatUnifiedDiff as f,
520
+ parseUnifiedDiff as p,
521
+ setupDiff as s
522
+ };
523
+ //# sourceMappingURL=setup-CQYdbRuR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup-CQYdbRuR.js","sources":["../src/compute.ts","../src/parse.ts","../src/setup.ts"],"sourcesContent":["/**\n * The line diff, written here: Myers' O(ND) algorithm (\"An O(ND) Difference Algorithm and\n * Its Variations\", 1986), the one `git diff` and GNU diff start from. It finds the fewest\n * added and removed lines that turn one text into the other, in time proportional to the\n * length of the texts times the number of changes — small for an agent's edit.\n *\n * Three steps keep a large file fast, each said where it bites:\n * - the lines the two texts share at their start and at their end are taken off first, so\n * the search only runs over the part that changed;\n * - a part longer than `ANCHOR_FROM` lines is first cut at the lines that appear once in\n * each text, in the same order (the longest such run: \"patience\" anchoring, as\n * `git diff --patience` does), and each gap between two of them is diffed on its own. A\n * large file changed all through still gives one small hunk per change, in little time;\n * - in a gap, past `MAX_COST` changes (two texts with little in common there) the search\n * stops, and that gap alone is shown as removed then added: still a true account of the\n * change, no longer the shortest one. The memory the search keeps grows with the square\n * of the changes, which is what the bound is for.\n *\n * No DOM anywhere: `diffTexts` runs on a server as well.\n */\nimport type { AparteDiffFile, AparteDiffHunk, AparteDiffLine } from './types.js';\n\n/** One step of the edit: a line kept, removed from the old text, or added from the new one. */\nexport type Edit = 'eq' | 'del' | 'add';\n\n/** The changes the search follows, in one gap, before it shows the rest of the gap as replaced. */\nconst MAX_COST = 2000;\n\n/**\n * Past this many lines (both sides), a part is cut at its anchors before it is searched. Below\n * it the search is cheap and always finishes (it cannot need more than `MAX_COST` changes),\n * so the diff is the shortest.\n */\nconst ANCHOR_FROM = MAX_COST;\n\n/** The lines unchanged around a change, as `git diff` keeps them. */\nconst DEFAULT_CONTEXT = 3;\n\n/** A part of the two texts still to diff, or (a number) that many unchanged lines to write. */\ntype Task = { a0: number; a1: number; b0: number; b1: number } | number;\n\n/**\n * The edit that turns `a` into `b`, one step per line, removals before additions inside a\n * run of changes.\n */\nexport function diffLines(a: readonly string[], b: readonly string[]): Edit[] {\n const edits: Edit[] = [];\n // A stack rather than recursion: a long file cut at its anchors, again and again, must not\n // run out of call stack. The parts are taken in order, so the edits are written in order.\n const tasks: Task[] = [{ a0: 0, a1: a.length, b0: 0, b1: b.length }];\n while (tasks.length) {\n const task = tasks.pop()!;\n if (typeof task === 'number') {\n for (let k = 0; k < task; k++) edits.push('eq');\n continue;\n }\n let { a0, a1, b0, b1 } = task;\n while (a0 < a1 && b0 < b1 && a[a0] === b[b0]) {\n edits.push('eq');\n a0++;\n b0++;\n }\n let shared = 0;\n while (a1 > a0 && b1 > b0 && a[a1 - 1] === b[b1 - 1]) {\n a1--;\n b1--;\n shared++;\n }\n if (shared) tasks.push(shared);\n if (a1 - a0 + (b1 - b0) > ANCHOR_FROM) {\n const anchors = anchorsOf(a, b, a0, a1, b0, b1);\n if (anchors.length) {\n // Pushed last part first, so the first comes off first.\n const parts: Task[] = [];\n let pa = a0;\n let pb = b0;\n for (const [i, j] of anchors) {\n parts.push({ a0: pa, a1: i, b0: pb, b1: j }, 1);\n pa = i + 1;\n pb = j + 1;\n }\n parts.push({ a0: pa, a1, b0: pb, b1 });\n for (let k = parts.length - 1; k >= 0; k--) tasks.push(parts[k]!);\n continue;\n }\n }\n for (const edit of middle(a, b, a0, a1, b0, b1)) edits.push(edit);\n }\n return removalsFirst(edits);\n}\n\n/**\n * The lines that appear exactly once in `a[a0..a1)` and once in `b[b0..b1)`, as pairs of\n * their positions — the longest run of them in the same order on both sides (a longest\n * increasing subsequence, by patience sorting). Lines unique on both sides are almost always\n * the same line, unmoved: the safe places to cut a large diff.\n */\nfunction anchorsOf(a: readonly string[], b: readonly string[], a0: number, a1: number, b0: number, b1: number): Array<[number, number]> {\n // Per line: its count in `a`, its count in `b`, and where it is in `b`.\n const seen = new Map<string, [number, number, number]>();\n for (let i = a0; i < a1; i++) {\n const entry = seen.get(a[i]!);\n if (entry) entry[0]++;\n else seen.set(a[i]!, [1, 0, -1]);\n }\n for (let j = b0; j < b1; j++) {\n const entry = seen.get(b[j]!);\n if (!entry) continue;\n entry[1]++;\n entry[2] = j;\n }\n const pairs: Array<[number, number]> = [];\n for (let i = a0; i < a1; i++) {\n const entry = seen.get(a[i]!)!;\n if (entry[0] === 1 && entry[1] === 1) pairs.push([i, entry[2]]);\n }\n if (!pairs.length) return pairs;\n // Patience sorting over the `b` positions: `tails[k]` ends the best run of length k + 1.\n const tails: number[] = [];\n const before = new Int32Array(pairs.length).fill(-1);\n for (let p = 0; p < pairs.length; p++) {\n const j = pairs[p]![1];\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (pairs[tails[mid]!]![1] < j) lo = mid + 1;\n else hi = mid;\n }\n if (lo > 0) before[p] = tails[lo - 1]!;\n tails[lo] = p;\n }\n const run: Array<[number, number]> = [];\n for (let p = tails[tails.length - 1]!; p >= 0; p = before[p]!) run.push(pairs[p]!);\n return run.reverse();\n}\n\n/** Myers' forward search over `a[a0..a1)` and `b[b0..b1)`, and the walk back along its trace. */\nfunction middle(a: readonly string[], b: readonly string[], a0: number, a1: number, b0: number, b1: number): Edit[] {\n const n = a1 - a0;\n const m = b1 - b0;\n if (n === 0) return new Array<Edit>(m).fill('add');\n if (m === 0) return new Array<Edit>(n).fill('del');\n const max = n + m;\n const offset = max + 1;\n // v[k + offset]: how far along `a` the furthest path on diagonal k reached.\n const v = new Int32Array(2 * max + 3);\n // Each step's v, over the diagonals it can read: -(d + 1)..(d + 1).\n const trace: Int32Array[] = [];\n let reached = -1;\n for (let d = 0; d <= Math.min(max, MAX_COST); d++) {\n trace.push(v.slice(offset - d - 1, offset + d + 2));\n for (let k = -d; k <= d; k += 2) {\n let x = k === -d || (k !== d && v[offset + k - 1]! < v[offset + k + 1]!) ? v[offset + k + 1]! : v[offset + k - 1]! + 1;\n let y = x - k;\n while (x < n && y < m && a[a0 + x] === b[b0 + y]) {\n x++;\n y++;\n }\n v[offset + k] = x;\n if (x >= n && y >= m) {\n reached = d;\n break;\n }\n }\n if (reached >= 0) break;\n }\n // Too many changes to search: the whole middle, replaced.\n if (reached < 0) return [...new Array<Edit>(n).fill('del'), ...new Array<Edit>(m).fill('add')];\n\n const out: Edit[] = [];\n let x = n;\n let y = m;\n for (let d = reached; d >= 0; d--) {\n const snapshot = trace[d]!;\n const at = (k: number): number => snapshot[k + d + 1]!;\n const k = x - y;\n const down = k === -d || (k !== d && at(k - 1) < at(k + 1));\n const prevK = down ? k + 1 : k - 1;\n const prevX = at(prevK);\n const prevY = prevX - prevK;\n while (x > prevX && y > prevY) {\n out.push('eq');\n x--;\n y--;\n }\n if (d > 0) out.push(down ? 'add' : 'del');\n x = prevX;\n y = prevY;\n }\n return out.reverse();\n}\n\n/** Inside each run of changes, the removals first: how `git diff` shows a replaced block. */\nfunction removalsFirst(edits: Edit[]): Edit[] {\n const out: Edit[] = [];\n let dels = 0;\n let adds = 0;\n const flush = () => {\n for (; dels > 0; dels--) out.push('del');\n for (; adds > 0; adds--) out.push('add');\n };\n for (const edit of edits) {\n if (edit === 'del') dels++;\n else if (edit === 'add') adds++;\n else {\n flush();\n out.push('eq');\n }\n }\n flush();\n return out;\n}\n\n/** A text's lines, without their endings, and whether its last line ends with one. */\nexport function splitLines(text: string): { lines: string[]; endsWithNewline: boolean } {\n if (text === '') return { lines: [], endsWithNewline: true };\n const lines = text.split(/\\r?\\n/);\n const endsWithNewline = lines[lines.length - 1] === '';\n if (endsWithNewline) lines.pop();\n return { lines, endsWithNewline };\n}\n\n/** What `diffTexts` is told besides the two texts. */\nexport interface DiffTextsOptions {\n /** Unchanged lines kept on each side of a change (3, as `git diff`). */\n context?: number;\n /** The file's path, as the diff names it. */\n path?: string;\n /** Its path before, when it was renamed. Not read for a file created or deleted (a `null` side). */\n oldPath?: string;\n}\n\n/**\n * The diff of two texts, as one file's hunks with their line numbers. Lines are compared\n * without their endings, so a file saved with Windows line endings reads the same; a last\n * line that gains or loses its newline is shown changed, marked `noNewline`.\n *\n * A side that does not exist is `null`: `before: null` is a file created (`added`),\n * `after: null` a file deleted (`deleted`). `''` is a file that exists and is empty, so\n * `diffTexts('', text)` is that file filled in (`modified`). `formatUnifiedDiff` writes each\n * as git does, so the patch applies.\n *\n * @example\n * const file = diffTexts(before, after, { path: 'src/parse.ts' });\n * file.added; // 1\n * file.hunks; // [{ oldStart: 12, oldCount: 7, newStart: 12, newCount: 7, lines: [...] }]\n * diffTexts(null, 'hello\\n', { path: 'notes.md' }).status; // 'added'\n */\nexport function diffTexts(before: string | null, after: string | null, options: DiffTextsOptions = {}): AparteDiffFile {\n const context = Math.max(0, Math.floor(options.context ?? DEFAULT_CONTEXT));\n const old = splitLines(before ?? '');\n const next = splitLines(after ?? '');\n // A last line without its newline is not the same line as one with it: the key says so.\n const keys = (side: typeof old) => side.lines.map((line, i) => (i === side.lines.length - 1 && !side.endsWithNewline ? `${line}\\u0000` : line));\n const edits = diffLines(keys(old), keys(next));\n\n const lines: AparteDiffLine[] = [];\n let o = 0;\n let n = 0;\n for (const edit of edits) {\n const line: AparteDiffLine = edit === 'eq'\n ? { kind: 'context', text: next.lines[n]!, oldLine: o + 1, newLine: n + 1 }\n : edit === 'del'\n ? { kind: 'del', text: old.lines[o]!, oldLine: o + 1 }\n : { kind: 'add', text: next.lines[n]!, newLine: n + 1 };\n const lastOld = edit !== 'add' && o === old.lines.length - 1 && !old.endsWithNewline;\n const lastNew = edit !== 'del' && n === next.lines.length - 1 && !next.endsWithNewline;\n if (lastOld || lastNew) line.noNewline = true;\n if (edit !== 'add') o++;\n if (edit !== 'del') n++;\n lines.push(line);\n }\n\n const file: AparteDiffFile = {\n path: options.path ?? '',\n status: 'modified',\n added: lines.filter((l) => l.kind === 'add').length,\n removed: lines.filter((l) => l.kind === 'del').length,\n hunks: hunksOf(lines, context),\n };\n // A file created or deleted has one path: there is nothing to have been renamed.\n if (before === null && after !== null) file.status = 'added';\n else if (after === null && before !== null) file.status = 'deleted';\n else if (options.oldPath !== undefined && options.oldPath !== file.path) {\n file.oldPath = options.oldPath;\n file.status = 'renamed';\n }\n return file;\n}\n\n/** The changed lines with `context` unchanged ones around them, two hunks joined when their context touches. */\nexport function hunksOf(lines: readonly AparteDiffLine[], context: number): AparteDiffHunk[] {\n const changed: number[] = [];\n lines.forEach((line, i) => {\n if (line.kind !== 'context') changed.push(i);\n });\n const hunks: AparteDiffHunk[] = [];\n let i = 0;\n while (i < changed.length) {\n let last = changed[i]!;\n let j = i + 1;\n // Joined while the unchanged run between two changes is no longer than both contexts.\n while (j < changed.length && changed[j]! - last - 1 <= 2 * context) last = changed[j++]!;\n const from = Math.max(0, changed[i]! - context);\n const to = Math.min(lines.length - 1, last + context);\n hunks.push(hunkOf(lines, from, to));\n i = j;\n }\n return hunks;\n}\n\n/** `lines[from..to]` as a hunk, its header counted from the lines' own numbers. */\nfunction hunkOf(lines: readonly AparteDiffLine[], from: number, to: number): AparteDiffHunk {\n const own = lines.slice(from, to + 1);\n const oldCount = own.filter((l) => l.kind !== 'add').length;\n const newCount = own.filter((l) => l.kind !== 'del').length;\n // The lines before the hunk on each side: its start when it is empty there, else one more.\n let oldBefore = 0;\n let newBefore = 0;\n for (let k = from - 1; k >= 0; k--) {\n const line = lines[k]!;\n if (oldBefore === 0 && line.oldLine !== undefined) oldBefore = line.oldLine;\n if (newBefore === 0 && line.newLine !== undefined) newBefore = line.newLine;\n if (oldBefore && newBefore) break;\n }\n return {\n oldStart: oldCount > 0 ? oldBefore + 1 : oldBefore,\n oldCount,\n newStart: newCount > 0 ? newBefore + 1 : newBefore,\n newCount,\n lines: own.map((line) => ({ ...line })),\n };\n}\n","/**\n * `parseUnifiedDiff` and `formatUnifiedDiff`: the text a tool prints, read into files and\n * written back. Pure string work, so both run on a server.\n *\n * The reader follows `git diff` and `diff -u`: a file starts at `diff --git` or at a `---`\n * line followed by a `+++` one; its extended headers (`new file mode`, `deleted file mode`,\n * `rename from` / `rename to`, `similarity index`, `Binary files … differ`) say what happened\n * to it — a rename only when git says so, so `diff -u x.c.orig x.c` is a change to `x.c`; a hunk is\n * read for exactly as many lines as its `@@` header counts, so a removed line that itself\n * starts with `--` is a line, not the next file. What is around the diff — a commit message,\n * an email's signature — is skipped.\n */\nimport type { AparteDiffFile, AparteDiffHunk, AparteDiffLine } from './types.js';\n\nconst HUNK = /^@@ -(\\d+)(?:,(\\d+))? \\+(\\d+)(?:,(\\d+))? @@ ?(.*)$/;\nconst NO_NEWLINE = '\\\\';\n/** The mode a created or deleted file is written with: a regular file, as git gives most. */\nconst FILE_MODE = '100644';\n\n/**\n * The files of a unified diff, in the order it lists them. A text that holds no diff gives\n * an empty list. Windows line endings read the same as Unix ones.\n *\n * @example\n * const files = parseUnifiedDiff(gitDiffOutput);\n * files.map((f) => `${f.path} +${f.added} −${f.removed}`);\n */\nexport function parseUnifiedDiff(text: string): AparteDiffFile[] {\n const lines = text.split(/\\r?\\n/);\n const files: AparteDiffFile[] = [];\n let file: AparteDiffFile | null = null;\n // A file's paths as its headers say them, settled when it ends.\n let fromGit: [string, string] | null = null;\n let renameFrom: string | null = null;\n let renameTo: string | null = null;\n // Its `---` and `+++` paths as written, prefixes kept: null for `/dev/null`.\n let minus: string | null = null;\n let plus: string | null = null;\n // Whether the file's `---` / `+++` pair was read: `minus` is null for `/dev/null` too.\n let headers = false;\n // git said the two paths are one file moved (`similarity index`), or a copy of it.\n let similar = false;\n let copied = false;\n\n const close = () => {\n if (!file) return;\n // `a/` and `b/` are git's prefixes: taken off under its `diff --git` line, or when the\n // pair wears both. Plain `diff -u old/x new/x` names its trees, which stay.\n const prefixed = fromGit !== null || ((minus === null || minus.startsWith('a/')) && (plus === null || plus.startsWith('b/')));\n const side = (path: string | null): string | null => (path !== null && prefixed ? stripSide(path) : path);\n const oldPath = renameFrom ?? (minus === null ? fromGit?.[0] : side(minus));\n const newPath = renameTo ?? (plus === null ? fromGit?.[1] : side(plus));\n if (file.status !== 'deleted') file.path = newPath ?? oldPath ?? '';\n else file.path = oldPath ?? newPath ?? '';\n // A rename is git's word for it, never two paths that differ: `diff -u x.c.orig x.c`\n // is a change to x.c.\n const renamed = renameFrom !== null || renameTo !== null || (similar && !copied && fromGit !== null && fromGit[0] !== fromGit[1]);\n if (file.status === 'modified' && renamed && oldPath && newPath && oldPath !== newPath) {\n file.status = 'renamed';\n file.oldPath = oldPath;\n }\n file.added = file.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.kind === 'add').length, 0);\n file.removed = file.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.kind === 'del').length, 0);\n files.push(file);\n file = null;\n fromGit = null;\n renameFrom = renameTo = minus = plus = null;\n headers = similar = copied = false;\n };\n const open = () => {\n close();\n file = { path: '', status: 'modified', added: 0, removed: 0, hunks: [] };\n return file;\n };\n\n let i = 0;\n while (i < lines.length) {\n const line = lines[i]!;\n if (line.startsWith('diff --git ')) {\n open();\n fromGit = gitPaths(line.slice('diff --git '.length));\n i++;\n continue;\n }\n if (line.startsWith('--- ') && lines[i + 1]?.startsWith('+++ ')) {\n // A `---` that is not under its own `diff --git` (or comes after a hunk) starts a file.\n const current = file as AparteDiffFile | null;\n if (!current || current.hunks.length > 0 || headers) open();\n const target = file as unknown as AparteDiffFile;\n minus = headerPath(line.slice(4));\n plus = headerPath(lines[i + 1]!.slice(4));\n headers = true;\n if (minus === null) target.status = 'added';\n if (plus === null) target.status = 'deleted';\n i += 2;\n continue;\n }\n const current = file as AparteDiffFile | null;\n if (current) {\n const hunk = HUNK.exec(line);\n if (hunk) {\n i = readHunk(lines, i, hunk, current);\n continue;\n }\n if (line.startsWith('new file mode')) current.status = 'added';\n else if (line.startsWith('deleted file mode')) current.status = 'deleted';\n else if (line.startsWith('rename from ')) renameFrom = unquote(line.slice('rename from '.length));\n else if (line.startsWith('rename to ')) renameTo = unquote(line.slice('rename to '.length));\n else if (line.startsWith('similarity index ')) similar = true;\n else if (line.startsWith('copy from ') || line.startsWith('copy to ')) copied = true;\n else if (line.startsWith('Binary files ') || line === 'GIT binary patch') current.binary = true;\n }\n i++;\n }\n close();\n return files;\n}\n\n/** Read the hunk whose header is on line `at`, for as many lines as it counts. Returns the next line to read. */\nfunction readHunk(lines: readonly string[], at: number, header: RegExpExecArray, file: AparteDiffFile): number {\n const hunk: AparteDiffHunk = {\n oldStart: Number(header[1]),\n oldCount: header[2] === undefined ? 1 : Number(header[2]),\n newStart: Number(header[3]),\n newCount: header[4] === undefined ? 1 : Number(header[4]),\n lines: [],\n };\n const section = header[5]?.trim();\n if (section) hunk.section = section;\n let oldLeft = hunk.oldCount;\n let newLeft = hunk.newCount;\n let oldLine = hunk.oldCount === 0 ? hunk.oldStart + 1 : hunk.oldStart;\n let newLine = hunk.newCount === 0 ? hunk.newStart + 1 : hunk.newStart;\n let i = at + 1;\n for (; i < lines.length; i++) {\n const raw = lines[i]!;\n // The marker belongs to the line before it, even once the counts are spent.\n if (raw.startsWith(NO_NEWLINE)) {\n const last = hunk.lines[hunk.lines.length - 1];\n if (last) last.noNewline = true;\n continue;\n }\n if (oldLeft <= 0 && newLeft <= 0) break;\n const sign = raw[0];\n let line: AparteDiffLine;\n if (sign === '-' && oldLeft > 0) {\n line = { kind: 'del', text: raw.slice(1), oldLine: oldLine++ };\n oldLeft--;\n } else if (sign === '+' && newLeft > 0) {\n line = { kind: 'add', text: raw.slice(1), newLine: newLine++ };\n newLeft--;\n } else if ((sign === ' ' || raw === '') && oldLeft > 0 && newLeft > 0) {\n // An empty line is an unchanged empty line whose space a tool trimmed.\n line = { kind: 'context', text: raw.slice(1), oldLine: oldLine++, newLine: newLine++ };\n oldLeft--;\n newLeft--;\n } else break;\n hunk.lines.push(line);\n }\n file.hunks.push(hunk);\n return i;\n}\n\n/** A `---` / `+++` path: without its date and its quotes, its `a/` or `b/` still on; `null` for `/dev/null`. */\nfunction headerPath(value: string): string | null {\n let path = value.includes('\\t') ? value.slice(0, value.indexOf('\\t')) : value;\n path = unquote(path.trimEnd());\n return path === '/dev/null' ? null : path;\n}\n\nfunction stripSide(path: string): string {\n return /^[ab]\\//.test(path) ? path.slice(2) : path;\n}\n\n/** The two paths of a `diff --git a/x b/y` line. */\nfunction gitPaths(rest: string): [string, string] | null {\n if (rest.startsWith('\"')) {\n const first = quotedEnd(rest);\n const a = unquote(rest.slice(0, first));\n const b = unquote(rest.slice(first).trim());\n return [stripSide(a), stripSide(b)];\n }\n // Unquoted: a path with a space is ambiguous, so the split that gives the same path twice wins.\n const half = (rest.length - 1) / 2;\n if (Number.isInteger(half) && rest[half] === ' ' && rest.slice(2, half) === rest.slice(half + 3)) {\n return [stripSide(rest.slice(0, half)), stripSide(rest.slice(half + 1))];\n }\n const at = rest.lastIndexOf(' b/');\n if (at > 0) return [stripSide(rest.slice(0, at)), stripSide(rest.slice(at + 1))];\n return null;\n}\n\n/** Where the quoted token at the start of `text` ends. */\nfunction quotedEnd(text: string): number {\n for (let i = 1; i < text.length; i++) {\n if (text[i] === '\\\\') i++;\n else if (text[i] === '\"') return i + 1;\n }\n return text.length;\n}\n\n/** A path git quoted (C escapes, octal bytes of UTF-8), as it reads. Unquoted paths are returned as they are. */\nfunction unquote(value: string): string {\n if (value.length < 2 || !value.startsWith('\"') || !value.endsWith('\"')) return value;\n const body = value.slice(1, -1);\n const bytes: number[] = [];\n const named: Record<string, number> = { n: 10, t: 9, r: 13, '\"': 34, '\\\\': 92, a: 7, b: 8, f: 12, v: 11 };\n for (let i = 0; i < body.length; i++) {\n const ch = body[i]!;\n if (ch !== '\\\\') {\n for (const byte of new TextEncoder().encode(ch)) bytes.push(byte);\n continue;\n }\n const next = body[i + 1] ?? '';\n const octal = /^[0-7]{3}/.exec(body.slice(i + 1));\n if (octal) {\n bytes.push(parseInt(octal[0], 8));\n i += 3;\n } else {\n bytes.push(named[next] ?? next.charCodeAt(0));\n i += 1;\n }\n }\n return new TextDecoder().decode(new Uint8Array(bytes));\n}\n\n/** A path as git writes it in a header: quoted when it holds a quote, a backslash or a control character. */\nfunction quote(path: string): string {\n // eslint-disable-next-line no-control-regex\n if (!/[\"\\\\\\u0000-\\u001f\\u007f]/.test(path)) return path;\n return `\"${path.replace(/\\\\/g, '\\\\\\\\').replace(/\"/g, '\\\\\"').replace(/\\n/g, '\\\\n').replace(/\\t/g, '\\\\t')}\"`;\n}\n\n/** `-12,7` — the count left out when it is 1, as git writes it. */\nfunction range(start: number, count: number): string {\n return count === 1 ? `${start}` : `${start},${count}`;\n}\n\n/**\n * Files as a unified diff, the way `git diff` writes it: what `parseUnifiedDiff` reads back,\n * and what `git apply` takes. With `diffTexts`, a server can send a patch as text. A file\n * created or deleted carries git's `new file mode` / `deleted file mode` header (as a regular\n * file, `100644`), which `git apply` needs to create one; a rename carries `rename from` and\n * `rename to`.\n *\n * @example\n * formatUnifiedDiff(diffTexts(before, after, { path: 'src/parse.ts' }));\n * // diff --git a/src/parse.ts b/src/parse.ts\n * // --- a/src/parse.ts\n * // +++ b/src/parse.ts\n * // @@ -12,7 +12,7 @@\n * // …\n */\nexport function formatUnifiedDiff(files: AparteDiffFile | readonly AparteDiffFile[]): string {\n const out: string[] = [];\n for (const file of Array.isArray(files) ? files : [files as AparteDiffFile]) {\n const status = file.status ?? (file.oldPath && file.oldPath !== file.path ? 'renamed' : 'modified');\n const oldPath = file.oldPath ?? file.path;\n out.push(`diff --git ${quote(`a/${oldPath}`)} ${quote(`b/${file.path}`)}`);\n // Without it, git reads `--- /dev/null` as a file named `dev/null` and refuses the patch.\n if (status === 'added') out.push(`new file mode ${FILE_MODE}`);\n else if (status === 'deleted') out.push(`deleted file mode ${FILE_MODE}`);\n else if (status === 'renamed') out.push(`rename from ${quote(oldPath)}`, `rename to ${quote(file.path)}`);\n if (file.binary) {\n out.push(`Binary files ${status === 'added' ? '/dev/null' : `a/${oldPath}`} and ${status === 'deleted' ? '/dev/null' : `b/${file.path}`} differ`);\n continue;\n }\n if (file.hunks.length === 0) continue;\n out.push(`--- ${status === 'added' ? '/dev/null' : quote(`a/${oldPath}`)}`);\n out.push(`+++ ${status === 'deleted' ? '/dev/null' : quote(`b/${file.path}`)}`);\n for (const hunk of file.hunks) {\n out.push(`@@ -${range(hunk.oldStart, hunk.oldCount)} +${range(hunk.newStart, hunk.newCount)} @@${hunk.section ? ` ${hunk.section}` : ''}`);\n for (const line of hunk.lines) {\n out.push(`${line.kind === 'add' ? '+' : line.kind === 'del' ? '-' : ' '}${line.text}`);\n if (line.noNewline) out.push('\\\');\n }\n }\n }\n return out.length ? `${out.join('\\n')}\\n` : '';\n}\n","/**\n * `setupDiff()` — a tool's result shown as a diff, under the tool's line in the reply.\n *\n * An edit tool's row keeps everything core gives it: its name, the summary the app set, its\n * states (running, waiting for approval, refused, failed). What it holds behind its line is\n * the change, opened — `<aparte-diff>` in place of the raw Input and Output. Through the\n * display's `details`, so it is the same row in every framework and every state.\n *\n * DOM-free at import: the element is created only when a chat draws a resolved call, in a\n * browser. On a server the setup registers the same display and draws nothing.\n */\nimport { aparteGlobalConfig, type AparteConfig, type AparteToolCallSegment, type AparteToolDisplay, type AparteToolMatcher } from '@aparte/core';\nimport { parseUnifiedDiff } from './parse.js';\nimport type { AparteDiffInput } from './types.js';\n\n/** How an app shows a tool's result as a diff. */\nexport interface DiffSetupOptions {\n /** The tool (or tools) whose result is a change. */\n tool: AparteToolMatcher | AparteToolMatcher[];\n /**\n * The change, from the tool's result — its `structuredContent` when it has one, else its\n * text parsed as JSON, else the text — and the call's input: a unified diff's text,\n * `{ path, before, after }`, or a list of files. Return `null` for a result with no change\n * to show. Without `map`, the result must already be one of those.\n */\n // `any`: the result's shape is your tool's, and `map` is where you read it.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n map?: (result: any, input: Record<string, unknown>, segment: AparteToolCallSegment) => AparteDiffInput | null | undefined;\n /** Let the reader comment on its lines: `aparte-diff-comment` bubbles out of the chat. Off by default. */\n commentable?: boolean;\n /** Show the diff opened under the line (the default), or `false` for closed until the reader opens it. */\n open?: boolean;\n}\n\nfunction parseResult(segment: AparteToolCallSegment): unknown {\n if (segment.structuredResult !== undefined) return segment.structuredResult;\n const text = segment.result;\n if (typeof text !== 'string') return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return text;\n }\n}\n\nfunction fingerprint(value: unknown): string {\n try {\n return JSON.stringify(value) ?? '';\n } catch {\n return '';\n }\n}\n\n/** A side of a file's two texts: a text, or `null` for a file that is not there. */\nconst isSide = (side: unknown): boolean => typeof side === 'string' || side === null;\n\n/** Whether `<aparte-diff>` can draw it: diff text that holds a file, a file's two texts, or a list of those. */\nfunction drawable(value: unknown): value is AparteDiffInput {\n const texts = (item: object): boolean => {\n const { before, after } = item as { before?: unknown; after?: unknown };\n return isSide(before) && isSide(after) && (before !== null || after !== null);\n };\n const one = (item: unknown): boolean => typeof item === 'object' && item !== null\n && (texts(item) || Array.isArray((item as { hunks?: unknown }).hunks));\n if (typeof value === 'string') return parseUnifiedDiff(value).length > 0;\n if (Array.isArray(value)) return value.length > 0 && value.every(one);\n return one(value);\n}\n\n/**\n * Show a tool's result as a diff, under the tool's line in the reply. Returns the function that\n * takes it off again.\n *\n * The rest of the tool's display (its label, its summary, its icon) stays the app's: set it\n * with `registerToolDisplay` before or after. Taking the setup off gives back what the tool\n * showed behind its line before.\n *\n * @example\n * setupDiff({\n * tool: 'edit_file',\n * map: (result, input) => ({ path: String(input.path), before: result.before, after: result.after }),\n * });\n */\nexport function setupDiff(options: DiffSetupOptions, config: AparteConfig = aparteGlobalConfig): () => void {\n const matchers = Array.isArray(options.tool) ? options.tool : [options.tool];\n // The result each diff was drawn from. Weak: a diff lives in its row, and goes with it.\n const drawnFrom = new WeakMap<HTMLElement, string>();\n let warned = false;\n\n const details = (segment: AparteToolCallSegment, held?: HTMLElement): HTMLElement | string => {\n if (segment.status !== 'resolved' || typeof document === 'undefined') return '';\n const raw = parseResult(segment);\n const from = fingerprint(raw);\n // The same result drawn again in its row (an update, a language switch): the diff the\n // row already holds, with what the reader opened and wrote in it. Another row of the\n // same call — a pane beside the reply — draws its own.\n if (held && from && drawnFrom.get(held) === from) return held;\n let input: unknown;\n try {\n input = options.map ? options.map(raw, (segment.toolCall?.input ?? {}) as Record<string, unknown>, segment) : raw;\n } catch (error) {\n console.warn('[aparte-diff] the map of setupDiff threw:', error);\n return '';\n }\n if (input === null || input === undefined) return '';\n if (!drawable(input)) {\n if (!warned) {\n warned = true;\n console.warn(`[aparte-diff] the result of \"${segment.toolCall?.name}\" is not a diff it can show — check the map of setupDiff.`);\n }\n return '';\n }\n const element = document.createElement('aparte-diff') as HTMLElement & { diff: AparteDiffInput | null };\n element.diff = input;\n if (options.commentable) element.setAttribute('commentable', '');\n drawnFrom.set(element, from);\n return element;\n };\n\n const own: Pick<AparteToolDisplay, 'details' | 'open'> = { details, open: options.open ?? true };\n const restores = matchers.map((match) => {\n // What applies to the tool now. A tool with no entry of its own would get one here and\n // lose what a broader entry (`'*'`) said of it, so that is carried into it.\n const before = typeof match === 'string' ? config.getToolDisplay(match) : undefined;\n config.registerToolDisplay(match, { ...before, ...own });\n return () => {\n if (typeof match === 'string' && config.getToolDisplay(match)?.details !== details) return;\n if (before) config.registerToolDisplay(match, { details: before.details, open: before.open });\n else config.unregisterToolDisplay(match);\n };\n });\n\n let stopped = false;\n return () => {\n if (stopped) return;\n stopped = true;\n for (const restore of restores) restore();\n };\n}\n"],"names":["x","y","k","i","current"],"mappings":"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"}