From 588dd2724d22559e6fcc27ccac786c473359fe7a Mon Sep 17 00:00:00 2001 From: JC-Chung <52159296+JC-Chung@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:08:37 +0800 Subject: [PATCH] feat: indicate history filters with no matching branch/tag --- src/Converters/FilterModeConverters.cs | 22 +++++++- src/Models/HistoryFilter.cs | 12 +++- src/ViewModels/Repository.cs | 78 ++++++++++++++++++++++++++ src/Views/Repository.axaml | 7 ++- 4 files changed, 116 insertions(+), 3 deletions(-) diff --git a/src/Converters/FilterModeConverters.cs b/src/Converters/FilterModeConverters.cs index 016613e83..535716cbb 100644 --- a/src/Converters/FilterModeConverters.cs +++ b/src/Converters/FilterModeConverters.cs @@ -1,4 +1,6 @@ -using Avalonia.Data.Converters; +using System.Collections.Generic; + +using Avalonia.Data.Converters; using Avalonia.Media; namespace SourceGit.Converters @@ -15,5 +17,23 @@ public static class FilterModeConverters _ => Brushes.Transparent, }; }); + + public static readonly IMultiValueConverter ToBorderBrushWithMatchState = + new FuncMultiValueConverter(values => + { + var list = new List(values); + if (list.Count < 2 || list[0] is not Models.FilterMode mode) + return Brushes.Transparent; + + if (list[1] is true) + return Brushes.Gray; + + return mode switch + { + Models.FilterMode.Included => Brushes.Green, + Models.FilterMode.Excluded => Brushes.Red, + _ => Brushes.Transparent, + }; + }); } } diff --git a/src/Models/HistoryFilter.cs b/src/Models/HistoryFilter.cs index b09f074cb..9c29e4cae 100644 --- a/src/Models/HistoryFilter.cs +++ b/src/Models/HistoryFilter.cs @@ -1,4 +1,6 @@ -using CommunityToolkit.Mvvm.ComponentModel; +using System.Text.Json.Serialization; + +using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.Models { @@ -43,6 +45,13 @@ public bool IsBranch get => Type != FilterType.Tag; } + [JsonIgnore] + public bool HasNoMatch + { + get => _hasNoMatch; + set => SetProperty(ref _hasNoMatch, value); + } + public HistoryFilter() { } @@ -56,5 +65,6 @@ public HistoryFilter(string pattern, FilterType type, FilterMode mode) private string _pattern = string.Empty; private FilterMode _mode = FilterMode.None; + private bool _hasNoMatch = false; } } diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index e72b6d2d4..d98f9ca60 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -1168,6 +1168,7 @@ public void RefreshBranches() CurrentBranch = branches.Find(x => x.IsCurrent); LocalBranchTrees = builder.Locals; RemoteBranchTrees = builder.Remotes; + UpdateBranchHistoryFilterMatchState(); var localBranchesCount = 0; foreach (var b in branches) @@ -1214,6 +1215,7 @@ public void RefreshTags() Tags = tags; VisibleTags = BuildVisibleTags(); + UpdateTagHistoryFilterMatchState(); }); }, token); } @@ -1788,6 +1790,10 @@ private void RefreshHistoryFilters(bool refresh) UpdateBranchTreeFilterMode(LocalBranchTrees, map); UpdateBranchTreeFilterMode(RemoteBranchTrees, map); UpdateTagFilterMode(map); + + UpdateBranchHistoryFilterMatchState(); + UpdateTagHistoryFilterMatchState(); + RefreshCommits(); } @@ -1816,6 +1822,78 @@ private void UpdateTagFilterMode(Dictionary map) } } + private void UpdateBranchHistoryFilterMatchState() + { + var branchPaths = new HashSet(StringComparer.Ordinal); + CollectBranchPaths(LocalBranchTrees, branchPaths); + CollectBranchPaths(RemoteBranchTrees, branchPaths); + + foreach (var filter in _uiStates.HistoryFilters) + { + switch (filter.Type) + { + case Models.FilterType.LocalBranch: + case Models.FilterType.RemoteBranch: + filter.HasNoMatch = !branchPaths.Contains(filter.Pattern); + break; + case Models.FilterType.LocalBranchFolder: + case Models.FilterType.RemoteBranchFolder: + var prefix = filter.Pattern + "/"; + var hasChild = false; + foreach (var p in branchPaths) + { + if (p.StartsWith(prefix, StringComparison.Ordinal)) + { + hasChild = true; + break; + } + } + filter.HasNoMatch = !hasChild; + break; + } + } + } + + private void UpdateTagHistoryFilterMatchState() + { + var tagNames = new HashSet(StringComparer.Ordinal); + if (VisibleTags is TagCollectionAsTree tree) + CollectTagNamesRecursive(tree.Tree, tagNames); + else if (VisibleTags is TagCollectionAsList list) + { + foreach (var item in list.TagItems) + tagNames.Add(item.Tag.Name); + } + + foreach (var filter in _uiStates.HistoryFilters) + { + if (filter.Type == Models.FilterType.Tag) + filter.HasNoMatch = !tagNames.Contains(filter.Pattern); + } + } + + private void CollectBranchPaths(List nodes, HashSet set) + { + foreach (var node in nodes) + { + if (node.IsBranch) + set.Add(node.Path); + else + CollectBranchPaths(node.Children, set); + } + } + + private static void CollectTagNamesRecursive(List nodes, HashSet set) + { + foreach (var node in nodes) + { + if (node.IsFolder) + CollectTagNamesRecursive(node.Children, set); + else + set.Add(node.FullPath); + } + } + private void ResetBranchTreeFilterMode(List nodes) { foreach (var node in nodes) diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index cda8c45e0..729264456 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -952,8 +952,13 @@ Margin="0,0,6,0" CornerRadius="12" BorderThickness="1" - BorderBrush="{Binding Mode, Converter={x:Static c:FilterModeConverters.ToBorderBrush}}" VerticalAlignment="Center"> + + + + + +