From 0d7d9c256018a6d14e0117f4e675e3598800051c Mon Sep 17 00:00:00 2001 From: leo Date: Fri, 28 Nov 2025 16:09:47 +0800 Subject: [PATCH] feature: auto-scroll to first change hunk when diff file changed (#1655) (#1684) Signed-off-by: leo --- src/ViewModels/BlockNavigation.cs | 9 +++-- src/ViewModels/TextDiffContext.cs | 24 +++++++++---- src/Views/TextDiffView.axaml.cs | 60 +++++++++++++++++++++++++++---- 3 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/ViewModels/BlockNavigation.cs b/src/ViewModels/BlockNavigation.cs index 7fec6381..d90b161b 100644 --- a/src/ViewModels/BlockNavigation.cs +++ b/src/ViewModels/BlockNavigation.cs @@ -7,7 +7,7 @@ namespace SourceGit.ViewModels { public record Block(int Start, int End) { - public bool IsInRange(int line) + public bool Contains(int line) { return line >= Start && line <= End; } @@ -27,7 +27,7 @@ namespace SourceGit.ViewModels } } - public BlockNavigation(List lines) + public BlockNavigation(List lines, bool gotoFirst) { _blocks.Clear(); _current = -1; @@ -65,6 +65,9 @@ namespace SourceGit.ViewModels blocks.Add(new Block(blockStartIdx, lines.Count)); _blocks.AddRange(blocks); + + if (gotoFirst) + GotoFirst(); } public Block GetCurrentBlock() @@ -126,7 +129,7 @@ namespace SourceGit.ViewModels if (_current >= 0 && _current < _blocks.Count) { var block = _blocks[_current]; - if (block.IsInRange(caretLine)) + if (block.Contains(caretLine)) return; } diff --git a/src/ViewModels/TextDiffContext.cs b/src/ViewModels/TextDiffContext.cs index 505a4c4f..dd85453c 100644 --- a/src/ViewModels/TextDiffContext.cs +++ b/src/ViewModels/TextDiffContext.cs @@ -5,6 +5,8 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels { + public record TextDiffDisplayRange(int Start, int End); + public record TextDiffSelectedChunk(double Y, double Height, int StartIdx, int EndIdx, bool Combined, bool IsOldSide) { public static bool IsChanged(TextDiffSelectedChunk oldValue, TextDiffSelectedChunk newValue) @@ -24,10 +26,6 @@ namespace SourceGit.ViewModels } } - public record TextDiffDisplayRange(int Start, int End) - { - } - public class TextDiffContext : ObservableObject { public Models.TextDiff Data => _data; @@ -147,10 +145,16 @@ namespace SourceGit.ViewModels public CombinedTextDiff(Models.TextDiff diff, CombinedTextDiff previous = null) { _data = diff; - _blockNavigation = new BlockNavigation(_data.Lines); if (previous != null && previous.File.Equals(File, StringComparison.Ordinal)) - _scrollOffset = previous.ScrollOffset; + { + _blockNavigation = new BlockNavigation(_data.Lines, false); + _scrollOffset = previous._scrollOffset; + } + else + { + _blockNavigation = new BlockNavigation(_data.Lines, true); + } } public override TextDiffContext SwitchMode() @@ -187,10 +191,16 @@ namespace SourceGit.ViewModels } FillEmptyLines(); - _blockNavigation = new BlockNavigation(Old); if (previous != null && previous.File.Equals(File, StringComparison.Ordinal)) + { + _blockNavigation = new BlockNavigation(_data.Lines, false); _scrollOffset = previous._scrollOffset; + } + else + { + _blockNavigation = new BlockNavigation(_data.Lines, true); + } } public override bool IsSideBySide() diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 448f3b67..9d329ff6 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -221,7 +221,7 @@ namespace SourceGit.Views if (_presenter.Document == null || !textView.VisualLinesValid) return; - var changeBlock = _presenter.BlockNavigation?.GetCurrentBlock(); + var changeBlock = _presenter.BlockNavigation.GetCurrentBlock(); var changeBlockBG = new SolidColorBrush(Colors.Gray, 0.25); var changeBlockFG = new Pen(Brushes.Gray); @@ -285,7 +285,7 @@ namespace SourceGit.Views } } - if (changeBlock != null && changeBlock.IsInRange(index)) + if (changeBlock != null && changeBlock.Contains(index)) { drawingContext.DrawRectangle(changeBlockBG, null, new Rect(0, startY, width, endY - startY)); if (index == changeBlock.Start) @@ -498,7 +498,7 @@ namespace SourceGit.Views public virtual void GotoFirstChange() { - var first = BlockNavigation?.GotoFirst(); + var first = BlockNavigation.GotoFirst(); if (first != null) { TextArea.Caret.Line = first.Start; @@ -508,7 +508,7 @@ namespace SourceGit.Views public virtual void GotoPrevChange() { - var prev = BlockNavigation?.GotoPrev(); + var prev = BlockNavigation.GotoPrev(); if (prev != null) { TextArea.Caret.Line = prev.Start; @@ -518,7 +518,7 @@ namespace SourceGit.Views public virtual void GotoNextChange() { - var next = BlockNavigation?.GotoNext(); + var next = BlockNavigation.GotoNext(); if (next != null) { TextArea.Caret.Line = next.Start; @@ -528,7 +528,7 @@ namespace SourceGit.Views public virtual void GotoLastChange() { - var next = BlockNavigation?.GotoLast(); + var next = BlockNavigation.GotoLast(); if (next != null) { TextArea.Caret.Line = next.Start; @@ -627,6 +627,23 @@ namespace SourceGit.Views } } + protected override void OnDataContextBeginUpdate() + { + base.OnDataContextBeginUpdate(); + AutoScrollToFirstChange(); + } + + protected override void OnSizeChanged(SizeChangedEventArgs e) + { + base.OnSizeChanged(e); + + if (!_execSizeChanged) + { + _execSizeChanged = true; + AutoScrollToFirstChange(); + } + } + private async void OnTextAreaKeyDown(object sender, KeyEventArgs e) { if (e.KeyModifiers.Equals(OperatingSystem.IsMacOS() ? KeyModifiers.Meta : KeyModifiers.Control)) @@ -644,7 +661,7 @@ namespace SourceGit.Views private void OnTextAreaCaretPositionChanged(object sender, EventArgs e) { - BlockNavigation?.UpdateByCaretPosition(TextArea?.Caret?.Line ?? 0); + BlockNavigation.UpdateByCaretPosition(TextArea?.Caret?.Line ?? 0); } private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) @@ -783,6 +800,34 @@ namespace SourceGit.Views } } + private void AutoScrollToFirstChange() + { + if (Bounds.Height < 0.1) + return; + + if (DataContext is not ViewModels.TextDiffContext ctx) + return; + + if (ctx.IsSideBySide() && !IsOld) + return; + + var curBlock = ctx.BlockNavigation.GetCurrentBlock(); + if (curBlock == null) + return; + + var lineHeight = TextArea.TextView.DefaultLineHeight; + var vOffset = lineHeight * (curBlock.Start - 1) - Bounds.Height * 0.5; + if (vOffset >= 0) + { + var scroller = this.FindDescendantOfType(); + if (scroller != null) + { + ctx.ScrollOffset = new Vector(0, vOffset); + scroller.Offset = ctx.ScrollOffset; + } + } + } + private async Task CopyWithoutIndicatorsAsync() { var selection = TextArea.Selection; @@ -852,6 +897,7 @@ namespace SourceGit.Views await App.CopyTextAsync(builder.ToString()); } + private bool _execSizeChanged = false; private TextMate.Installation _textMate = null; private TextLocation _lastSelectStart = TextLocation.Empty; private TextLocation _lastSelectEnd = TextLocation.Empty;