OXYGENHUB

Coverage / src/diffutil.ts 86.1%

87 of 101 lines run, 2 of 2 functions, 34 of 36 branches · run #26 on main · run missed

10×
// A line diff (Myers, O(ND)) used to align blame computed on the base
20×
// version of a file with the lines of the editor's current text.
31×
40×
/** For each line of `after`, the index of the matching line in `before`, or -1 when new. */
51×
export function alignLines(before: string[], after: string[]): number[] {
61×
  const n = before.length;
71×
  const m = after.length;
81×
  const result = new Array<number>(m).fill(-1);
91×
  if (n === 0 || m === 0) {
102×
    return result;
112×
  }
123×
  // Trim common prefix and suffix first; they dominate real edits.
133×
  let start = 0;
141×
  while (start < n && start < m && before[start] === after[start]) {
153×
    result[start] = start;
163×
    start++;
173×
  }
183×
  let endB = n;
193×
  let endA = m;
201×
  while (endB > start && endA > start && before[endB - 1] === after[endA - 1]) {
210×
    endB--;
220×
    endA--;
230×
    result[endA] = endB;
240×
  }
253×
  const a = before.slice(start, endB);
263×
  const b = after.slice(start, endA);
271×
  if (a.length === 0 || b.length === 0) {
281×
    return result;
291×
  }
302×
  // Bail out to positional matching on huge middles to bound memory.
311×
  if (a.length * b.length > 25_000_000) {
320×
    for (let i = 0; i < Math.min(a.length, b.length); i++) {
330×
      if (a[i] === b[i]) {
340×
        result[start + i] = start + i;
350×
      }
360×
    }
370×
    return result;
380×
  }
391×
  for (const [i, j] of myers(a, b)) {
401×
    result[start + j] = start + i;
411×
  }
421×
  return result;
432×
}
442×
452×
/** Pairs (i, j) of equal lines a[i] === b[j] along a shortest edit script. */
462×
function myers(a: string[], b: string[]): [number, number][] {
472×
  const n = a.length;
482×
  const m = b.length;
492×
  const max = n + m;
502×
  const offset = max;
512×
  const trace: Int32Array[] = [];
522×
  let v = new Int32Array(2 * max + 2);
532×
  v[offset + 1] = 0;
542×
  outer: for (let d = 0; d <= max; d++) {
559×
    const snapshot = v.slice();
569×
    trace.push(snapshot);
579×
    for (let k = -d; k <= d; k += 2) {
5821×
      let x: number;
5921×
      if (k === -d || (k !== d && v[offset + k - 1] < v[offset + k + 1])) {
6014×
        x = v[offset + k + 1];
6121×
      } else {
627×
        x = v[offset + k - 1] + 1;
637×
      }
6421×
      let y = x - k;
6521×
      while (x < n && y < m && a[x] === b[y]) {
667×
        x++;
677×
        y++;
687×
      }
6921×
      v[offset + k] = x;
7021×
      if (x >= n && y >= m) {
712×
        break outer;
722×
      }
7321×
    }
747×
  }
752×
  // Backtrack.
762×
  const pairs: [number, number][] = [];
772×
  let x = n;
782×
  let y = m;
792×
  for (let d = trace.length - 1; d >= 0; d--) {
809×
    const vd = trace[d];
819×
    const k = x - y;
829×
    let prevK: number;
839×
    if (k === -d || (k !== d && vd[offset + k - 1] < vd[offset + k + 1])) {
846×
      prevK = k + 1;
859×
    } else {
863×
      prevK = k - 1;
873×
    }
889×
    const prevX = vd[offset + prevK];
899×
    const prevY = prevX - prevK;
909×
    while (x > prevX && y > prevY) {
915×
      x--;
925×
      y--;
935×
      pairs.push([x, y]);
945×
    }
959×
    if (d > 0) {
967×
      x = prevX;
977×
      y = prevY;
987×
    }
999×
  }
1002×
  return pairs.reverse();
1012×
}