OXYGENHUB
◆ main oxygen-vscode / src / diffutil.ts
2.7 KiB · last changed Milestone 0.4: history graph webview with per-change actions, file history diffs between versions, inline blame aligned to unsaved edits with a Myers line diff 1 month ago source blame history raw
1
// A line diff (Myers, O(ND)) used to align blame computed on the base
2
// version of a file with the lines of the editor's current text.
3
4
/** For each line of `after`, the index of the matching line in `before`, or -1 when new. */
5
export function alignLines(before: string[], after: string[]): number[] {
6
  const n = before.length;
7
  const m = after.length;
8
  const result = new Array<number>(m).fill(-1);
9
  if (n === 0 || m === 0) {
10
    return result;
11
  }
12
  // Trim common prefix and suffix first; they dominate real edits.
13
  let start = 0;
14
  while (start < n && start < m && before[start] === after[start]) {
15
    result[start] = start;
16
    start++;
17
  }
18
  let endB = n;
19
  let endA = m;
20
  while (endB > start && endA > start && before[endB - 1] === after[endA - 1]) {
21
    endB--;
22
    endA--;
23
    result[endA] = endB;
24
  }
25
  const a = before.slice(start, endB);
26
  const b = after.slice(start, endA);
27
  if (a.length === 0 || b.length === 0) {
28
    return result;
29
  }
30
  // Bail out to positional matching on huge middles to bound memory.
31
  if (a.length * b.length > 25_000_000) {
32
    for (let i = 0; i < Math.min(a.length, b.length); i++) {
33
      if (a[i] === b[i]) {
34
        result[start + i] = start + i;
35
      }
36
    }
37
    return result;
38
  }
39
  for (const [i, j] of myers(a, b)) {
40
    result[start + j] = start + i;
41
  }
42
  return result;
43
}
44
45
/** Pairs (i, j) of equal lines a[i] === b[j] along a shortest edit script. */
46
function myers(a: string[], b: string[]): [number, number][] {
47
  const n = a.length;
48
  const m = b.length;
49
  const max = n + m;
50
  const offset = max;
51
  const trace: Int32Array[] = [];
52
  let v = new Int32Array(2 * max + 2);
53
  v[offset + 1] = 0;
54
  outer: for (let d = 0; d <= max; d++) {
55
    const snapshot = v.slice();
56
    trace.push(snapshot);
57
    for (let k = -d; k <= d; k += 2) {
58
      let x: number;
59
      if (k === -d || (k !== d && v[offset + k - 1] < v[offset + k + 1])) {
60
        x = v[offset + k + 1];
61
      } else {
62
        x = v[offset + k - 1] + 1;
63
      }
64
      let y = x - k;
65
      while (x < n && y < m && a[x] === b[y]) {
66
        x++;
67
        y++;
68
      }
69
      v[offset + k] = x;
70
      if (x >= n && y >= m) {
71
        break outer;
72
      }
73
    }
74
  }
75
  // Backtrack.
76
  const pairs: [number, number][] = [];
77
  let x = n;
78
  let y = m;
79
  for (let d = trace.length - 1; d >= 0; d--) {
80
    const vd = trace[d];
81
    const k = x - y;
82
    let prevK: number;
83
    if (k === -d || (k !== d && vd[offset + k - 1] < vd[offset + k + 1])) {
84
      prevK = k + 1;
85
    } else {
86
      prevK = k - 1;
87
    }
88
    const prevX = vd[offset + prevK];
89
    const prevY = prevX - prevK;
90
    while (x > prevX && y > prevY) {
91
      x--;
92
      y--;
93
      pairs.push([x, y]);
94
    }
95
    if (d > 0) {
96
      x = prevX;
97
      y = prevY;
98
    }
99
  }
100
  return pairs.reverse();
101
}