From 38fdaf231f04f022c4031a6d093d7cf6ad38fbb8 Mon Sep 17 00:00:00 2001 From: Tibor Prokai Date: Fri, 30 Oct 2026 12:14:00 +0100 Subject: [PATCH 1/2] feature: add history navigation tools --- screenshots/history-navigation-tools.png | Bin 0 -> 52351 bytes src/Models/CommitNavigationHistory.cs | 49 ++++ src/Resources/Icons.axaml | 3 + src/Resources/Locales/en_US.axaml | 3 + src/ViewModels/Histories.Navigation.cs | 160 ++++++++++++++ src/ViewModels/Histories.cs | 3 +- src/Views/Histories.axaml | 21 ++ src/Views/Histories.axaml.cs | 41 +++- .../HistoryNavigationChecks.csproj | 11 + tests/HistoryNavigationChecks/Program.cs | 209 ++++++++++++++++++ 10 files changed, 493 insertions(+), 7 deletions(-) create mode 100644 screenshots/history-navigation-tools.png create mode 100644 src/Models/CommitNavigationHistory.cs create mode 100644 src/ViewModels/Histories.Navigation.cs create mode 100644 tests/HistoryNavigationChecks/HistoryNavigationChecks.csproj create mode 100644 tests/HistoryNavigationChecks/Program.cs diff --git a/screenshots/history-navigation-tools.png b/screenshots/history-navigation-tools.png new file mode 100644 index 0000000000000000000000000000000000000000..0c832829aa3b770cac9163150ed63c836b7d02c9 GIT binary patch literal 52351 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 N002ovPDHLkV1jl>u`vJu literal 0 HcmV?d00001 diff --git a/src/Models/CommitNavigationHistory.cs b/src/Models/CommitNavigationHistory.cs new file mode 100644 index 0000000000..1dda5df8ea --- /dev/null +++ b/src/Models/CommitNavigationHistory.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; + +namespace SourceGit.Models +{ + public class CommitNavigationHistory + { + public bool CanGoBack => _position > 0; + public bool CanGoForward => _position + 1 < _commits.Count; + public string Current => _position >= 0 ? _commits[_position] : null; + public string Previous => CanGoBack ? _commits[_position - 1] : null; + public string Next => CanGoForward ? _commits[_position + 1] : null; + + public void Visit(string sha) + { + if (_position >= 0 && _commits[_position] == sha) + return; + + _commits.RemoveRange(_position + 1, _commits.Count - _position - 1); + _commits.Add(sha); + _position++; + } + + public string Back() + { + return CanGoBack ? _commits[--_position] : null; + } + + public string Forward() + { + return CanGoForward ? _commits[++_position] : null; + } + + public void Forget(string sha) + { + for (var i = _commits.Count - 1; i >= 0; i--) + { + if (_commits[i] != sha) + continue; + + _commits.RemoveAt(i); + if (i <= _position) + _position--; + } + } + + private readonly List _commits = []; + private int _position = -1; + } +} diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index f9afba45ea..6aa49d9ffe 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -1,4 +1,7 @@ + M9 2L2 9L9 16L11 14L7 10H18V8H7L11 4Z + M11 2L18 9L11 16L9 14L13 10H2V8H13L9 4Z + M10 1L0 10H3V19H8V13H12V19H17V10H20Z M41 512c0-128 46-241 138-333C271 87 384 41 512 41s241 46 333 138c92 92 138 205 138 333s-46 241-138 333c-92 92-205 138-333 138s-241-46-333-138C87 753 41 640 41 512zm87 0c0 108 36 195 113 271s164 113 271 113c108 0 195-36 271-113s113-164 113-271-36-195-113-271c-77-77-164-113-271-113-108 0-195 36-271 113C164 317 128 404 128 512zm256 148V292l195 113L768 512l-195 113-195 113v-77zm148-113-61 36V440l61 36 61 36-61 36z M306 761c3-3 8-7 16-11 15-7 35-14 60-19 4-1 14-3 11-18l-3-16c-4-18-13-14-18-13C301 699 256 724 256 763c0 58 112 91 252 91 140 0 252-33 252-91 0-38-45-64-116-78-5-1-16-3-18 13-1 6-2 11-3 15-4 18 8 18 14 19 24 5 44 11 58 18 8 4 13 8 16 11 1 2 2 2 2 1 0 3-17 15-50 24-40 12-95 19-155 19-59 0-114-7-155-19-32-9-49-21-50-24 0 0 0-0 2-2zm73-123c54 79 107 142 129 142 22 0 75-62 129-142 62-91 101-173 101-231C738 280 635 176 508 176c-127 0-230 104-230 232 0 57 39 139 101 231zm150 64a446 446 0 01-21 23c-6-6-13-14-21-23-21-25-46-57-68-91-57-84-92-159-92-204 0-102 82-184 182-184 100 0 182 82 182 184 0 45-36 120-92 204-23 33-47 66-68 91zm-21-200a102 102 0 100-204 102 102 0 000 204zm0-48a54 54 0 110-108 54 54 0 010 108z M304 464a128 128 0 01128-128c71 0 128 57 128 128v224a32 32 0 01-64 0V592h-128v95a32 32 0 01-64 0v-224zm64 1v64h128v-64a64 64 0 00-64-64c-35 0-64 29-64 64zM688 337c18 0 32 14 32 32v319a32 32 0 01-32 32c-18 0-32-14-32-32v-319a32 32 0 0132-32zM84 911l60-143A446 446 0 0164 512C64 265 265 64 512 64s448 201 448 448-201 448-448 448c-54 0-105-9-153-27l-242 22a32 32 0 01-32-44zm133-150-53 126 203-18 13 5c41 15 85 23 131 23 212 0 384-172 384-384S724 128 512 128 128 300 128 512c0 82 26 157 69 220l20 29z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 603d223a9c..70f16961c7 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -524,6 +524,9 @@ Track all *{0} files Select Commit Advanced Options + Back + Forward + Go to current HEAD AUTHOR AUTHOR TIME COMMIT TIME diff --git a/src/ViewModels/Histories.Navigation.cs b/src/ViewModels/Histories.Navigation.cs new file mode 100644 index 0000000000..7238ec6c74 --- /dev/null +++ b/src/ViewModels/Histories.Navigation.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +using Avalonia.Threading; + +namespace SourceGit.ViewModels +{ + public partial class Histories + { + public bool CanNavigateBack => !_isResolvingNavigation && (_navigationHistory.CanGoBack || HasUnrecordedNavigationSelection); + public bool CanNavigateForward => !_isResolvingNavigation && _navigationHistory.CanGoForward; + + public async Task NavigateHomeAsync() + { + CancelNavigationVisit(); + var request = _navigationRequest; + // Resolve HEAD in this worktree, even if the loaded history omits it + // or its decorations belong to an earlier checkout. + var commit = await GetCommitAsync("HEAD"); + if (request != _navigationRequest) + return; + + if (commit == null && _commits.Count > 0) + commit = _commits[0]; + if (commit != null) + SelectedCommits = [EnsureNavigationCommitVisible(commit)]; + } + + public Task NavigateBackAsync() => NavigateHistoryAsync(-1); + public Task NavigateForwardAsync() => NavigateHistoryAsync(1); + + public void ResumeNavigationVisits() + { + _isNavigationActive = true; + ScheduleNavigationVisit(); + } + + public void SuspendNavigationVisits() + { + _isNavigationActive = false; + CancelNavigationVisit(); + } + + public void CancelNavigationVisit() + { + _navigationRequest++; + _navigationVisitTimer?.Stop(); + } + + private void ScheduleNavigationVisit() + { + CancelNavigationVisit(); + NotifyNavigationChanged(); + if (!_isNavigationActive || _isNavigatingHistory || _ignoreSelectionChange || _selectedCommits.Count != 1) + return; + + if (_navigationVisitTimer == null) + { + _navigationVisitTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; + _navigationVisitTimer.Tick += (_, _) => + { + _navigationVisitTimer.Stop(); + if (_selectedCommits.Count == 1) + { + _navigationHistory.Visit(_selectedCommits[0].SHA); + NotifyNavigationChanged(); + } + }; + } + + _navigationVisitTimer.Start(); + } + + private async Task NavigateHistoryAsync(int direction) + { + if (direction < 0 ? !CanNavigateBack : !CanNavigateForward) + return; + + CancelNavigationVisit(); + var request = _navigationRequest; + _isResolvingNavigation = true; + NotifyNavigationChanged(); + try + { + while (true) + { + var returnToCurrent = direction < 0 && HasUnrecordedNavigationSelection; + var sha = direction < 0 ? + returnToCurrent ? _navigationHistory.Current : _navigationHistory.Previous : + _navigationHistory.Next; + if (sha == null) + return; + + var commit = _commits.Find(c => c.SHA == sha); + if (commit == null) + { + commit = await GetCommitAsync(sha); + if (request != _navigationRequest) + return; + if (commit == null) + { + // A rewritten, unreachable commit may have been pruned by Git. + _navigationHistory.Forget(sha); + continue; + } + } + + if (!returnToCurrent) + { + if (direction < 0) + _navigationHistory.Back(); + else + _navigationHistory.Forward(); + } + + _isNavigatingHistory = true; + SelectedCommits = [EnsureNavigationCommitVisible(commit)]; + return; + } + } + finally + { + _isNavigatingHistory = false; + _isResolvingNavigation = false; + NotifyNavigationChanged(); + } + } + + private void NotifyNavigationChanged() + { + OnPropertyChanged(nameof(CanNavigateBack)); + OnPropertyChanged(nameof(CanNavigateForward)); + } + + private Models.Commit EnsureNavigationCommitVisible(Models.Commit commit) + { + var existing = _commits.Find(c => c.SHA == commit.SHA); + if (existing != null) + return existing; + + var commits = new List(_commits); + // Keep children above their parents when restoring an omitted row. + var parentIndex = commits.FindIndex(c => commit.Parents.Contains(c.SHA)); + commits.Insert(parentIndex < 0 ? commits.Count : parentIndex, commit); + Commits = commits; + return commit; + } + + private bool HasUnrecordedNavigationSelection => _navigationHistory.Current != null && + (_selectedCommits.Count != 1 || _selectedCommits[0].SHA != _navigationHistory.Current); + + private readonly Models.CommitNavigationHistory _navigationHistory = new(); + private DispatcherTimer _navigationVisitTimer; + private bool _isNavigatingHistory; + private bool _isResolvingNavigation; + private bool _isNavigationActive; + private int _navigationRequest; + } +} diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index a22f810cdb..b7a25d2fa5 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -10,7 +10,7 @@ namespace SourceGit.ViewModels { - public class Histories : ObservableObject + public partial class Histories : ObservableObject { public bool IsLoading { @@ -526,6 +526,7 @@ private void PostCommitsChanged() private void PostSelectedCommitsChanged() { + ScheduleNavigationVisit(); if (_ignoreSelectionChange) return; diff --git a/src/Views/Histories.axaml b/src/Views/Histories.axaml index a3d24529fd..c307ffc978 100644 --- a/src/Views/Histories.axaml +++ b/src/Views/Histories.axaml @@ -272,6 +272,27 @@ + + + diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index d42529fe2e..e915ff1176 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -137,12 +137,9 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang if (change.Property == SelectedCommitsProperty && IsLoaded && !_ignoreSelectionChanged) { - if (change.OldValue is List { Count: 1 } old && - change.NewValue is List { Count: 1 } cur && - old[0] == cur[0]) - ScrollIntoView(old[0], null); - else - ApplySelection(); + ApplySelection(); + if (change.NewValue is List { Count: 1 } selected) + ScrollIntoView(selected[0], null); } } @@ -372,6 +369,32 @@ public bool IsDetailsPanelExpanded public Histories() { InitializeComponent(); + AddHandler(KeyDownEvent, OnNavigationKeyDown, RoutingStrategies.Tunnel); + Loaded += (_, _) => (DataContext as ViewModels.Histories)?.ResumeNavigationVisits(); + Unloaded += (_, _) => (DataContext as ViewModels.Histories)?.SuspendNavigationVisits(); + } + + private async void OnNavigationKeyDown(object sender, KeyEventArgs e) + { + if (DataContext is not ViewModels.Histories vm) + return; + + var macCommand = OperatingSystem.IsMacOS() && e.KeyModifiers == KeyModifiers.Meta; + if (e.KeyModifiers == KeyModifiers.Alt && e.Key == Key.Home) + { + e.Handled = true; + await vm.NavigateHomeAsync(); + } + else if ((e.KeyModifiers == KeyModifiers.Alt && e.Key == Key.Left) || (macCommand && e.Key == Key.OemOpenBrackets)) + { + e.Handled = true; + await vm.NavigateBackAsync(); + } + else if ((e.KeyModifiers == KeyModifiers.Alt && e.Key == Key.Right) || (macCommand && e.Key == Key.OemCloseBrackets)) + { + e.Handled = true; + await vm.NavigateForwardAsync(); + } } public async Task GotoParent() @@ -462,8 +485,13 @@ public async Task GotoChild() protected override void OnDataContextChanged(EventArgs e) { + _navigationHistories?.SuspendNavigationVisits(); base.OnDataContextChanged(e); + _navigationHistories = DataContext as ViewModels.Histories; + if (IsLoaded) + _navigationHistories?.ResumeNavigationVisits(); + if (DataContext is ViewModels.Histories vm) CommitListContainer.Columns[1].Width = new(vm.AuthorColumnWidth, DataGridLengthUnitType.Pixel); } @@ -1964,6 +1992,7 @@ private async Task InteractiveRebaseWithPrefillActionAsync(ViewModels.Repository await this.ShowDialogAsync(new ViewModels.InteractiveRebase(repo, on, prefill)); } + private ViewModels.Histories _navigationHistories = null; private Models.Branch _currentBranch = null; private Models.Bisect _bisect = null; private bool _hasSingleRemote = false; diff --git a/tests/HistoryNavigationChecks/HistoryNavigationChecks.csproj b/tests/HistoryNavigationChecks/HistoryNavigationChecks.csproj new file mode 100644 index 0000000000..cbfcb000ed --- /dev/null +++ b/tests/HistoryNavigationChecks/HistoryNavigationChecks.csproj @@ -0,0 +1,11 @@ + + + Exe + net10.0 + enable + + + + + + diff --git a/tests/HistoryNavigationChecks/Program.cs b/tests/HistoryNavigationChecks/Program.cs new file mode 100644 index 0000000000..cf4f6ee5ab --- /dev/null +++ b/tests/HistoryNavigationChecks/Program.cs @@ -0,0 +1,209 @@ +using System.Diagnostics; +using System.Reflection; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Primitives; +using Avalonia.Headless; +using Avalonia.Input; +using Avalonia.Threading; +using Avalonia.VisualTree; +using SourceGit.Models; +using SourceGit.ViewModels; + +var fixture = Path.Combine(Path.GetTempPath(), "sourcegit-navigation-" + Guid.NewGuid().ToString("N")); +Directory.CreateDirectory(fixture); +SourceGit.Native.OS.GitExecutable = "git"; +SourceGit.Native.OS.BasicDirectories.ConfigDir = fixture; +SourceGit.Native.OS.BasicDirectories.CacheDir = fixture; +typeof(Preferences).GetField("_instance", BindingFlags.NonPublic | BindingFlags.Static)!.SetValue(null, new Preferences()); +AppBuilder.Configure().UseSkia().WithInterFont() + .ConfigureFonts(manager => manager.AddFontCollection(new Avalonia.Media.Fonts.EmbeddedFontCollection( + new Uri("fonts:SourceGit"), new Uri("avares://SourceGit/Resources/Fonts")))) + .UseHeadless(new AvaloniaHeadlessPlatformOptions { UseHeadlessDrawing = false }).SetupWithoutStarting(); + +Git("init", "-b", "main"); +Git("config", "user.name", "Navigation Checks"); +Git("config", "user.email", "navigation@example.com"); +for (var i = 0; i < 3; i++) + Git("commit", "--allow-empty", "-m", $"Commit {i}"); +var commits = new SourceGit.Commands.QueryCommits(fixture, "HEAD").GetResultAsync().GetAwaiter().GetResult(); +var head = commits[0]; +var second = commits[1]; +var oldest = commits[2]; +// Put HEAD below the top row to distinguish Home from scrolling to the first row. +commits.InsertRange(0, Enumerable.Range(1, 60).Select(i => new Commit { SHA = i.ToString("x40"), Subject = $"Row {i}" })); +var repo = new Repository(false, fixture, Path.Combine(fixture, ".git")); +var vm = new Histories(repo) { Commits = commits, IsLoading = false }; +var view = new SourceGit.Views.Histories { DataContext = vm }; +var window = new Window { Content = view, Width = 1000, Height = 650 }; +window.Show(); +Pump(200); +var grid = view.FindControl("CommitListContainer")!; +var homeButton = view.FindControl