From e7a1d3109fa9fd120591257543d638b695300a0d Mon Sep 17 00:00:00 2001 From: Sebastian Janus Date: 2025年7月24日 00:11:29 +0200 Subject: [PATCH 1/4] Sort by Status base feature --- src/Models/Change.cs | 6 ++ src/Resources/Locales/en_US.axaml | 2 + src/ViewModels/ChangeTreeNode.cs | 73 ++++++++++++++++++--- src/ViewModels/Preferences.cs | 29 ++++++++ src/Views/BranchCompare.axaml | 4 +- src/Views/ChangeCollectionView.axaml.cs | 80 ++++++++++++++++++++++- src/Views/ChangeViewModeSwitcher.axaml | 11 ++++ src/Views/ChangeViewModeSwitcher.axaml.cs | 21 ++++++ src/Views/CommitChanges.axaml | 4 +- src/Views/RevisionCompare.axaml | 4 +- src/Views/StashesPage.axaml | 4 +- src/Views/WorkingCopy.axaml | 8 ++- 12 files changed, 229 insertions(+), 17 deletions(-) diff --git a/src/Models/Change.cs b/src/Models/Change.cs index 2ad448add..fde316da2 100644 --- a/src/Models/Change.cs +++ b/src/Models/Change.cs @@ -7,6 +7,12 @@ public enum ChangeViewMode Tree, } + public enum ChangeSortMode + { + Path, + Status, + } + public enum ChangeState { None, diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index 655f6e976..1c882266c 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -83,6 +83,8 @@ Show as File and Dir List Show as Path List Show as Filesystem Tree + Sort by Path + Sort by Status Change Submodule's URL Submodule: URL: diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs index 4d71a153b..5c3b4a3e0 100644 --- a/src/ViewModels/ChangeTreeNode.cs +++ b/src/ViewModels/ChangeTreeNode.cs @@ -47,7 +47,7 @@ public ChangeTreeNode(string path, bool isExpanded, int depth) IsExpanded = isExpanded; } - public static List Build(IList changes, HashSet folded) + public static List Build(IList changes, HashSet folded, Models.ChangeSortMode sortMode = Models.ChangeSortMode.Path) { var nodes = new List(); var folders = new Dictionary(); @@ -93,7 +93,7 @@ public static List Build(IList changes, HashSet collection, ChangeTreeNode collection.Add(subFolder); } - private static void Sort(List nodes) + private static void Sort(List nodes, Models.ChangeSortMode sortMode) { foreach (var node in nodes) { if (node.IsFolder) - Sort(node.Children); + Sort(node.Children, sortMode); } - nodes.Sort((l, r) => + if (sortMode == Models.ChangeSortMode.Status) { - if (l.IsFolder == r.IsFolder) + nodes.Sort((l, r) => + { + // Sort folders first + if (l.IsFolder != r.IsFolder) + return l.IsFolder ? -1 : 1; + + // If both are folders, sort by path + if (l.IsFolder && r.IsFolder) + return Models.NumericSort.Compare(l.FullPath, r.FullPath); + + // For files, sort by status first + var leftPriority = GetStatusSortPriority(l.Change); + var rightPriority = GetStatusSortPriority(r.Change); + + if (leftPriority != rightPriority) + return leftPriority.CompareTo(rightPriority); + + // Same status, sort by path return Models.NumericSort.Compare(l.FullPath, r.FullPath); - return l.IsFolder ? -1 : 1; - }); + }); + } + else + { + nodes.Sort((l, r) => + { + if (l.IsFolder == r.IsFolder) + return Models.NumericSort.Compare(l.FullPath, r.FullPath); + return l.IsFolder ? -1 : 1; + }); + } + } + + private static int GetStatusSortPriority(Models.Change change) + { + if (change == null) return int.MaxValue; + + // Prioritize Index (staged) changes first, then WorkTree (unstaged) changes + var indexState = change.Index; + var workTreeState = change.WorkTree; + + // For staged changes (Index state) + if (indexState != Models.ChangeState.None) + { + return indexState switch + { + Models.ChangeState.Modified => 1, + Models.ChangeState.Renamed => 2, + Models.ChangeState.Added => 3, + Models.ChangeState.Deleted => 4, + _ => 5 + }; + } + + // For unstaged changes (WorkTree state) + return workTreeState switch + { + Models.ChangeState.Modified => 10, + Models.ChangeState.Deleted => 11, // Missing files + Models.ChangeState.Untracked => 12, // Untracked files + _ => 20 + }; } private bool _isExpanded = true; diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 05be06010..0a2a975d9 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -291,6 +291,30 @@ public Models.ChangeViewMode StashChangeViewMode set => SetProperty(ref _stashChangeViewMode, value); } + public Models.ChangeSortMode UnstagedChangeSortMode + { + get => _unstagedChangeSortMode; + set => SetProperty(ref _unstagedChangeSortMode, value); + } + + public Models.ChangeSortMode StagedChangeSortMode + { + get => _stagedChangeSortMode; + set => SetProperty(ref _stagedChangeSortMode, value); + } + + public Models.ChangeSortMode CommitChangeSortMode + { + get => _commitChangeSortMode; + set => SetProperty(ref _commitChangeSortMode, value); + } + + public Models.ChangeSortMode StashChangeSortMode + { + get => _stashChangeSortMode; + set => SetProperty(ref _stashChangeSortMode, value); + } + public string GitInstallPath { get => Native.OS.GitExecutable; @@ -715,6 +739,11 @@ private bool RemoveInvalidRepositoriesRecursive(List collection) private Models.ChangeViewMode _commitChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stashChangeViewMode = Models.ChangeViewMode.List; + private Models.ChangeSortMode _unstagedChangeSortMode = Models.ChangeSortMode.Path; + private Models.ChangeSortMode _stagedChangeSortMode = Models.ChangeSortMode.Path; + private Models.ChangeSortMode _commitChangeSortMode = Models.ChangeSortMode.Path; + private Models.ChangeSortMode _stashChangeSortMode = Models.ChangeSortMode.Path; + private string _gitDefaultCloneDir = string.Empty; private int _shellOrTerminal = -1; diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml index b80fefb5d..b77347b0b 100644 --- a/src/Views/BranchCompare.axaml +++ b/src/Views/BranchCompare.axaml @@ -119,12 +119,14 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeSortMode, Mode=TwoWay}"/> diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 8b9f90586..9d015f9c6 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -109,6 +109,15 @@ public Models.ChangeViewMode ViewMode set => SetValue(ViewModeProperty, value); } + public static readonly StyledProperty SortModeProperty = + AvaloniaProperty.Register(nameof(SortMode), Models.ChangeSortMode.Path); + + public Models.ChangeSortMode SortMode + { + get => GetValue(SortModeProperty); + set => SetValue(SortModeProperty, value); + } + public static readonly StyledProperty> ChangesProperty = AvaloniaProperty.Register>(nameof(Changes)); @@ -250,6 +259,8 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang if (change.Property == ViewModeProperty) UpdateDataSource(true); + else if (change.Property == SortModeProperty) + UpdateDataSource(true); else if (change.Property == ChangesProperty) UpdateDataSource(false); else if (change.Property == SelectedChangesProperty) @@ -384,7 +395,7 @@ private void UpdateDataSource(bool onlyViewModeChange) } var tree = new ViewModels.ChangeCollectionAsTree(); - tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded); + tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded, SortMode); var rows = new List(); MakeTreeRows(rows, tree.Tree); @@ -408,7 +419,8 @@ private void UpdateDataSource(bool onlyViewModeChange) else if (ViewMode == Models.ChangeViewMode.Grid) { var grid = new ViewModels.ChangeCollectionAsGrid(); - grid.Changes.AddRange(changes); + var sortedChanges = SortChanges(changes, SortMode); + grid.Changes.AddRange(sortedChanges); if (selected.Count> 0) grid.SelectedChanges.AddRange(selected); @@ -417,7 +429,8 @@ private void UpdateDataSource(bool onlyViewModeChange) else { var list = new ViewModels.ChangeCollectionAsList(); - list.Changes.AddRange(changes); + var sortedChanges = SortChanges(changes, SortMode); + list.Changes.AddRange(sortedChanges); if (selected.Count> 0) list.SelectedChanges.AddRange(selected); @@ -497,6 +510,67 @@ private void UpdateRowTips(Control control, Models.Change change) ToolTip.SetTip(control, tip); } + private List SortChanges(List changes, Models.ChangeSortMode sortMode) + { + var sortedChanges = new List(changes); + + if (sortMode == Models.ChangeSortMode.Status) + { + sortedChanges.Sort((l, r) => + { + var leftPriority = GetStatusSortPriority(l); + var rightPriority = GetStatusSortPriority(r); + + if (leftPriority != rightPriority) + return leftPriority.CompareTo(rightPriority); + + // Same status, sort by path + return Models.NumericSort.Compare(l.Path, r.Path); + }); + } + else + { + sortedChanges.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); + } + + return sortedChanges; + } + + private int GetStatusSortPriority(Models.Change change) + { + // Prioritize Index (staged) changes first, then WorkTree (unstaged) changes + var indexState = change.Index; + var workTreeState = change.WorkTree; + + // For staged changes (Index state) + if (indexState != Models.ChangeState.None) + { + return indexState switch + { + Models.ChangeState.Modified => 1, + Models.ChangeState.TypeChanged => 2, + Models.ChangeState.Renamed => 3, + Models.ChangeState.Copied => 4, + Models.ChangeState.Added => 5, + Models.ChangeState.Deleted => 6, + _ => 9 + }; + } + + // For unstaged changes (WorkTree state) + return workTreeState switch + { + Models.ChangeState.Conflicted => 10, // Conflicts first - most urgent + Models.ChangeState.Modified => 11, + Models.ChangeState.TypeChanged => 12, + Models.ChangeState.Deleted => 13, // Missing files + Models.ChangeState.Renamed => 14, + Models.ChangeState.Copied => 15, + Models.ChangeState.Untracked => 16, // New files last + _ => 20 + }; + } + private bool _disableSelectionChangingEvent = false; } } diff --git a/src/Views/ChangeViewModeSwitcher.axaml b/src/Views/ChangeViewModeSwitcher.axaml index b72fcbcb8..31c19fb9e 100644 --- a/src/Views/ChangeViewModeSwitcher.axaml +++ b/src/Views/ChangeViewModeSwitcher.axaml @@ -24,6 +24,17 @@ + + + + + + + + + + + diff --git a/src/Views/ChangeViewModeSwitcher.axaml.cs b/src/Views/ChangeViewModeSwitcher.axaml.cs index ed3066196..ef70411b1 100644 --- a/src/Views/ChangeViewModeSwitcher.axaml.cs +++ b/src/Views/ChangeViewModeSwitcher.axaml.cs @@ -15,6 +15,15 @@ public Models.ChangeViewMode ViewMode set => SetValue(ViewModeProperty, value); } + public static readonly StyledProperty SortModeProperty = + AvaloniaProperty.Register(nameof(SortMode)); + + public Models.ChangeSortMode SortMode + { + get => GetValue(SortModeProperty); + set => SetValue(SortModeProperty, value); + } + public ChangeViewModeSwitcher() { InitializeComponent(); @@ -37,5 +46,17 @@ private void SwitchToTree(object sender, RoutedEventArgs e) ViewMode = Models.ChangeViewMode.Tree; e.Handled = true; } + + private void SortByPath(object sender, RoutedEventArgs e) + { + SortMode = Models.ChangeSortMode.Path; + e.Handled = true; + } + + private void SortByStatus(object sender, RoutedEventArgs e) + { + SortMode = Models.ChangeSortMode.Status; + e.Handled = true; + } } } diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 4a1d95bcf..48abd5216 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -41,13 +41,15 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeSortMode, Mode=TwoWay}"/> diff --git a/src/Views/RevisionCompare.axaml b/src/Views/RevisionCompare.axaml index b19bba554..57717c6f2 100644 --- a/src/Views/RevisionCompare.axaml +++ b/src/Views/RevisionCompare.axaml @@ -91,12 +91,14 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeSortMode, Mode=TwoWay}"/> diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 36d3d0881..3e3c2c8e8 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -121,13 +121,15 @@ Width="14" Height="14" Margin="0,0,8,0" HorizontalAlignment="Right" - ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StashChangeViewMode, Mode=TwoWay}"/> + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StashChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StashChangeSortMode, Mode=TwoWay}"/> diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 99505c9fb..d3ca0ec25 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -118,7 +118,8 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeSortMode, Mode=TwoWay}"/> @@ -129,6 +130,7 @@ SelectionMode="Multiple" Background="{DynamicResource Brush.Contents}" ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeSortMode}" Changes="{Binding VisibleUnstaged}" SelectedChanges="{Binding SelectedUnstaged, Mode=TwoWay}" ContextRequested="OnUnstagedContextRequested" @@ -172,7 +174,8 @@ + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeSortMode, Mode=TwoWay}"/> @@ -183,6 +186,7 @@ SelectionMode="Multiple" Background="{DynamicResource Brush.Contents}" ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeSortMode}" Changes="{Binding VisibleStaged}" SelectedChanges="{Binding SelectedStaged, Mode=TwoWay}" ContextRequested="OnStagedContextRequested" From 9577b1844c74e600630a03609e715f68a97cae00 Mon Sep 17 00:00:00 2001 From: Sebastian Janus Date: 2025年7月24日 00:11:45 +0200 Subject: [PATCH 2/4] Other localizations updated --- src/Resources/Locales/de_DE.axaml | 2 ++ src/Resources/Locales/es_ES.axaml | 2 ++ src/Resources/Locales/fr_FR.axaml | 2 ++ src/Resources/Locales/it_IT.axaml | 2 ++ src/Resources/Locales/ja_JP.axaml | 2 ++ src/Resources/Locales/pt_BR.axaml | 2 ++ src/Resources/Locales/ru_RU.axaml | 2 ++ src/Resources/Locales/zh_CN.axaml | 2 ++ 8 files changed, 16 insertions(+) diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index bb78b8e7c..67d08fc52 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -87,6 +87,8 @@ Zeige als Datei- und Ordnerliste Zeige als Pfadliste Zeige als Dateisystembaum + Nach Pfad sortieren + Nach Status sortieren Ändere URL des Submoduls Submodule: URL: diff --git a/src/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 4ef06eae9..a8bb15041 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -87,6 +87,8 @@ Mostrar como Lista de Archivos y Directorios Mostrar como Lista de Rutas Mostrar como Árbol de Sistema de Archivos + Ordenar por ruta + Ordenar por estado Cambiar la URL del Submódulo Submódulo: URL: diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml index 919dfeac8..731314fad 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -70,6 +70,8 @@ Afficher comme liste de dossiers/fichiers Afficher comme liste de chemins Afficher comme arborescence + Trier par chemin + Trier par statut Récupérer la branche Récupérer ce commit Commit : diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index c12e3e53c..7bf68de21 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -77,6 +77,8 @@ Mostra come elenco di file e cartelle Mostra come elenco di percorsi Mostra come albero del filesystem + Ordina per percorso + Ordina per stato Checkout Branch Checkout Commit Commit: diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 32c41f216..e45bef786 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -69,6 +69,8 @@ ファイルとディレクトリのリストを表示 パスのリストを表示 ファイルシステムのツリーを表示 + パスで並び替え + ステータスで並び替え ブランチをチェックアウト コミットをチェックアウト コミット: diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 3fbac432f..cf4481598 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -61,6 +61,8 @@ Exibir como Lista de Arquivos e Diretórios Exibir como Lista de Caminhos Exibir como Árvore de Sistema de Arquivos + Ordenar por caminho + Ordenar por status Checkout Branch Checkout Commit Commit: diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index 413b99e32..0e5b83357 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -87,6 +87,8 @@ Показывать в виде списка файлов и каталогов Показывать в виде списка путей Показывать в виде дерева файловой системы + Сортировать по пути + Сортировать по статусу Изменить URL-адрес подмодуля Подмодуль: URL-адрес: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index 711ab12f3..418184113 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -87,6 +87,8 @@ 文件名+路径列表模式 全路径列表模式 文件目录树形结构模式 + 按路径排序 + 按状态排序 修改子模块远程地址 子模块 : 远程地址 : From 18c581b28187b0500b38e14ba969a3d985251242 Mon Sep 17 00:00:00 2001 From: Sebastian Janus Date: 2025年7月24日 00:47:31 +0200 Subject: [PATCH 3/4] Fixed proper sorting while having same files added (staged) and then modified again in unstaged view --- src/ViewModels/ChangeTreeNode.cs | 67 ++++++++++++++----------- src/Views/ChangeCollectionView.axaml.cs | 58 ++++++++++----------- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs index 5c3b4a3e0..215370995 100644 --- a/src/ViewModels/ChangeTreeNode.cs +++ b/src/ViewModels/ChangeTreeNode.cs @@ -47,7 +47,7 @@ public ChangeTreeNode(string path, bool isExpanded, int depth) IsExpanded = isExpanded; } - public static List Build(IList changes, HashSet folded, Models.ChangeSortMode sortMode = Models.ChangeSortMode.Path) + public static List Build(IList changes, HashSet folded, Models.ChangeSortMode sortMode = Models.ChangeSortMode.Path, bool isUnstagedContext = false) { var nodes = new List(); var folders = new Dictionary(); @@ -93,7 +93,7 @@ public static List Build(IList changes, HashSet collection, ChangeTreeNode collection.Add(subFolder); } - private static void Sort(List nodes, Models.ChangeSortMode sortMode) + private static void Sort(List nodes, Models.ChangeSortMode sortMode, bool isUnstagedContext) { foreach (var node in nodes) { if (node.IsFolder) - Sort(node.Children, sortMode); + Sort(node.Children, sortMode, isUnstagedContext); } if (sortMode == Models.ChangeSortMode.Status) @@ -134,13 +134,16 @@ private static void Sort(List nodes, Models.ChangeSortMode sortM return Models.NumericSort.Compare(l.FullPath, r.FullPath); // For files, sort by status first - var leftPriority = GetStatusSortPriority(l.Change); - var rightPriority = GetStatusSortPriority(r.Change); + var leftPriority = GetStatusSortPriority(l.Change, isUnstagedContext); + var rightPriority = GetStatusSortPriority(r.Change, isUnstagedContext); - if (leftPriority != rightPriority) - return leftPriority.CompareTo(rightPriority); + // First sort by status priority + var statusComparison = leftPriority.CompareTo(rightPriority); + if (statusComparison != 0) + return statusComparison; - // Same status, sort by path + // If status priorities are equal, sort by path as secondary sort + // Use the same sorting logic as the path-only sort for consistency return Models.NumericSort.Compare(l.FullPath, r.FullPath); }); } @@ -155,35 +158,39 @@ private static void Sort(List nodes, Models.ChangeSortMode sortM } } - private static int GetStatusSortPriority(Models.Change change) + private static int GetStatusSortPriority(Models.Change change, bool isUnstagedContext) { if (change == null) return int.MaxValue; - // Prioritize Index (staged) changes first, then WorkTree (unstaged) changes - var indexState = change.Index; - var workTreeState = change.WorkTree; - - // For staged changes (Index state) - if (indexState != Models.ChangeState.None) + if (isUnstagedContext) { - return indexState switch + // For unstaged context, only consider WorkTree state + return change.WorkTree switch { - Models.ChangeState.Modified => 1, - Models.ChangeState.Renamed => 2, - Models.ChangeState.Added => 3, - Models.ChangeState.Deleted => 4, - _ => 5 + Models.ChangeState.Conflicted => 1, // Conflicts first - most urgent + Models.ChangeState.Modified => 2, + Models.ChangeState.TypeChanged => 3, + Models.ChangeState.Deleted => 4, // Missing files + Models.ChangeState.Renamed => 5, + Models.ChangeState.Copied => 6, + Models.ChangeState.Untracked => 7, // New files last + _ => 10 }; } - - // For unstaged changes (WorkTree state) - return workTreeState switch + else { - Models.ChangeState.Modified => 10, - Models.ChangeState.Deleted => 11, // Missing files - Models.ChangeState.Untracked => 12, // Untracked files - _ => 20 - }; + // For staged context, only consider Index state + return change.Index switch + { + Models.ChangeState.Modified => 1, + Models.ChangeState.TypeChanged => 2, + Models.ChangeState.Renamed => 3, + Models.ChangeState.Copied => 4, + Models.ChangeState.Added => 5, + Models.ChangeState.Deleted => 6, + _ => 10 + }; + } } private bool _isExpanded = true; diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 9d015f9c6..9a63e533e 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -395,7 +395,7 @@ private void UpdateDataSource(bool onlyViewModeChange) } var tree = new ViewModels.ChangeCollectionAsTree(); - tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded, SortMode); + tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded, SortMode, IsUnstagedChange); var rows = new List(); MakeTreeRows(rows, tree.Tree); @@ -518,34 +518,49 @@ private void UpdateRowTips(Control control, Models.Change change) { sortedChanges.Sort((l, r) => { - var leftPriority = GetStatusSortPriority(l); - var rightPriority = GetStatusSortPriority(r); + var leftPriority = GetStatusSortPriority(l, IsUnstagedChange); + var rightPriority = GetStatusSortPriority(r, IsUnstagedChange); - if (leftPriority != rightPriority) - return leftPriority.CompareTo(rightPriority); + // First sort by status priority + var statusComparison = leftPriority.CompareTo(rightPriority); + if (statusComparison != 0) + return statusComparison; - // Same status, sort by path + // If status priorities are equal, sort by path as secondary sort + // Use the same sorting logic as the path-only sort for consistency return Models.NumericSort.Compare(l.Path, r.Path); }); } else { + // Path sort mode - use NumericSort for consistency sortedChanges.Sort((l, r) => Models.NumericSort.Compare(l.Path, r.Path)); } return sortedChanges; } - private int GetStatusSortPriority(Models.Change change) + private int GetStatusSortPriority(Models.Change change, bool isUnstagedContext) { - // Prioritize Index (staged) changes first, then WorkTree (unstaged) changes - var indexState = change.Index; - var workTreeState = change.WorkTree; - - // For staged changes (Index state) - if (indexState != Models.ChangeState.None) + if (isUnstagedContext) + { + // For unstaged context, only consider WorkTree state + return change.WorkTree switch + { + Models.ChangeState.Conflicted => 1, // Conflicts first - most urgent + Models.ChangeState.Modified => 2, + Models.ChangeState.TypeChanged => 3, + Models.ChangeState.Deleted => 4, // Missing files + Models.ChangeState.Renamed => 5, + Models.ChangeState.Copied => 6, + Models.ChangeState.Untracked => 7, // New files last + _ => 10 + }; + } + else { - return indexState switch + // For staged context, only consider Index state + return change.Index switch { Models.ChangeState.Modified => 1, Models.ChangeState.TypeChanged => 2, @@ -553,22 +568,9 @@ private int GetStatusSortPriority(Models.Change change) Models.ChangeState.Copied => 4, Models.ChangeState.Added => 5, Models.ChangeState.Deleted => 6, - _ => 9 + _ => 10 }; } - - // For unstaged changes (WorkTree state) - return workTreeState switch - { - Models.ChangeState.Conflicted => 10, // Conflicts first - most urgent - Models.ChangeState.Modified => 11, - Models.ChangeState.TypeChanged => 12, - Models.ChangeState.Deleted => 13, // Missing files - Models.ChangeState.Renamed => 14, - Models.ChangeState.Copied => 15, - Models.ChangeState.Untracked => 16, // New files last - _ => 20 - }; } private bool _disableSelectionChangingEvent = false; From df5c31c66aa14c81614d8faa6194249d119da6c9 Mon Sep 17 00:00:00 2001 From: Sebastian Janus Date: 2025年7月25日 14:07:58 +0200 Subject: [PATCH 4/4] refactor: eliminate GetStatusSortPriority code duplication --- src/Models/Change.cs | 42 +++++++++++++++++++++++++ src/ViewModels/ChangeTreeNode.cs | 39 ++--------------------- src/Views/ChangeCollectionView.axaml.cs | 37 ++-------------------- 3 files changed, 46 insertions(+), 72 deletions(-) diff --git a/src/Models/Change.cs b/src/Models/Change.cs index fde316da2..9420559cd 100644 --- a/src/Models/Change.cs +++ b/src/Models/Change.cs @@ -85,6 +85,48 @@ public void Set(ChangeState index, ChangeState workTree = ChangeState.None) OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2); } + /// + /// Gets the sort priority for a change based on its status, used for sorting changes by status. + /// Lower numbers indicate higher priority (appear first in sorted lists). + /// + /// The change object to get priority for + /// True if sorting in unstaged context, false for staged context + /// Priority value where lower numbers appear first + public static int GetStatusSortPriority(Change change, bool isUnstagedContext) + { + if (change == null) return int.MaxValue; + + if (isUnstagedContext) + { + // For unstaged context, only consider WorkTree state + return change.WorkTree switch + { + ChangeState.Conflicted => 1, // Conflicts first - most urgent + ChangeState.Modified => 2, + ChangeState.TypeChanged => 3, + ChangeState.Deleted => 4, // Missing files + ChangeState.Renamed => 5, + ChangeState.Copied => 6, + ChangeState.Untracked => 7, // New files last + _ => 10 + }; + } + else + { + // For staged context, only consider Index state + return change.Index switch + { + ChangeState.Modified => 1, + ChangeState.TypeChanged => 2, + ChangeState.Renamed => 3, + ChangeState.Copied => 4, + ChangeState.Added => 5, + ChangeState.Deleted => 6, + _ => 10 + }; + } + } + private static readonly string[] TYPE_DESCS = [ "Unknown", diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs index 215370995..72efc8287 100644 --- a/src/ViewModels/ChangeTreeNode.cs +++ b/src/ViewModels/ChangeTreeNode.cs @@ -134,8 +134,8 @@ private static void Sort(List nodes, Models.ChangeSortMode sortM return Models.NumericSort.Compare(l.FullPath, r.FullPath); // For files, sort by status first - var leftPriority = GetStatusSortPriority(l.Change, isUnstagedContext); - var rightPriority = GetStatusSortPriority(r.Change, isUnstagedContext); + var leftPriority = Models.Change.GetStatusSortPriority(l.Change, isUnstagedContext); + var rightPriority = Models.Change.GetStatusSortPriority(r.Change, isUnstagedContext); // First sort by status priority var statusComparison = leftPriority.CompareTo(rightPriority); @@ -158,41 +158,6 @@ private static void Sort(List nodes, Models.ChangeSortMode sortM } } - private static int GetStatusSortPriority(Models.Change change, bool isUnstagedContext) - { - if (change == null) return int.MaxValue; - - if (isUnstagedContext) - { - // For unstaged context, only consider WorkTree state - return change.WorkTree switch - { - Models.ChangeState.Conflicted => 1, // Conflicts first - most urgent - Models.ChangeState.Modified => 2, - Models.ChangeState.TypeChanged => 3, - Models.ChangeState.Deleted => 4, // Missing files - Models.ChangeState.Renamed => 5, - Models.ChangeState.Copied => 6, - Models.ChangeState.Untracked => 7, // New files last - _ => 10 - }; - } - else - { - // For staged context, only consider Index state - return change.Index switch - { - Models.ChangeState.Modified => 1, - Models.ChangeState.TypeChanged => 2, - Models.ChangeState.Renamed => 3, - Models.ChangeState.Copied => 4, - Models.ChangeState.Added => 5, - Models.ChangeState.Deleted => 6, - _ => 10 - }; - } - } - private bool _isExpanded = true; } } diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 9a63e533e..df2d55a1a 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -518,8 +518,8 @@ private void UpdateRowTips(Control control, Models.Change change) { sortedChanges.Sort((l, r) => { - var leftPriority = GetStatusSortPriority(l, IsUnstagedChange); - var rightPriority = GetStatusSortPriority(r, IsUnstagedChange); + var leftPriority = Models.Change.GetStatusSortPriority(l, IsUnstagedChange); + var rightPriority = Models.Change.GetStatusSortPriority(r, IsUnstagedChange); // First sort by status priority var statusComparison = leftPriority.CompareTo(rightPriority); @@ -540,39 +540,6 @@ private void UpdateRowTips(Control control, Models.Change change) return sortedChanges; } - private int GetStatusSortPriority(Models.Change change, bool isUnstagedContext) - { - if (isUnstagedContext) - { - // For unstaged context, only consider WorkTree state - return change.WorkTree switch - { - Models.ChangeState.Conflicted => 1, // Conflicts first - most urgent - Models.ChangeState.Modified => 2, - Models.ChangeState.TypeChanged => 3, - Models.ChangeState.Deleted => 4, // Missing files - Models.ChangeState.Renamed => 5, - Models.ChangeState.Copied => 6, - Models.ChangeState.Untracked => 7, // New files last - _ => 10 - }; - } - else - { - // For staged context, only consider Index state - return change.Index switch - { - Models.ChangeState.Modified => 1, - Models.ChangeState.TypeChanged => 2, - Models.ChangeState.Renamed => 3, - Models.ChangeState.Copied => 4, - Models.ChangeState.Added => 5, - Models.ChangeState.Deleted => 6, - _ => 10 - }; - } - } - private bool _disableSelectionChangingEvent = false; } }

AltStyle によって変換されたページ (->オリジナル) /