diff --git a/src/Models/Change.cs b/src/Models/Change.cs index baf6e8500..013dd49ce 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, @@ -79,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/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml index 6da606aee..b70316335 100644 --- a/src/Resources/Locales/de_DE.axaml +++ b/src/Resources/Locales/de_DE.axaml @@ -103,6 +103,8 @@ Zeige als Datei- und Ordnerliste Zeige als Pfadliste Zeige als Dateisystem-Baumstruktur + Nach Pfad sortieren + Nach Status sortieren Ändere URL des Submoduls Submodul: URL: diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index c997f8221..8c2155d0f 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -108,6 +108,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/Resources/Locales/es_ES.axaml b/src/Resources/Locales/es_ES.axaml index 5f374f093..3fb8e2ac7 100644 --- a/src/Resources/Locales/es_ES.axaml +++ b/src/Resources/Locales/es_ES.axaml @@ -112,6 +112,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 8227387c9..ec0165f5e 100644 --- a/src/Resources/Locales/fr_FR.axaml +++ b/src/Resources/Locales/fr_FR.axaml @@ -107,6 +107,8 @@ Afficher comme liste de dossiers/fichiers Afficher comme liste de chemins Afficher comme arborescence + Trier par chemin + Trier par statut Changer l'URL du sous-module Sous-module : URL : diff --git a/src/Resources/Locales/it_IT.axaml b/src/Resources/Locales/it_IT.axaml index 13122e41c..84a5eb13d 100644 --- a/src/Resources/Locales/it_IT.axaml +++ b/src/Resources/Locales/it_IT.axaml @@ -102,6 +102,8 @@ Mostra come elenco di file e cartelle Mostra come elenco di percorsi Mostra come albero del filesystem + Ordina per percorso + Ordina per stato Cambia l'URL del Sottomodulo Sottomodulo: URL: diff --git a/src/Resources/Locales/ja_JP.axaml b/src/Resources/Locales/ja_JP.axaml index 7f2341eb6..d84e6e08b 100644 --- a/src/Resources/Locales/ja_JP.axaml +++ b/src/Resources/Locales/ja_JP.axaml @@ -112,6 +112,8 @@ ファイルとディレクトリの一覧で表示 パスの一覧で表示 ファイルシステムのツリーで表示 + パスで並び替え + ステータスで並び替え サブモジュールの URL を変更 サブモジュール: URL: diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml index 8155dc647..33059aa28 100644 --- a/src/Resources/Locales/pt_BR.axaml +++ b/src/Resources/Locales/pt_BR.axaml @@ -93,6 +93,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 Alterações Locais: Branch: diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml index ee438ac49..f24bc99f8 100644 --- a/src/Resources/Locales/ru_RU.axaml +++ b/src/Resources/Locales/ru_RU.axaml @@ -112,6 +112,8 @@ Показывать в виде списка файлов и каталогов Показывать в виде списка путей Показывать в виде дерева файловой системы + Сортировать по пути + Сортировать по статусу Изменить URL-адрес подмодуля Подмодуль: URL-адрес: diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index cf27832e7..060d8de07 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -112,6 +112,8 @@ 文件名+路径列表模式 全路径列表模式 文件目录树形结构模式 + 按路径排序 + 按状态排序 修改子模块远程地址 子模块 : 远程地址 : diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs index c35f4fc1f..edd355f7b 100644 --- a/src/ViewModels/ChangeTreeNode.cs +++ b/src/ViewModels/ChangeTreeNode.cs @@ -48,7 +48,7 @@ public ChangeTreeNode(string path, bool isExpanded) IsExpanded = isExpanded; } - public static List Build(IList changes, HashSet folded, bool compactFolders) + public static List Build(IList changes, HashSet folded, Models.ChangeSortMode sortMode = Models.ChangeSortMode.Path, bool isUnstagedContext = false, bool compactFolders = false) { var nodes = new List(); var folders = new Dictionary(); @@ -98,7 +98,7 @@ public static List Build(IList changes, HashSet nodes, int depth) + private static void Sort(List nodes, Models.ChangeSortMode sortMode, bool isUnstagedContext, int depth = 0) { foreach (var node in nodes) { node.Depth = depth; if (node.IsFolder) - SortAndSetDepth(node.Children, depth + 1); + Sort(node.Children, sortMode, isUnstagedContext, depth + 1); } - 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.DisplayName, r.DisplayName); + + // For files, sort by status first + 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); + if (statusComparison != 0) + return statusComparison; + + // If status priorities are equal, sort by path as secondary sort return Models.NumericSort.Compare(l.DisplayName, r.DisplayName); - return l.IsFolder ? -1 : 1; - }); + }); + } + else + { + nodes.Sort((l, r) => + { + if (l.IsFolder == r.IsFolder) + return Models.NumericSort.Compare(l.DisplayName, r.DisplayName); + return l.IsFolder ? -1 : 1; + }); + } } private bool _isExpanded = true; diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index ce0942d4c..b18fbb00d 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -331,6 +331,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; @@ -838,6 +862,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 _shellOrTerminalType = -1; } diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 8cf0a32ce..8243a937b 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -114,6 +114,18 @@ public Models.ChangeViewMode ViewMode set => SetAndRaise(ViewModeProperty, ref _viewMode, value); } + public static readonly DirectProperty SortModeProperty = + AvaloniaProperty.RegisterDirect( + nameof(SortMode), + static o => o.SortMode, + static (o, v) => o.SortMode = v); + + public Models.ChangeSortMode SortMode + { + get => _sortMode; + set => SetAndRaise(SortModeProperty, ref _sortMode, value); + } + public static readonly DirectProperty EnableCompactFoldersProperty = AvaloniaProperty.RegisterDirect( nameof(EnableCompactFolders), @@ -237,12 +249,12 @@ public Models.Change GetNextChangeWithoutSelection() if (lastUnselected != -1) return tree.Rows[lastUnselected].Change; } - else + else if (Content is ViewModels.ChangeCollectionAsGrid grid) { var lastUnselected = -1; - for (int i = changes.Count - 1; i >= 0; i--) + for (int i = grid.Changes.Count - 1; i >= 0; i--) { - if (set.Contains(changes[i].Path)) + if (set.Contains(grid.Changes[i].Path)) { if (lastUnselected == -1) continue; @@ -254,7 +266,26 @@ public Models.Change GetNextChangeWithoutSelection() } if (lastUnselected != -1) - return changes[lastUnselected]; + return grid.Changes[lastUnselected]; + } + else if (Content is ViewModels.ChangeCollectionAsList list) + { + var lastUnselected = -1; + for (int i = list.Changes.Count - 1; i >= 0; i--) + { + if (set.Contains(list.Changes[i].Path)) + { + if (lastUnselected == -1) + continue; + + break; + } + + lastUnselected = i; + } + + if (lastUnselected != -1) + return list.Changes[lastUnselected]; } return null; @@ -273,6 +304,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) @@ -412,7 +445,7 @@ private void UpdateDataSource(bool onlyViewModeChange) } var tree = new ViewModels.ChangeCollectionAsTree(); - tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded, EnableCompactFolders); + tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded, SortMode, IsUnstagedChange, EnableCompactFolders); var rows = new List(); MakeTreeRows(rows, tree.Tree); @@ -436,7 +469,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); @@ -445,7 +479,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); @@ -524,8 +559,38 @@ 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 = Models.Change.GetStatusSortPriority(l, IsUnstagedChange); + var rightPriority = Models.Change.GetStatusSortPriority(r, IsUnstagedChange); + + // First sort by status priority + var statusComparison = leftPriority.CompareTo(rightPriority); + if (statusComparison != 0) + return statusComparison; + + // 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 bool _isUnstagedChange = false; private Models.ChangeViewMode _viewMode = Models.ChangeViewMode.Tree; + private Models.ChangeSortMode _sortMode = Models.ChangeSortMode.Path; private bool _enableCompactFolders = false; private List _changes = null; private List _selectedChanges = null; 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 a51eccefd..e31152812 100644 --- a/src/Views/ChangeViewModeSwitcher.axaml.cs +++ b/src/Views/ChangeViewModeSwitcher.axaml.cs @@ -18,6 +18,18 @@ public Models.ChangeViewMode ViewMode set => SetAndRaise(ViewModeProperty, ref _viewMode, value); } + public static readonly DirectProperty SortModeProperty = + AvaloniaProperty.RegisterDirect( + nameof(SortMode), + static o => o.SortMode, + static (o, v) => o.SortMode = v); + + public Models.ChangeSortMode SortMode + { + get => _sortMode; + set => SetAndRaise(SortModeProperty, ref _sortMode, value); + } + public ChangeViewModeSwitcher() { InitializeComponent(); @@ -41,6 +53,19 @@ private void SwitchToTree(object sender, RoutedEventArgs e) 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; + } + private Models.ChangeViewMode _viewMode = Models.ChangeViewMode.List; + private Models.ChangeSortMode _sortMode = Models.ChangeSortMode.Path; } } diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index d7d245ccf..c180bb507 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -41,12 +41,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}"/> + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeSortMode, Mode=TwoWay}"/> + ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeViewMode, Mode=TwoWay}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=CommitChangeSortMode, 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}"/> + 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 @@ IsUnstagedChange="True" Background="{DynamicResource Brush.Contents}" ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeSortMode}" EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleUnstaged}" SelectedChanges="{Binding SelectedUnstaged, Mode=TwoWay}" @@ -174,7 +176,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}"/> @@ -184,6 +187,7 @@ IsUnstagedChange="False" Background="{DynamicResource Brush.Contents}" ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode}" + SortMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeSortMode}" EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleStaged}" SelectedChanges="{Binding SelectedStaged, Mode=TwoWay}"