feat(diff): sync vertical scrolling across the split panes
This commit is contained in:
@@ -5,6 +5,8 @@ using System.IO;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Threading;
|
||||
using Avalonia.VisualTree;
|
||||
using AvaloniaEdit;
|
||||
using AvaloniaEdit.Document;
|
||||
using AvaloniaEdit.Editing;
|
||||
@@ -64,6 +66,10 @@ public partial class DiffTextView : UserControl
|
||||
|
||||
private DiffLineNumberMargin? _leftMargin, _rightMargin;
|
||||
|
||||
private ScrollViewer? _leftScroll, _rightScroll;
|
||||
private bool _scrollHooked;
|
||||
private bool _syncing;
|
||||
|
||||
public DiffTextView()
|
||||
{
|
||||
InitializeComponent();
|
||||
@@ -71,6 +77,7 @@ public partial class DiffTextView : UserControl
|
||||
_rightTm = RightEditor.InstallTextMate(Registry);
|
||||
InstallRenderers();
|
||||
Rebuild();
|
||||
HookScrollSync();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
||||
@@ -199,6 +206,49 @@ public partial class DiffTextView : UserControl
|
||||
RightEditor.TextArea.TextView.InvalidateVisual();
|
||||
}
|
||||
|
||||
/// The editors' ScrollViewers only exist once the template has been applied.
|
||||
private void HookScrollSync()
|
||||
{
|
||||
if (_scrollHooked) return;
|
||||
_scrollHooked = true;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_leftScroll = LeftEditor.FindDescendantOfType<ScrollViewer>();
|
||||
_rightScroll = RightEditor.FindDescendantOfType<ScrollViewer>();
|
||||
if (_leftScroll is not null) _leftScroll.ScrollChanged += OnPaneScroll;
|
||||
if (_rightScroll is not null) _rightScroll.ScrollChanged += OnPaneScroll;
|
||||
}, DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
private void OnPaneScroll(object? sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (_syncing || !IsSplit || _leftScroll is null || _rightScroll is null) return;
|
||||
var fromLeft = ReferenceEquals(sender, _leftScroll);
|
||||
var source = fromLeft ? _leftScroll : _rightScroll;
|
||||
var target = fromLeft ? _rightScroll : _leftScroll;
|
||||
var targetEditor = fromLeft ? RightEditor : LeftEditor;
|
||||
var sourceEditor = fromLeft ? LeftEditor : RightEditor;
|
||||
|
||||
_syncing = true;
|
||||
try
|
||||
{
|
||||
if (WrapLines)
|
||||
{
|
||||
// Line heights differ once lines wrap, so anchor on the top visible line.
|
||||
var topLine = sourceEditor.TextArea.TextView.VisualLines is { Count: > 0 } visualLines
|
||||
? visualLines[0].FirstDocumentLine.LineNumber
|
||||
: (int?)null;
|
||||
if (topLine is { } line && line >= 1 && line <= targetEditor.Document.LineCount)
|
||||
targetEditor.ScrollToLine(line);
|
||||
}
|
||||
else if (Math.Abs(target.Offset.Y - source.Offset.Y) > 0.5)
|
||||
{
|
||||
target.Offset = new Vector(target.Offset.X, source.Offset.Y);
|
||||
}
|
||||
}
|
||||
finally { _syncing = false; }
|
||||
}
|
||||
|
||||
/// <summary>What one document line represents, for the margin and the background renderers.</summary>
|
||||
internal sealed record RowInfo(
|
||||
AlignedSide Kind, int? OldNo, int? NewNo, IReadOnlyList<TextSpan> Spans);
|
||||
|
||||
Reference in New Issue
Block a user