From 0a245d3dfe7107ff880dd9e2aae67f85c3695159 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 14 Oct 2026 14:46:44 +0400 Subject: [PATCH] refac --- .../components/common/DiffEditorModal.svelte | 29 ++++++++++++++++++- .../components/common/diffEditorAlignment.ts | 21 ++++++++++++-- 2 files changed, 47 insertions(+), 3 deletions(-) diff --git a/src/lib/components/common/DiffEditorModal.svelte b/src/lib/components/common/DiffEditorModal.svelte index 03e61df49e..db83bf61c2 100644 --- a/src/lib/components/common/DiffEditorModal.svelte +++ b/src/lib/components/common/DiffEditorModal.svelte @@ -14,7 +14,12 @@ import ConfirmDialog from './ConfirmDialog.svelte'; import FileCodeEditor from '$lib/components/chat/FileNav/FileCodeEditor.svelte'; import XMark from '$lib/components/icons/XMark.svelte'; - import { lineAlignment, alignChangedLines, clearLineAlignment } from './diffEditorAlignment'; + import { + lineAlignment, + alignChangedLines, + clearLineAlignment, + pairedLinePosition + } from './diffEditorAlignment'; import { copyChunk, selectedChunk, @@ -222,10 +227,32 @@ (alignmentDocuments[0] !== documents[0] || alignmentDocuments[1] !== documents[1] || geometry !== alignmentGeometry); + const resized = alignmentDocuments && geometry !== alignmentGeometry; alignmentDocuments = documents; alignmentGeometry = geometry; if (!reset || !clearLineAlignment(view.a, view.b)) alignChangedLines(view.a, view.b, view.chunks); + if (resized) { + // Reflow can leave one viewport entirely inside an old EOF spacer. + // Measure both panes around the row the reader was looking at. + const top = Math.max(0, node.getBoundingClientRect().top - view.a.documentTop); + const position = Math.min( + view.a.lineBlockAtHeight(top).from, + view.a.state.doc.length + ); + const other = pairedLinePosition( + view.a.state.doc, + view.b.state.doc, + view.chunks, + position + ); + view.a.dispatch({ + effects: EditorView.scrollIntoView(position, { y: 'start', yMargin: 0 }) + }); + view.b.dispatch({ + effects: EditorView.scrollIntoView(other, { y: 'start', yMargin: 0 }) + }); + } view.reconfigure({}); } }); diff --git a/src/lib/components/common/diffEditorAlignment.ts b/src/lib/components/common/diffEditorAlignment.ts index 8fec987d0b..129259ef6f 100644 --- a/src/lib/components/common/diffEditorAlignment.ts +++ b/src/lib/components/common/diffEditorAlignment.ts @@ -1,4 +1,4 @@ -import { StateEffect, StateField } from '@codemirror/state'; +import { StateEffect, StateField, type Text } from '@codemirror/state'; import { BlockType, Decoration, @@ -8,6 +8,21 @@ import { } from '@codemirror/view'; import type { Chunk } from '@codemirror/merge'; +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; + } + offset = chunk.toB - chunk.toA; + } + return Math.max(0, Math.min(position + offset, b.length)); +} + class RowSpacer extends WidgetType { constructor(readonly height: number) { super(); @@ -65,7 +80,9 @@ export function alignChangedLines(a: EditorView, b: EditorView, chunks: readonly const block = view.lineBlockAt(pos); const text = Array.isArray(block.type) ? block.type.find((part) => part.type === BlockType.Text && part.from === pos) - : block.type === BlockType.Text ? block : null; + : block.type === BlockType.Text + ? block + : null; return text?.from === pos ? text.top : null; }; const align = (posA: number, posB: number) => {