From 10ce0d23651fc2c96fd0d276147cd48105e21b84 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 15 Oct 2026 15:23:52 +0400 Subject: [PATCH] refac --- src/lib/components/common/diffEditor.ts | 42 +++++++++++++++++++ .../components/common/diffEditorAlignment.ts | 29 ++++--------- 2 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/lib/components/common/diffEditor.ts b/src/lib/components/common/diffEditor.ts index c1519c6b73..877e3b4584 100644 --- a/src/lib/components/common/diffEditor.ts +++ b/src/lib/components/common/diffEditor.ts @@ -6,6 +6,48 @@ export const diffEditorConfig: DiffConfig = { timeout: 100 }; export type MergeDirection = 'to-working' | 'to-proposal'; +// Match rows by the text preserved inside a chunk, not their ordinal position. +export function changedLinePairs(a: Text, b: Text, chunk: Chunk): [number, number][] { + const candidates = new Map>(); + let fromA = chunk.fromA; + let fromB = chunk.fromB; + for (let index = 0; index <= chunk.changes.length; index++) { + const change = chunk.changes[index]; + const endA = change ? chunk.fromA + change.fromA : Math.min(chunk.toA, a.length); + const endB = change ? chunk.fromB + change.fromB : Math.min(chunk.toB, b.length); + while (fromA < endA && fromB < endB) { + const lineA = a.lineAt(fromA), + lineB = b.lineAt(fromB); + const length = Math.min(endA - fromA, endB - fromB, lineA.to - fromA, lineB.to - fromB); + const weight = a.sliceString(fromA, fromA + length).replace(/\s/g, '').length; + if (weight) { + let row = candidates.get(lineA.from); + if (!row) candidates.set(lineA.from, (row = new Map())); + const pair = row.get(lineB.from) ?? { a: lineA.from, b: lineB.from, weight: 0 }; + pair.weight += weight; + row.set(lineB.from, pair); + } + fromA += length || 1; + fromB += length || 1; + } + if (change) { + fromA = chunk.fromA + change.toA; + fromB = chunk.fromB + change.toB; + } + } + const bestA = new Map(); + const bestB = new Map(); + for (const row of candidates.values()) { + for (const pair of row.values()) { + if (pair.weight > (bestA.get(pair.a)?.weight ?? 0)) bestA.set(pair.a, pair); + if (pair.weight > (bestB.get(pair.b)?.weight ?? 0)) bestB.set(pair.b, pair); + } + } + return [...bestA.values()] + .filter((pair) => bestB.get(pair.b) === pair) + .map((pair) => [pair.a, pair.b]); +} + export function changedLineCounts(a: Text, b: Text, chunks: readonly Chunk[]) { const count = (doc: Text, side: 'A' | 'B') => { let total = 0; diff --git a/src/lib/components/common/diffEditorAlignment.ts b/src/lib/components/common/diffEditorAlignment.ts index 129259ef6f..0e4d2f1e9a 100644 --- a/src/lib/components/common/diffEditorAlignment.ts +++ b/src/lib/components/common/diffEditorAlignment.ts @@ -7,16 +7,19 @@ import { type DecorationSet } from '@codemirror/view'; import type { Chunk } from '@codemirror/merge'; +import { changedLinePairs } from './diffEditor'; export function pairedLinePosition(a: Text, b: Text, chunks: readonly Chunk[], position: number) { let offset = 0; for (const chunk of chunks) { if (position < chunk.fromA) break; if (position < chunk.toA) { - const row = a.lineAt(position).number - a.lineAt(chunk.fromA).number; - const start = b.lineAt(Math.min(chunk.fromB, b.length)).number; - const end = b.lineAt(Math.min(Math.max(chunk.fromB, chunk.toB - 1), b.length)).number; - return b.line(Math.min(start + row, end)).from; + const pairs = changedLinePairs(a, b, chunk); + const line = a.lineAt(position).from; + return ( + (pairs.find(([from]) => from >= line) ?? pairs.at(-1))?.[1] ?? + Math.min(chunk.fromB, b.length) + ); } offset = chunk.toB - chunk.toA; } @@ -128,23 +131,7 @@ export function alignChangedLines(a: EditorView, b: EditorView, chunks: readonly align(posA, posB); } if (!chunk) break; - if (chunk.fromA < chunk.toA && chunk.fromB < chunk.toB) { - const lineA = a.state.doc.lineAt(chunk.fromA).number, - lineB = b.state.doc.lineAt(chunk.fromB).number; - let row = Math.max( - 0, - a.state.doc.lineAt(a.viewport.from).number - lineA, - b.state.doc.lineAt(b.viewport.from).number - lineB - ); - while (lineA + row <= a.state.doc.lines && lineB + row <= b.state.doc.lines) { - const posA = a.state.doc.line(lineA + row).from, - posB = b.state.doc.line(lineB + row).from; - if (posA >= chunk.toA || posB >= chunk.toB || posA > a.viewport.to || posB > b.viewport.to) - break; - align(posA, posB); - row++; - } - } + for (const [posA, posB] of changedLinePairs(a.state.doc, b.state.doc, chunk)) align(posA, posB); fromA = chunk.toA; fromB = chunk.toB; }