From 536f2258675f988e724220060c8b138c01f45e20 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 2 Dec 2024 10:38:40 +0800 Subject: [PATCH 01/24] feature: allow using `Amend` while rebasing (#773) --- src/Views/WorkingCopy.axaml | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index e77547e7..594a72b9 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -239,7 +239,6 @@ Margin="8,0,0,0" HorizontalAlignment="Left" IsChecked="{Binding UseAmend, Mode=TwoWay}" - IsVisible="{Binding InProgressContext, Converter={x:Static ObjectConverters.IsNull}}" Content="{DynamicResource Text.WorkingCopy.Amend}"/> From 894f3e9b033f15df5f87f251299afa6ce9428227 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 2 Dec 2024 21:44:15 +0800 Subject: [PATCH 02/24] feature: supports searching revision files (#775) --- ...sionFiles.cs => QueryRevisionFileNames.cs} | 8 +- src/Commands/QueryRevisionObjects.cs | 22 ++++- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/CommitDetail.cs | 95 ++++++++++++++++++- src/ViewModels/Repository.cs | 2 +- src/Views/RevisionFileTreeView.axaml.cs | 64 +++++++++++++ src/Views/RevisionFiles.axaml | 90 +++++++++++++++++- src/Views/RevisionFiles.axaml.cs | 81 ++++++++++++++++ 10 files changed, 350 insertions(+), 15 deletions(-) rename src/Commands/{QueryCurrentRevisionFiles.cs => QueryRevisionFileNames.cs} (55%) diff --git a/src/Commands/QueryCurrentRevisionFiles.cs b/src/Commands/QueryRevisionFileNames.cs similarity index 55% rename from src/Commands/QueryCurrentRevisionFiles.cs rename to src/Commands/QueryRevisionFileNames.cs index 217ea20e..d2d69614 100644 --- a/src/Commands/QueryCurrentRevisionFiles.cs +++ b/src/Commands/QueryRevisionFileNames.cs @@ -1,19 +1,19 @@ namespace SourceGit.Commands { - public class QueryCurrentRevisionFiles : Command + public class QueryRevisionFileNames : Command { - public QueryCurrentRevisionFiles(string repo) + public QueryRevisionFileNames(string repo, string revision) { WorkingDirectory = repo; Context = repo; - Args = "ls-tree -r --name-only HEAD"; + Args = $"ls-tree -r -z --name-only {revision}"; } public string[] Result() { var rs = ReadToEnd(); if (rs.IsSuccess) - return rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries); + return rs.StdOut.Split('\0', System.StringSplitOptions.RemoveEmptyEntries); return []; } diff --git a/src/Commands/QueryRevisionObjects.cs b/src/Commands/QueryRevisionObjects.cs index bcad9129..de3406e8 100644 --- a/src/Commands/QueryRevisionObjects.cs +++ b/src/Commands/QueryRevisionObjects.cs @@ -12,7 +12,7 @@ namespace SourceGit.Commands { WorkingDirectory = repo; Context = repo; - Args = $"ls-tree {sha}"; + Args = $"ls-tree -z {sha}"; if (!string.IsNullOrEmpty(parentFolder)) Args += $" -- \"{parentFolder}\""; @@ -20,11 +20,27 @@ namespace SourceGit.Commands public List Result() { - Exec(); + var rs = ReadToEnd(); + if (rs.IsSuccess) + { + var start = 0; + var end = rs.StdOut.IndexOf('\0', start); + while (end > 0) + { + var line = rs.StdOut.Substring(start, end - start); + Parse(line); + start = end + 1; + end = rs.StdOut.IndexOf('\0', start); + } + + if (start < rs.StdOut.Length) + Parse(rs.StdOut.Substring(start)); + } + return _objects; } - protected override void OnReadline(string line) + private void Parse(string line) { var match = REG_FORMAT().Match(line); if (!match.Success) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index d06635eb..6bea386d 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -121,6 +121,7 @@ Search Changes... FILES LFS File + Search Files... Submodule INFORMATION AUTHOR diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 9cf0eedf..56a5353e 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -124,6 +124,7 @@ 查找变更... 文件列表 LFS文件 + 查找文件... 子模块 基本信息 修改者 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index 920d9114..61029b45 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -124,6 +124,7 @@ 搜尋變更... 檔案列表 LFS 檔案 + 搜尋檔案... 子模組 基本資訊 作者 diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index f14b0359..ec2822cd 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -82,7 +82,7 @@ namespace SourceGit.ViewModels { get; private set; - } = new AvaloniaList(); + } = []; public string SearchChangeFilter { @@ -106,13 +106,68 @@ namespace SourceGit.ViewModels { get; private set; - } = new AvaloniaList(); + } = []; public AvaloniaList IssueTrackerRules { get => _repo.Settings?.IssueTrackerRules; } + public string RevisionFileSearchFilter + { + get => _revisionFileSearchFilter; + set + { + if (SetProperty(ref _revisionFileSearchFilter, value)) + { + RevisionFileSearchSuggestion.Clear(); + + if (!string.IsNullOrEmpty(value)) + { + if (_revisionFiles.Count == 0) + { + var sha = Commit.SHA; + + Task.Run(() => + { + var files = new Commands.QueryRevisionFileNames(_repo.FullPath, sha).Result(); + + Dispatcher.UIThread.Invoke(() => { + if (sha == Commit.SHA) + { + _revisionFiles.Clear(); + _revisionFiles.AddRange(files); + UpdateRevisionFileSearchSuggestion(); + } + }); + }); + } + else + { + UpdateRevisionFileSearchSuggestion(); + } + } + else + { + IsRevisionFileSearchSuggestionOpen = false; + GC.Collect(); + } + } + } + } + + public AvaloniaList RevisionFileSearchSuggestion + { + get; + private set; + } = []; + + public bool IsRevisionFileSearchSuggestionOpen + { + get => _isRevisionFileSearchSuggestionOpen; + set => SetProperty(ref _isRevisionFileSearchSuggestionOpen, value); + } + public CommitDetail(Repository repo) { _repo = repo; @@ -147,17 +202,23 @@ namespace SourceGit.ViewModels { _repo = null; _commit = null; + if (_changes != null) _changes.Clear(); if (_visibleChanges != null) _visibleChanges.Clear(); if (_selectedChanges != null) _selectedChanges.Clear(); + _signInfo = null; _searchChangeFilter = null; _diffContext = null; _viewRevisionFileContent = null; _cancelToken = null; + + WebLinks.Clear(); + _revisionFiles.Clear(); + RevisionFileSearchSuggestion.Clear(); } public void NavigateTo(string commitSHA) @@ -175,6 +236,11 @@ namespace SourceGit.ViewModels SearchChangeFilter = string.Empty; } + public void ClearRevisionFileSearchFilter() + { + RevisionFileSearchFilter = string.Empty; + } + public Models.Commit GetParent(string sha) { return new Commands.QuerySingleCommit(_repo.FullPath, sha).Result(); @@ -543,6 +609,8 @@ namespace SourceGit.ViewModels private void Refresh() { _changes = null; + _revisionFiles.Clear(); + FullMessage = string.Empty; SignInfo = null; Changes = []; @@ -550,6 +618,8 @@ namespace SourceGit.ViewModels SelectedChanges = null; ViewRevisionFileContent = null; Children.Clear(); + RevisionFileSearchFilter = string.Empty; + IsRevisionFileSearchSuggestionOpen = false; if (_commit == null) return; @@ -716,6 +786,24 @@ namespace SourceGit.ViewModels menu.Items.Add(new MenuItem() { Header = "-" }); } + private void UpdateRevisionFileSearchSuggestion() + { + var suggestion = new List(); + foreach (var file in _revisionFiles) + { + if (file.Contains(_revisionFileSearchFilter, StringComparison.OrdinalIgnoreCase) && + file.Length != _revisionFileSearchFilter.Length) + suggestion.Add(file); + + if (suggestion.Count >= 100) + break; + } + + RevisionFileSearchSuggestion.Clear(); + RevisionFileSearchSuggestion.AddRange(suggestion); + IsRevisionFileSearchSuggestionOpen = suggestion.Count > 0; + } + [GeneratedRegex(@"^version https://git-lfs.github.com/spec/v\d+\r?\noid sha256:([0-9a-f]+)\r?\nsize (\d+)[\r\n]*$")] private static partial Regex REG_LFS_FORMAT(); @@ -736,5 +824,8 @@ namespace SourceGit.ViewModels private DiffContext _diffContext = null; private object _viewRevisionFileContent = null; private Commands.Command.CancelToken _cancelToken = null; + private List _revisionFiles = []; + private string _revisionFileSearchFilter = string.Empty; + private bool _isRevisionFileSearchSuggestionOpen = false; } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 92bfb288..514bdb6d 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -2147,7 +2147,7 @@ namespace SourceGit.ViewModels { Task.Run(() => { - var files = new Commands.QueryCurrentRevisionFiles(_fullpath).Result(); + var files = new Commands.QueryRevisionFileNames(_fullpath, "HEAD").Result(); Dispatcher.UIThread.Invoke(() => { if (_searchCommitFilterType != 3) diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs index af9beb7d..b671851c 100644 --- a/src/Views/RevisionFileTreeView.axaml.cs +++ b/src/Views/RevisionFileTreeView.axaml.cs @@ -144,6 +144,68 @@ namespace SourceGit.Views InitializeComponent(); } + public void SetSearchResult(string file) + { + _rows.Clear(); + _searchResult.Clear(); + + var rows = new List(); + if (string.IsNullOrEmpty(file)) + { + MakeRows(rows, _tree, 0); + } + else + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null || vm.Commit == null) + return; + + var objects = vm.GetRevisionFilesUnderFolder(file); + if (objects == null || objects.Count != 1) + return; + + var routes = file.Split('/', StringSplitOptions.None); + if (routes.Length == 1) + { + _searchResult.Add(new ViewModels.RevisionFileTreeNode + { + Backend = objects[0] + }); + } + else + { + var last = _searchResult; + var prefix = string.Empty; + for (var i = 0; i < routes.Length - 1; i++) + { + var folder = new ViewModels.RevisionFileTreeNode + { + Backend = new Models.Object + { + Type = Models.ObjectType.Tree, + Path = prefix + routes[i], + }, + IsExpanded = true, + }; + + last.Add(folder); + last = folder.Children; + prefix = folder.Backend + "/"; + } + + last.Add(new ViewModels.RevisionFileTreeNode + { + Backend = objects[0] + }); + } + + MakeRows(rows, _searchResult, 0); + } + + _rows.AddRange(rows); + GC.Collect(); + } + public void ToggleNodeIsExpanded(ViewModels.RevisionFileTreeNode node) { _disableSelectionChangingEvent = true; @@ -189,6 +251,7 @@ namespace SourceGit.Views { _tree.Clear(); _rows.Clear(); + _searchResult.Clear(); var vm = DataContext as ViewModels.CommitDetail; if (vm == null || vm.Commit == null) @@ -308,5 +371,6 @@ namespace SourceGit.Views private List _tree = []; private AvaloniaList _rows = []; private bool _disableSelectionChangingEvent = false; + private List _searchResult = []; } } diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index d0b20963..fdb15807 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -4,6 +4,7 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:vm="using:SourceGit.ViewModels" xmlns:v="using:SourceGit.Views" + xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.RevisionFiles" x:DataType="vm:CommitDetail"> @@ -14,17 +15,96 @@ - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs index 53c36b1c..007c58ef 100644 --- a/src/Views/RevisionFiles.axaml.cs +++ b/src/Views/RevisionFiles.axaml.cs @@ -3,6 +3,7 @@ using System; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Media; @@ -118,5 +119,85 @@ namespace SourceGit.Views { InitializeComponent(); } + + private void OnSearchBoxKeyDown(object _, KeyEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + if (e.Key == Key.Enter) + { + FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + e.Handled = true; + } + else if (e.Key == Key.Down || e.Key == Key.Up) + { + if (vm.IsRevisionFileSearchSuggestionOpen) + { + SearchSuggestionBox.Focus(NavigationMethod.Tab); + SearchSuggestionBox.SelectedIndex = 0; + } + + e.Handled = true; + } + else if (e.Key == Key.Escape) + { + if (vm.IsRevisionFileSearchSuggestionOpen) + { + vm.RevisionFileSearchSuggestion.Clear(); + vm.IsRevisionFileSearchSuggestionOpen = false; + } + + e.Handled = true; + } + } + + private void OnSearchBoxTextChanged(object _, TextChangedEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + if (string.IsNullOrEmpty(vm.RevisionFileSearchFilter)) + FileTree.SetSearchResult(null); + } + + private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + if (e.Key == Key.Escape) + { + vm.RevisionFileSearchSuggestion.Clear(); + e.Handled = true; + } + else if (e.Key == Key.Enter && SearchSuggestionBox.SelectedItem is string content) + { + vm.RevisionFileSearchFilter = content; + TxtSearchRevisionFiles.CaretIndex = content.Length; + FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + e.Handled = true; + } + } + + private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e) + { + var vm = DataContext as ViewModels.CommitDetail; + if (vm == null) + return; + + var content = (sender as StackPanel)?.DataContext as string; + if (!string.IsNullOrEmpty(content)) + { + vm.RevisionFileSearchFilter = content; + TxtSearchRevisionFiles.CaretIndex = content.Length; + FileTree.SetSearchResult(vm.RevisionFileSearchFilter); + } + + e.Handled = true; + } } } From a52977baf3581432de46b96974c42bd11d18e5bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 2 Dec 2024 13:44:50 +0000 Subject: [PATCH 03/24] doc: Update translation status and missing keys --- README.md | 2 +- TRANSLATION.md | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index a1d19e1f..51f11672 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.86%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.87%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.30%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.73%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.15%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.72%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.73%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.17%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.59%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.01%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.86%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) ## How to Use diff --git a/TRANSLATION.md b/TRANSLATION.md index 82be4441..93544ec6 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -1,19 +1,21 @@ -### de_DE.axaml: 99.86% +### de_DE.axaml: 99.72%
Missing Keys +- Text.CommitDetail.Files.Search - Text.WorkingCopy.CommitToEdit
-### es_ES.axaml: 97.87% +### es_ES.axaml: 97.73%
Missing Keys +- Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.Fetch.Force - Text.Preference.Appearance.FontSize @@ -32,7 +34,7 @@
-### fr_FR.axaml: 97.30% +### fr_FR.axaml: 97.17%
@@ -41,6 +43,7 @@ - Text.CherryPick.AppendSourceToMessage - Text.CherryPick.Mainline.Tips - Text.CommitCM.CherryPickMultiple +- Text.CommitDetail.Files.Search - Text.Fetch.Force - Text.Preference.Appearance.FontSize - Text.Preference.Appearance.FontSize.Default @@ -60,12 +63,13 @@
-### it_IT.axaml: 97.73% +### it_IT.axaml: 97.59%
Missing Keys +- Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.Configure.IssueTracker.AddSampleGitLabMergeRequest - Text.Configure.OpenAI.Preferred @@ -85,12 +89,13 @@
-### pt_BR.axaml: 99.15% +### pt_BR.axaml: 99.01%
Missing Keys +- Text.CommitDetail.Files.Search - Text.CommitDetail.Info.Children - Text.Fetch.Force - Text.Preference.General.ShowChildren @@ -100,13 +105,13 @@
-### ru_RU.axaml: 100.00% +### ru_RU.axaml: 99.86%
Missing Keys - +- Text.CommitDetail.Files.Search
From d1a1b4b2b9757406af7670276f1126caaa8e92e3 Mon Sep 17 00:00:00 2001 From: leo Date: Mon, 2 Dec 2024 21:50:50 +0800 Subject: [PATCH 04/24] enhance: do NOT show search suggestion if input string is empty (#775) --- src/ViewModels/CommitDetail.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index ec2822cd..2872528a 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -137,7 +137,9 @@ namespace SourceGit.ViewModels { _revisionFiles.Clear(); _revisionFiles.AddRange(files); - UpdateRevisionFileSearchSuggestion(); + + if (!string.IsNullOrEmpty(_revisionFileSearchFilter)) + UpdateRevisionFileSearchSuggestion(); } }); }); From 0160600c754b5e40e722bf8098d86c7713ba51ae Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 3 Dec 2024 09:25:53 +0800 Subject: [PATCH 05/24] revert: changes about `SystemAccentColor` (#776) This reverts commit db8ee3410bf19f6d81f6f0871bd6635efd8d1c7d. --- src/App.axaml.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/App.axaml.cs b/src/App.axaml.cs index a4e5bd5b..0615724a 100644 --- a/src/App.axaml.cs +++ b/src/App.axaml.cs @@ -164,7 +164,12 @@ namespace SourceGit var resDic = new ResourceDictionary(); var overrides = JsonSerializer.Deserialize(File.ReadAllText(themeOverridesFile), JsonCodeGen.Default.ThemeOverrides); foreach (var kv in overrides.BasicColors) - resDic[$"Color.{kv.Key}"] = kv.Value; + { + if (kv.Key.Equals("SystemAccentColor", StringComparison.Ordinal)) + resDic["SystemAccentColor"] = kv.Value; + else + resDic[$"Color.{kv.Key}"] = kv.Value; + } if (overrides.GraphColors.Count > 0) Models.CommitGraph.SetPens(overrides.GraphColors, overrides.GraphPenThickness); From ea0bec16dac1cc767d53089227b4f860c3b4b17d Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 3 Dec 2024 09:33:06 +0800 Subject: [PATCH 06/24] refactor: use control instead of DataContext to get input string --- src/Views/RevisionFiles.axaml.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Views/RevisionFiles.axaml.cs b/src/Views/RevisionFiles.axaml.cs index 007c58ef..f748fb0d 100644 --- a/src/Views/RevisionFiles.axaml.cs +++ b/src/Views/RevisionFiles.axaml.cs @@ -155,11 +155,7 @@ namespace SourceGit.Views private void OnSearchBoxTextChanged(object _, TextChangedEventArgs e) { - var vm = DataContext as ViewModels.CommitDetail; - if (vm == null) - return; - - if (string.IsNullOrEmpty(vm.RevisionFileSearchFilter)) + if (string.IsNullOrEmpty(TxtSearchRevisionFiles.Text)) FileTree.SetSearchResult(null); } From ca29a232e4d4eca75e78813ca8ede1c1b38b3544 Mon Sep 17 00:00:00 2001 From: leo Date: Tue, 3 Dec 2024 10:44:27 +0800 Subject: [PATCH 07/24] enhance: call gc after viewing commit changed --- src/ViewModels/CommitDetail.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ViewModels/CommitDetail.cs b/src/ViewModels/CommitDetail.cs index 2872528a..29de3946 100644 --- a/src/ViewModels/CommitDetail.cs +++ b/src/ViewModels/CommitDetail.cs @@ -623,6 +623,8 @@ namespace SourceGit.ViewModels RevisionFileSearchFilter = string.Empty; IsRevisionFileSearchSuggestionOpen = false; + GC.Collect(); + if (_commit == null) return; From abacccab000a00dcef10394df1c5596708d26561 Mon Sep 17 00:00:00 2001 From: stone-w4tch3r <100294019+stone-w4tch3r@users.noreply.github.com> Date: Wed, 4 Dec 2024 06:31:29 +0500 Subject: [PATCH 08/24] feat: Add libicu dependency to RPM spec file (#781) (cherry picked from commit f312895ef8c77098612645a54420539fef70a849) (cherry picked from commit 662149045f9b37e8b8842125c82d835625055150) --- build/resources/rpm/SPECS/build.spec | 1 + 1 file changed, 1 insertion(+) diff --git a/build/resources/rpm/SPECS/build.spec b/build/resources/rpm/SPECS/build.spec index d31b6587..bc10ca48 100644 --- a/build/resources/rpm/SPECS/build.spec +++ b/build/resources/rpm/SPECS/build.spec @@ -7,6 +7,7 @@ URL: https://sourcegit-scm.github.io/ Source: https://github.com/sourcegit-scm/sourcegit/archive/refs/tags/v%_version.tar.gz Requires: libX11.so.6()(%{__isa_bits}bit) Requires: libSM.so.6()(%{__isa_bits}bit) +Requires: libicu %define _build_id_links none From 43928936bcb0ff71e2f3cdb59b3d3a776b78b0b2 Mon Sep 17 00:00:00 2001 From: stone-w4tch3r <100294019+stone-w4tch3r@users.noreply.github.com> Date: Wed, 4 Dec 2024 06:57:30 +0500 Subject: [PATCH 09/24] Added repositories for deb/rpm packages on buildkite (#780) * added and used new github action for publishing packages to packagecloud * publish-packages.yml WIP * publish-packages.yml now uses curl push * fix naming from packagecloud to buildkite * Add debug logs to publish-packages.yml * fix package path * change repo name to sourcegit * fixed unused code * added --fail to curl pushes * Remove leftowers from release.yml --- .github/workflows/publish-packages.yml | 39 ++++++++++++++++++++++++++ .github/workflows/release.yml | 5 ++++ SourceGit.sln | 1 + 3 files changed, 45 insertions(+) create mode 100644 .github/workflows/publish-packages.yml diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 00000000..9e465fe7 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,39 @@ +name: Publish to Buildkite +on: + workflow_call: + secrets: + BUILDKITE_TOKEN: + required: true +jobs: + publish: + name: Publish to Buildkite + runs-on: ubuntu-latest + strategy: + matrix: + runtime: [linux-x64, linux-arm64] + steps: + - name: Download package artifacts + uses: actions/download-artifact@v4 + with: + name: package.${{ matrix.runtime }} + path: packages + + - name: Publish DEB package + env: + BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} + run: | + FILE=$(echo packages/*.deb) + curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-deb/packages \ + -H "Authorization: Bearer $BUILDKITE_TOKEN" \ + -F "file=@$FILE" \ + --fail + + - name: Publish RPM package + env: + BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} + run: | + FILE=$(echo packages/*.rpm) + curl -X POST https://api.buildkite.com/v2/packages/organizations/sourcegit/registries/sourcegit-rpm/packages \ + -H "Authorization: Bearer $BUILDKITE_TOKEN" \ + -F "file=@$FILE" \ + --fail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c19103e3..6a38c174 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,11 @@ jobs: uses: ./.github/workflows/package.yml with: version: ${{ needs.version.outputs.version }} + publish-packages: + name: Publish Packages + uses: ./.github/workflows/publish-packages.yml + secrets: + BUILDKITE_TOKEN: ${{ secrets.BUILDKITE_TOKEN }} release: needs: [package, version] name: Release diff --git a/SourceGit.sln b/SourceGit.sln index 9c5fcdb1..3eeb8a54 100644 --- a/SourceGit.sln +++ b/SourceGit.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\package.yml = .github\workflows\package.yml .github\workflows\release.yml = .github\workflows\release.yml .github\workflows\localization-check.yml = .github\workflows\localization-check.yml + .github\workflows\publish-packages.yml = .github\workflows\publish-packages.yml EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}" From d616d0897b3b68c49b90fa209bf1899c610671e3 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 4 Dec 2024 11:25:25 +0800 Subject: [PATCH 10/24] refactor: relative time display mode (#777) --- src/Views/Histories.axaml.cs | 46 +++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs index 7edcb295..fbb907d9 100644 --- a/src/Views/Histories.axaml.cs +++ b/src/Views/Histories.axaml.cs @@ -430,31 +430,43 @@ namespace SourceGit.Views if (ShowAsDateTime) return DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss"); - var today = DateTime.Today; + var now = DateTime.Now; var localTime = DateTime.UnixEpoch.AddSeconds(timestamp).ToLocalTime(); + var span = now - localTime; + if (span.TotalMinutes < 1) + return App.Text("Period.JustNow"); - if (localTime >= today) + if (span.TotalHours < 1) + return App.Text("Period.MinutesAgo", (int)span.TotalMinutes); + + if (span.TotalDays < 1) + return App.Text("Period.HoursAgo", (int)span.TotalHours); + + var lastDay = now.AddDays(-1).Date; + if (localTime >= lastDay) + return App.Text("Period.Yesterday"); + + if ((localTime.Year == now.Year && localTime.Month == now.Month) || span.TotalDays < 28) { - var now = DateTime.Now; - var timespan = now - localTime; - if (timespan.TotalHours > 1) - return App.Text("Period.HoursAgo", (int)timespan.TotalHours); - - return timespan.TotalMinutes < 1 ? App.Text("Period.JustNow") : App.Text("Period.MinutesAgo", (int)timespan.TotalMinutes); + var diffDay = now.Date - localTime.Date; + return App.Text("Period.DaysAgo", (int)diffDay.TotalDays); } - var diffYear = today.Year - localTime.Year; - if (diffYear == 0) - { - var diffMonth = today.Month - localTime.Month; - if (diffMonth > 0) - return diffMonth == 1 ? App.Text("Period.LastMonth") : App.Text("Period.MonthsAgo", diffMonth); + var lastMonth = now.AddMonths(-1).Date; + if (localTime.Year == lastMonth.Year && localTime.Month == lastMonth.Month) + return App.Text("Period.LastMonth"); - var diffDay = today.Day - localTime.Day; - return diffDay == 1 ? App.Text("Period.Yesterday") : App.Text("Period.DaysAgo", diffDay); + if (localTime.Year == now.Year || localTime > now.AddMonths(-11)) + { + var diffMonth = (12 + now.Month - localTime.Month) % 12; + return App.Text("Period.MonthsAgo", diffMonth); } - return diffYear == 1 ? App.Text("Period.LastYear") : App.Text("Period.YearsAgo", diffYear); + var diffYear = now.Year - localTime.Year; + if (diffYear == 1) + return App.Text("Period.LastYear"); + + return App.Text("Period.YearsAgo", diffYear); } private IDisposable _refreshTimer = null; From 1ddd348a4098f9f511ebd49c3c81f7c9733dce40 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 4 Dec 2024 18:04:57 +0800 Subject: [PATCH 11/24] feature: show tracking status in `Delete Branch` panel if possible (#785) --- src/ViewModels/DeleteBranch.cs | 7 +++++++ src/Views/DeleteBranch.axaml | 12 ++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/ViewModels/DeleteBranch.cs b/src/ViewModels/DeleteBranch.cs index e7136a0d..e23f4201 100644 --- a/src/ViewModels/DeleteBranch.cs +++ b/src/ViewModels/DeleteBranch.cs @@ -10,6 +10,12 @@ namespace SourceGit.ViewModels private set; } + public string TrackStatus + { + get; + private set; + } + public Models.Branch TrackingRemoteBranch { get; @@ -32,6 +38,7 @@ namespace SourceGit.ViewModels { _repo = repo; Target = branch; + TrackStatus = branch.TrackStatus.ToString(); if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream)) { diff --git a/src/Views/DeleteBranch.axaml b/src/Views/DeleteBranch.axaml index b2693bf0..bdaa9d8a 100644 --- a/src/Views/DeleteBranch.axaml +++ b/src/Views/DeleteBranch.axaml @@ -16,6 +16,18 @@ + + + From 75e9f1e9a4b711cce7ebb47f26d48bfcfb430471 Mon Sep 17 00:00:00 2001 From: leo Date: Wed, 4 Dec 2024 19:14:48 +0800 Subject: [PATCH 12/24] feature: show track status in `Delete Multiple Branches` panel (#785) --- src/Models/Branch.cs | 2 ++ src/ViewModels/DeleteBranch.cs | 7 ------- src/Views/DeleteBranch.axaml | 4 ++-- src/Views/DeleteMultipleBranches.axaml | 15 ++++++++++++++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Models/Branch.cs b/src/Models/Branch.cs index ac6b8c67..0ba320c1 100644 --- a/src/Models/Branch.cs +++ b/src/Models/Branch.cs @@ -7,6 +7,8 @@ namespace SourceGit.Models public List Ahead { get; set; } = new List(); public List Behind { get; set; } = new List(); + public bool IsVisible => Ahead.Count > 0 || Behind.Count > 0; + public override string ToString() { if (Ahead.Count == 0 && Behind.Count == 0) diff --git a/src/ViewModels/DeleteBranch.cs b/src/ViewModels/DeleteBranch.cs index e23f4201..e7136a0d 100644 --- a/src/ViewModels/DeleteBranch.cs +++ b/src/ViewModels/DeleteBranch.cs @@ -10,12 +10,6 @@ namespace SourceGit.ViewModels private set; } - public string TrackStatus - { - get; - private set; - } - public Models.Branch TrackingRemoteBranch { get; @@ -38,7 +32,6 @@ namespace SourceGit.ViewModels { _repo = repo; Target = branch; - TrackStatus = branch.TrackStatus.ToString(); if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream)) { diff --git a/src/Views/DeleteBranch.axaml b/src/Views/DeleteBranch.axaml index bdaa9d8a..05ec6e26 100644 --- a/src/Views/DeleteBranch.axaml +++ b/src/Views/DeleteBranch.axaml @@ -22,11 +22,11 @@ VerticalAlignment="Center" CornerRadius="9" Background="{DynamicResource Brush.Badge}" - IsVisible="{Binding TrackStatus, Converter={x:Static StringConverters.IsNotNullOrEmpty}}"> + IsVisible="{Binding Target.TrackStatus.IsVisible}"> + Text="{Binding Target.TrackStatus}"/> diff --git a/src/Views/DeleteMultipleBranches.axaml b/src/Views/DeleteMultipleBranches.axaml index 2a888118..cf084e14 100644 --- a/src/Views/DeleteMultipleBranches.axaml +++ b/src/Views/DeleteMultipleBranches.axaml @@ -42,9 +42,22 @@ - + + + + From e18d6d65e8623981efdf0c610720b437ab9bde10 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 5 Dec 2024 20:43:31 +0800 Subject: [PATCH 13/24] ux: style of MenuItem --- src/Resources/Styles.axaml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml index a42b2cb8..2cc09e39 100644 --- a/src/Resources/Styles.axaml +++ b/src/Resources/Styles.axaml @@ -856,12 +856,7 @@
- + Date: Fri, 6 Dec 2024 02:13:45 +0100 Subject: [PATCH 14/24] Update Spanish translation (#791) * Update spanish translation * doc: Update translation status and missing keys * Add missing key (cherry picked from commit 2bf0641323325bf97d1fac9ed225228e5015a3ba) * doc: Update translation status and missing keys --------- Co-authored-by: github-actions[bot] --- README.md | 2 +- TRANSLATION.md | 19 ++----------------- src/Resources/Locales/es_ES.axaml | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 51f11672..47785e73 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ## Translation Status -[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.72%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-97.73%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.17%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.59%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.01%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.86%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) +[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-99.72%25-yellow)](TRANSLATION.md) [![es__ES](https://img.shields.io/badge/es__ES-100.00%25-brightgreen)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-97.17%25-yellow)](TRANSLATION.md) [![it__IT](https://img.shields.io/badge/it__IT-97.59%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-99.01%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-99.86%25-yellow)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-100.00%25-brightgreen)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md) ## How to Use diff --git a/TRANSLATION.md b/TRANSLATION.md index 93544ec6..569a0222 100644 --- a/TRANSLATION.md +++ b/TRANSLATION.md @@ -9,28 +9,13 @@ -### es_ES.axaml: 97.73% +### es_ES.axaml: 100.00%
Missing Keys -- Text.CommitDetail.Files.Search -- Text.CommitDetail.Info.Children -- Text.Fetch.Force -- Text.Preference.Appearance.FontSize -- Text.Preference.Appearance.FontSize.Default -- Text.Preference.Appearance.FontSize.Editor -- Text.Preference.General.ShowChildren -- Text.Repository.FilterCommits -- Text.Repository.FilterCommits.Default -- Text.Repository.FilterCommits.Exclude -- Text.Repository.FilterCommits.Include -- Text.Repository.HistoriesOrder -- Text.Repository.HistoriesOrder.ByDate -- Text.Repository.HistoriesOrder.Topo -- Text.SHALinkCM.NavigateTo -- Text.WorkingCopy.CommitToEdit +
diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 563419be..7c7f8d7c 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -126,10 +126,12 @@ Buscar Cambios... ARCHIVOS Archivo LFS + Buscar Archivos... Submódulo INFORMACIÓN AUTOR CAMBIADO + HIJOS COMMITTER Ver refs que contienen este commit COMMIT ESTÁ CONTENIDO EN @@ -272,6 +274,7 @@ Fast-Forward (sin checkout) Fetch Fetch todos los remotos + Utilizar opción '--force' Fetch sin etiquetas Remoto: Fetch Cambios Remotos @@ -437,6 +440,9 @@ Servidor APARIENCIA Fuente por defecto + Tamaño de fuente + Por defecto + Editor Fuente Monospace Usar solo fuente monospace en el editor de texto Tema @@ -452,6 +458,7 @@ Idioma Commits en el historial Mostrar hora del autor en lugar de la hora del commit en el gráfico + Mostrar hijos en los detalles de commit Longitud de la guía del asunto GIT Habilitar Auto CRLF @@ -541,6 +548,13 @@ Habilitar Opción '--reflog' Abrir en el Explorador Buscar Ramas/Etiquetas/Submódulos + Visibilidad en el Gráfico + Desestablecer + Ocultar en el Gráfico de Commits + Filtrar en el Gráfico de Commits + Cambiar Modo de Ordenación + Fecha de Commit (--date-order) + Topológicamente (--topo-order) RAMAS LOCALES Navegar a HEAD Habilitar Opción '--first-parent' @@ -593,6 +607,7 @@ Actualización de Software Actualmente no hay actualizaciones disponibles. Copiar SHA + Ir a Squash Commits En: Clave Privada SSH: @@ -670,6 +685,7 @@ COMMIT & PUSH Plantilla/Historias Activar evento de clic + Commit (Editar) Stagear todos los cambios y commit ¡Commit vacío detectado! ¿Quieres continuar (--allow-empty)? CONFLICTOS DETECTADOS From c062f270816581210896b21f1120e2a2b19a65fe Mon Sep 17 00:00:00 2001 From: GadflyFang Date: Fri, 6 Dec 2024 15:09:14 +0800 Subject: [PATCH 15/24] fix: Dispose _autoFetchTimer before _setting set to null (#792) Signed-off-by: Gadfly --- src/ViewModels/Repository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index 514bdb6d..38855fef 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -427,12 +427,12 @@ namespace SourceGit.ViewModels { // Ignore } - _settings = null; - _historiesFilterMode = Models.FilterMode.None; - _autoFetchTimer.Dispose(); _autoFetchTimer = null; + _settings = null; + _historiesFilterMode = Models.FilterMode.None; + _watcher?.Dispose(); _histories.Cleanup(); _workingCopy.Cleanup(); From 655d71b0bc163e35f18eb510df45f53bfdfb4095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=B6ran=20W?= <44604769+goran-w@users.noreply.github.com> Date: Sun, 8 Dec 2024 10:50:33 +0100 Subject: [PATCH 16/24] Add navigation and highlighting of change-blocks in Text Diff (#616, #717) (#703) * Corrected misspelled local variable nextHigh(t)light * Implemented change-block navigation * Modified behavior of the Prev/Next Change buttons in DiffView toolbar. * Well-defined change-blocks are pre-calculated and can be navigated between. * Current change-block is highlighted in the Diff panel(s). * Prev/next at start/end of range (re-)scrolls to first/last change-block (I.e when unset, or already at first/last change-block, or at the only one.) * Current change-block is unset in RefreshContent(). * Added safeguards for edge cases * Added indicator of current/total change-blocks in DiffView toolbar * Added new Icon and String (en-US) for Highlighted Diff Navigation * Added Preference and ToggleButton for diff navigation style --- src/Commands/Diff.cs | 3 + src/Models/DiffResult.cs | 60 +++++++++++- src/Resources/Icons.axaml | 1 + src/Resources/Locales/en_US.axaml | 1 + src/ViewModels/DiffContext.cs | 73 +++++++++++++- src/ViewModels/Preference.cs | 7 ++ src/ViewModels/TwoSideTextDiff.cs | 33 +++++++ src/Views/DiffView.axaml | 27 +++++- src/Views/DiffView.axaml.cs | 44 ++++++--- src/Views/TextDiffView.axaml | 3 + src/Views/TextDiffView.axaml.cs | 156 ++++++++++++++++++++++++------ 11 files changed, 361 insertions(+), 47 deletions(-) diff --git a/src/Commands/Diff.cs b/src/Commands/Diff.cs index da971e58..f1bef7b7 100644 --- a/src/Commands/Diff.cs +++ b/src/Commands/Diff.cs @@ -51,6 +51,9 @@ namespace SourceGit.Commands _result.TextDiff.MaxLineNumber = Math.Max(_newLine, _oldLine); } + if (_result.TextDiff != null) + _result.TextDiff.ProcessChangeBlocks(); + return _result; } diff --git a/src/Models/DiffResult.cs b/src/Models/DiffResult.cs index e0ae82e0..afe22ad4 100644 --- a/src/Models/DiffResult.cs +++ b/src/Models/DiffResult.cs @@ -2,6 +2,8 @@ using System.Text; using System.Text.RegularExpressions; +using CommunityToolkit.Mvvm.ComponentModel; + using Avalonia; using Avalonia.Media.Imaging; @@ -59,16 +61,70 @@ namespace SourceGit.Models } } - public partial class TextDiff + public class TextDiffChangeBlock + { + public TextDiffChangeBlock(int startLine, int endLine) + { + StartLine = startLine; + EndLine = endLine; + } + + public int StartLine { get; set; } = 0; + public int EndLine { get; set; } = 0; + + public bool IsInRange(int line) + { + return line >= StartLine && line <= EndLine; + } + } + + public partial class TextDiff : ObservableObject { public string File { get; set; } = string.Empty; public List Lines { get; set; } = new List(); public Vector ScrollOffset { get; set; } = Vector.Zero; public int MaxLineNumber = 0; + public int CurrentChangeBlockIdx + { + get => _currentChangeBlockIdx; + set => SetProperty(ref _currentChangeBlockIdx, value); + } + public string Repo { get; set; } = null; public DiffOption Option { get; set; } = null; + public List ChangeBlocks { get; set; } = []; + + public void ProcessChangeBlocks() + { + ChangeBlocks.Clear(); + int lineIdx = 0, blockStartIdx = 0; + bool isNewBlock = true; + foreach (var line in Lines) + { + lineIdx++; + if (line.Type == Models.TextDiffLineType.Added || + line.Type == Models.TextDiffLineType.Deleted || + line.Type == Models.TextDiffLineType.None) // Empty + { + if (isNewBlock) + { + isNewBlock = false; + blockStartIdx = lineIdx; + } + } + else + { + if (!isNewBlock) + { + ChangeBlocks.Add(new TextDiffChangeBlock(blockStartIdx, lineIdx - 1)); + isNewBlock = true; + } + } + } + } + public TextDiffSelection MakeSelection(int startLine, int endLine, bool isCombined, bool isOldSide) { var rs = new TextDiffSelection(); @@ -626,6 +682,8 @@ namespace SourceGit.Models return true; } + private int _currentChangeBlockIdx = -1; // NOTE: Use -1 as "not set". + [GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")] private static partial Regex REG_INDICATOR(); } diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml index 8d66a250..b2c347ba 100644 --- a/src/Resources/Icons.axaml +++ b/src/Resources/Icons.axaml @@ -54,6 +54,7 @@ M30 271l241 0 0-241-241 0 0 241zM392 271l241 0 0-241-241 0 0 241zM753 30l0 241 241 0 0-241-241 0zM30 632l241 0 0-241-241 0 0 241zM392 632l241 0 0-241-241 0 0 241zM753 632l241 0 0-241-241 0 0 241zM30 994l241 0 0-241-241 0 0 241zM392 994l241 0 0-241-241 0 0 241zM753 994l241 0 0-241-241 0 0 241z M0 512M1024 512M512 0M512 1024M955 323q0 23-16 39l-414 414-78 78q-16 16-39 16t-39-16l-78-78-207-207q-16-16-16-39t16-39l78-78q16-16 39-16t39 16l168 169 375-375q16-16 39-16t39 16l78 78q16 16 16 39z M416 64H768v64h-64v704h64v64H448v-64h64V512H416a224 224 0 1 1 0-448zM576 832h64V128H576v704zM416 128H512v320H416a160 160 0 0 1 0-320z + M20.2585648,2.00438474 C20.6382605,2.00472706 20.9518016,2.28716326 21.0011348,2.65328337 L21.0078899,2.75506004 L21.0038407,7.25276883 C21.0009137,8.40908568 20.1270954,9.36072944 19.0029371,9.48671858 L19.0024932,11.7464847 C19.0024932,12.9373487 18.0773316,13.9121296 16.906542,13.9912939 L16.7524932,13.9964847 L16.501,13.9963847 L16.5017549,16.7881212 C16.5017549,17.6030744 16.0616895,18.349347 15.3600767,18.7462439 L15.2057929,18.8258433 L8.57108142,21.9321389 C8.10484975,22.1504232 7.57411944,21.8450614 7.50959937,21.3535767 L7.50306874,21.2528982 L7.503,13.9963847 L7.25,13.9964847 C6.05913601,13.9964847 5.08435508,13.0713231 5.00519081,11.9005335 L5,11.7464847 L5.00043957,9.4871861 C3.92882124,9.36893736 3.08392302,8.49812196 3.0058865,7.41488149 L3,7.25086975 L3,2.75438506 C3,2.3401715 3.33578644,2.00438474 3.75,2.00438474 C4.12969577,2.00438474 4.44349096,2.28653894 4.49315338,2.6526145 L4.5,2.75438506 L4.5,7.25086975 C4.5,7.63056552 4.78215388,7.94436071 5.14822944,7.99402313 L5.25,8.00086975 L18.7512697,8.00087075 C19.1315998,8.00025031 19.4461483,7.71759877 19.4967392,7.3518545 L19.5038434,7.25019537 L19.5078902,2.75371008 C19.508263,2.33949668 19.8443515,2.00401258 20.2585648,2.00438474 Z M15.001,13.9963847 L9.003,13.9963847 L9.00306874,20.0736262 L14.5697676,17.4673619 C14.8004131,17.3593763 14.9581692,17.1431606 14.9940044,16.89581 L15.0017549,16.7881212 L15.001,13.9963847 Z M17.502,9.50038474 L6.5,9.50038474 L6.5,11.7464847 C6.5,12.1261805 6.78215388,12.4399757 7.14822944,12.4896381 L7.25,12.4964847 L16.7524932,12.4964847 C17.1321889,12.4964847 17.4459841,12.2143308 17.4956465,11.8482552 L17.5024932,11.7464847 L17.502,9.50038474 Z M24 512A488 488 0 01512 24A488 488 0 011000 512A488 488 0 01512 1000A488 488 0 0124 512zm447-325v327L243 619l51 111 300-138V187H471z M832 64h128v278l-128-146V64zm64 448L512 73 128 512H0L448 0h128l448 512h-128zm0 83V1024H640V704c0-35-29-64-64-64h-128a64 64 0 00-64 64v320H128V595l384-424 384 424z M512 0C229 0 0 229 0 512c0 283 229 512 512 512s512-229 512-512c0-283-229-512-512-512zm0 958C266 958 66 758 66 512S266 66 512 66 958 266 958 512 758 958 512 958zM192 416h96a32 32 0 0032-32v-32a32 32 0 00-32-32H192a32 32 0 00-32 32v32a32 32 0 0032 32zM384 416h96a32 32 0 0032-32v-32a32 32 0 00-32-32h-96a32 32 0 00-32 32v32a32 32 0 0032 32zM576 416h96a32 32 0 0032-32v-32a32 32 0 00-32-32h-96a32 32 0 00-32 32v32a32 32 0 0032 32zM832 320h-64a32 32 0 00-32 32v128h-160a32 32 0 00-32 32v32a32 32 0 0032 32h256a32 32 0 0032-32v-192a32 32 0 00-32-32zM320 544v-32a32 32 0 00-32-32H192a32 32 0 00-32 32v32a32 32 0 0032 32h96a32 32 0 0032-32zM384 576h96a32 32 0 0032-32v-32a32 32 0 00-32-32h-96a32 32 0 00-32 32v32a32 32 0 0032 32zM800 640H256a32 32 0 00-32 32v32a32 32 0 0032 32h544a32 32 0 0032-32v-32a32 32 0 00-32-32z diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 6bea386d..792bc9b8 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -233,6 +233,7 @@ NEW OLD Copy + Highlighted Diff Navigation File Mode Changed Ignore Whitespace Change LFS OBJECT CHANGE diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs index 87b1a6de..6e6e9fb5 100644 --- a/src/ViewModels/DiffContext.cs +++ b/src/ViewModels/DiffContext.cs @@ -51,6 +51,12 @@ namespace SourceGit.ViewModels private set => SetProperty(ref _unifiedLines, value); } + public string ChangeBlockIndicator + { + get => _changeBlockIndicator; + private set => SetProperty(ref _changeBlockIndicator, value); + } + public DiffContext(string repo, Models.DiffOption option, DiffContext previous = null) { _repo = repo; @@ -73,12 +79,68 @@ namespace SourceGit.ViewModels LoadDiffContent(); } + public void PrevChange() + { + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx > 0) + { + textDiff.CurrentChangeBlockIdx--; + } + else if (textDiff.ChangeBlocks.Count > 0) + { + // Force property value change and (re-)jump to first change block + textDiff.CurrentChangeBlockIdx = -1; + textDiff.CurrentChangeBlockIdx = 0; + } + } + RefreshChangeBlockIndicator(); + } + + public void NextChange() + { + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx < textDiff.ChangeBlocks.Count - 1) + { + textDiff.CurrentChangeBlockIdx++; + } + else if (textDiff.ChangeBlocks.Count > 0) + { + // Force property value change and (re-)jump to last change block + textDiff.CurrentChangeBlockIdx = -1; + textDiff.CurrentChangeBlockIdx = textDiff.ChangeBlocks.Count - 1; + } + RefreshChangeBlockIndicator(); + } + } + + public void RefreshChangeBlockIndicator() + { + string curr = "-", tot = "-"; + if (_content is Models.TextDiff textDiff) + { + if (textDiff.CurrentChangeBlockIdx >= 0) + curr = (textDiff.CurrentChangeBlockIdx + 1).ToString(); + tot = (textDiff.ChangeBlocks.Count).ToString(); + } + ChangeBlockIndicator = curr + "/" + tot; + } + public void ToggleFullTextDiff() { Preference.Instance.UseFullTextDiff = !Preference.Instance.UseFullTextDiff; LoadDiffContent(); } + public void ToggleHighlightedDiffNavigation() + { + Preference.Instance.EnableChangeBlocks = !Preference.Instance.EnableChangeBlocks; + if (_content is Models.TextDiff textDiff) + textDiff.CurrentChangeBlockIdx = -1; + RefreshChangeBlockIndicator(); + } + public void IncrUnified() { UnifiedLines = _unifiedLines + 1; @@ -91,6 +153,12 @@ namespace SourceGit.ViewModels LoadDiffContent(); } + public void ToggleTwoSideDiff() + { + Preference.Instance.UseSideBySideDiff = !Preference.Instance.UseSideBySideDiff; + RefreshChangeBlockIndicator(); + } + public void OpenExternalMergeTool() { var toolType = Preference.Instance.ExternalMergeToolType; @@ -217,7 +285,9 @@ namespace SourceGit.ViewModels FileModeChange = latest.FileModeChange; Content = rs; IsTextDiff = rs is Models.TextDiff; - }); + + RefreshChangeBlockIndicator(); + }); }); } @@ -281,6 +351,7 @@ namespace SourceGit.ViewModels private string _title; private string _fileModeChange = string.Empty; private int _unifiedLines = 4; + private string _changeBlockIndicator = "-/-"; private bool _isTextDiff = false; private bool _ignoreWhitespace = false; private object _content = null; diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 1ccee4cd..8d09272f 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -206,6 +206,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _useFullTextDiff, value); } + public bool EnableChangeBlocks + { + get => _enableChangeBlocks; + set => SetProperty(ref _enableChangeBlocks, value); + } + public Models.ChangeViewMode UnstagedChangeViewMode { get => _unstagedChangeViewMode; @@ -614,6 +620,7 @@ namespace SourceGit.ViewModels private bool _enableDiffViewWordWrap = false; private bool _showHiddenSymbolsInDiffView = false; private bool _useFullTextDiff = false; + private bool _enableChangeBlocks = false; private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List; diff --git a/src/ViewModels/TwoSideTextDiff.cs b/src/ViewModels/TwoSideTextDiff.cs index 3fb1e63b..493174e0 100644 --- a/src/ViewModels/TwoSideTextDiff.cs +++ b/src/ViewModels/TwoSideTextDiff.cs @@ -45,10 +45,43 @@ namespace SourceGit.ViewModels FillEmptyLines(); + ProcessChangeBlocks(); + if (previous != null && previous.File == File) _syncScrollOffset = previous._syncScrollOffset; } + public List ChangeBlocks { get; set; } = []; + + public void ProcessChangeBlocks() + { + ChangeBlocks.Clear(); + int lineIdx = 0, blockStartIdx = 0; + bool isNewBlock = true; + foreach (var line in Old) // NOTE: Same block size in both Old and New lines. + { + lineIdx++; + if (line.Type == Models.TextDiffLineType.Added || + line.Type == Models.TextDiffLineType.Deleted || + line.Type == Models.TextDiffLineType.None) // Empty + { + if (isNewBlock) + { + isNewBlock = false; + blockStartIdx = lineIdx; + } + } + else + { + if (!isNewBlock) + { + ChangeBlocks.Add(new Models.TextDiffChangeBlock(blockStartIdx, lineIdx - 1)); + isNewBlock = true; + } + } + } + } + public void ConvertsToCombinedRange(Models.TextDiff combined, ref int startLine, ref int endLine, bool isOldSide) { endLine = Math.Min(endLine, combined.Lines.Count - 1); diff --git a/src/Views/DiffView.axaml b/src/Views/DiffView.axaml index e0627ad8..bf7b0339 100644 --- a/src/Views/DiffView.axaml +++ b/src/Views/DiffView.axaml @@ -34,6 +34,15 @@ + + + + + + + + + + + + + - - - + + + - + - - + + + +
+ + + +