fix(merge): harden 3-pane editor + document the new conflict resolver
Review follow-ups: coalesce gutter re-layout posts (avoid dispatcher flooding when visual lines aren't ready), drop the zero-length deletable segment (undo hygiene), and clear stale scroll-sync hooks on DataContext swap. Update Ui/CLAUDE.md to the 3-pane editor and log visual-verification items (incl. empty-side + alignment edges) in docs/open.md.
This commit is contained in:
@@ -38,6 +38,7 @@ public partial class ConflictResolverView : Window
|
||||
private bool _rebuilding;
|
||||
private bool _applyingAccept;
|
||||
private bool _syncing;
|
||||
private bool _gutterPending;
|
||||
|
||||
public ConflictResolverView()
|
||||
{
|
||||
@@ -53,6 +54,12 @@ public partial class ConflictResolverView : Window
|
||||
_vm.ActiveFileChanged -= Rebuild;
|
||||
_vm.CurrentChanged -= ScrollToCurrent;
|
||||
}
|
||||
// The editors persist across a DataContext swap, so drop stale scroll-sync hooks first.
|
||||
foreach (var sv in _scrollViewers)
|
||||
if (sv is not null) sv.ScrollChanged -= OnPaneScroll;
|
||||
_scrollViewers = Array.Empty<ScrollViewer?>();
|
||||
_wired = false;
|
||||
|
||||
_vm = DataContext as ConflictResolverViewModel;
|
||||
if (_vm is null) return;
|
||||
|
||||
@@ -150,7 +157,7 @@ public partial class ConflictResolverView : Window
|
||||
_wired = true;
|
||||
Dispatcher.UIThread.Post(HookScrollSync, DispatcherPriority.Loaded);
|
||||
}
|
||||
Dispatcher.UIThread.Post(PositionGutters, DispatcherPriority.Loaded);
|
||||
QueueGutters();
|
||||
}
|
||||
|
||||
private static (string Text, List<(int Offset, int Length, MergeConflictBlock Block)> Spans) BuildSide(
|
||||
@@ -185,7 +192,7 @@ public partial class ConflictResolverView : Window
|
||||
if (e.PropertyName is nameof(MergeConflictBlock.IsResolved) or nameof(MergeConflictBlock.Resolution))
|
||||
{
|
||||
InvalidateRenderers();
|
||||
Dispatcher.UIThread.Post(PositionGutters, DispatcherPriority.Background);
|
||||
QueueGutters();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +209,7 @@ public partial class ConflictResolverView : Window
|
||||
break;
|
||||
}
|
||||
}
|
||||
Dispatcher.UIThread.Post(PositionGutters, DispatcherPriority.Background);
|
||||
QueueGutters();
|
||||
}
|
||||
|
||||
// ── Accept a side into the result ────────────────────────────────────────
|
||||
@@ -233,6 +240,14 @@ public partial class ConflictResolverView : Window
|
||||
RightGutter.Children.Clear();
|
||||
}
|
||||
|
||||
// Coalesce gutter re-layouts so repeated change/scroll events can't flood the dispatcher.
|
||||
private void QueueGutters()
|
||||
{
|
||||
if (_gutterPending) return;
|
||||
_gutterPending = true;
|
||||
Dispatcher.UIThread.Post(() => { _gutterPending = false; PositionGutters(); }, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private void PositionGutters()
|
||||
{
|
||||
ClearGutters();
|
||||
@@ -240,7 +255,7 @@ public partial class ConflictResolverView : Window
|
||||
var tv = ResultEditor.TextArea.TextView;
|
||||
if (!tv.VisualLinesValid)
|
||||
{
|
||||
Dispatcher.UIThread.Post(PositionGutters, DispatcherPriority.Background);
|
||||
QueueGutters();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -314,7 +329,7 @@ public partial class ConflictResolverView : Window
|
||||
if (region.Block is null) return;
|
||||
var line = ResultEditor.Document.GetLineByOffset(region.Start.Offset).LineNumber;
|
||||
ResultEditor.ScrollToLine(line);
|
||||
Dispatcher.UIThread.Post(PositionGutters, DispatcherPriority.Background);
|
||||
QueueGutters();
|
||||
}
|
||||
|
||||
private void InvalidateRenderers()
|
||||
@@ -392,7 +407,6 @@ public partial class ConflictResolverView : Window
|
||||
var s = Math.Max(segment.Offset, start);
|
||||
var e = Math.Min(segment.EndOffset, end);
|
||||
if (e > s) yield return new Seg(s, e - s);
|
||||
else if (e == s && segment.Length == 0 && s >= start && s <= end) yield return new Seg(s, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user