char-slop/berry

multipurpose self-modifiable agentic harness

git clone https://git.t4t.associates/char-slop/berry

Charlotte Somdiff: add (codemirror) inline editing of diffsc5dd844

main
2.2 KiB63 linesraw
1import { diffArrays } from "diff";
2
3type Hunk = { start: number; end: number; lines: string[]; mine: boolean };
4
5/** Base lines `start` to `end` replaced by `lines`, for each run where `next` differs from `base`. */
6function hunks(base: string[], next: string[], mine: boolean): Hunk[] {
7  const found: Hunk[] = [];
8  let at = 0;
9  let current: Hunk | null = null;
10  for (const part of diffArrays(base, next)) {
11    if (!part.added && !part.removed) {
12      if (current) found.push(current);
13      current = null;
14      at += part.count!;
15      continue;
16    }
17    current ??= { start: at, end: at, lines: [], mine };
18    if (part.removed) current.end = at += part.count!;
19    else current.lines.push(...part.value);
20  }
21  if (current) found.push(current);
22  return found;
23}
24
25/**
26 * Both sides' changes to `base` as git merges them: by lines, refusing (with `false`) where they touch or overlap
27 * unless they make the same change.
28 */
29export function merge3(base: string, mine: string, theirs: string): string | false {
30  const lines = (text: string) => text.split(/(?<=\n)/).filter((line) => line !== "");
31  const original = lines(base);
32  const all = [
33    ...hunks(original, lines(mine), true),
34    ...hunks(original, lines(theirs), false),
35  ].sort((a, b) => a.start - b.start || a.end - b.end);
36  const out: string[] = [];
37  let at = 0;
38  for (let i = 0; i < all.length;) {
39    const group = [all[i++]];
40    let end = group[0].end;
41    while (i < all.length && all[i].start <= end) {
42      end = Math.max(end, all[i].end);
43      group.push(all[i++]);
44    }
45    const start = group[0].start;
46    const side = (mine: boolean) => {
47      const result: string[] = [];
48      let from = start;
49      for (const hunk of group.filter((hunk) => hunk.mine === mine)) {
50        result.push(...original.slice(from, hunk.start), ...hunk.lines);
51        from = hunk.end;
52      }
53      return [...result, ...original.slice(from, end)];
54    };
55    const ours = side(true);
56    if (group.some((hunk) => !hunk.mine) && group.some((hunk) => hunk.mine)) {
57      if (ours.join("") !== side(false).join("")) return false;
58    }
59    out.push(...original.slice(at, start), ...(group.some((h) => h.mine) ? ours : side(false)));
60    at = end;
61  }
62  return [...out, ...original.slice(at)].join("");
63}