import { diffArrays } from "diff"; import type { BlameRange } from "~/lib/code-intel"; const MAX_EDIT_LENGTH = 20_000; const DIFF_TIMEOUT_MS = 1000; /** Maps each new-version line index to its old-version index (-1 when added); null if the diff gave up. */ export function mapLines(oldLines: string[], newLines: string[]): Int32Array | null { const changes = diffArrays(oldLines, newLines, { maxEditLength: MAX_EDIT_LENGTH, timeout: DIFF_TIMEOUT_MS }); if (!changes) return null; const map = new Int32Array(newLines.length).fill(-1); let oldIndex = 0; let newIndex = 0; for (const change of changes) { const count = change.count; if (change.added) newIndex += count; else if (change.removed) oldIndex += count; else { for (let offset = 0; offset < count; offset++) map[newIndex + offset] = oldIndex + offset; oldIndex += count; newIndex += count; } } return map; } export function splitLines(text: string): string[] { const lines = text.split("\n"); if (lines.length > 1 && lines[lines.length - 1] === "") lines.pop(); return lines; } /** Tracks unattributed final-version lines through older versions of a file. */ export class LineTracker { readonly owners: Array; private pending: Array<{ line: number; current: number }>; constructor(lineCount: number) { this.owners = new Array(lineCount).fill(null); this.pending = Array.from({ length: lineCount }, (_, line) => ({ line, current: line })); } get done(): boolean { return this.pending.length === 0; } assignAll(sha: string): void { for (const item of this.pending) this.owners[item.line] = sha; this.pending = []; } /** Lines absent from the parent (per `map`) belong to `sha`; the rest move to parent line numbers. */ step(sha: string, map: Int32Array): void { const remaining: Array<{ line: number; current: number }> = []; for (const item of this.pending) { const parentLine = map[item.current] ?? -1; if (parentLine < 0) this.owners[item.line] = sha; else remaining.push({ line: item.line, current: parentLine }); } this.pending = remaining; } } export function compressRanges(owners: Array, fallback: string): BlameRange[] { const ranges: BlameRange[] = []; for (let line = 0; line < owners.length; line++) { const sha = owners[line] ?? fallback; const last = ranges[ranges.length - 1]; if (last && last.sha === sha) last.count++; else ranges.push({ start: line, count: 1, sha }); } return ranges; }