feature: auto-scroll to first change hunk when diff file changed (#1655) (#1684)

Signed-off-by: leo <longshuang@msn.cn>
This commit is contained in:
leo
2025-11-28 16:09:47 +08:00
parent c76b770408
commit 0d7d9c2560
3 changed files with 76 additions and 17 deletions

View File

@@ -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<Models.TextDiffLine> lines)
public BlockNavigation(List<Models.TextDiffLine> 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;
}

View File

@@ -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()

View File

@@ -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<ScrollViewer>();
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;