Skip to content
48 changes: 48 additions & 0 deletions src/Models/Change.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ public enum ChangeViewMode
Tree,
}

public enum ChangeSortMode
{
Path,
Status,
}

public enum ChangeState
{
None,
Expand Down Expand Up @@ -79,6 +85,48 @@ public void Set(ChangeState index, ChangeState workTree = ChangeState.None)
OriginalPath = OriginalPath.Substring(1, OriginalPath.Length - 2);
}

/// <summary>
/// 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).
/// </summary>
/// <param name="change">The change object to get priority for</param>
/// <param name="isUnstagedContext">True if sorting in unstaged context, false for staged context</param>
/// <returns>Priority value where lower numbers appear first</returns>
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",
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/de_DE.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Zeige als Datei- und Ordnerliste</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Zeige als Pfadliste</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Zeige als Dateisystem-Baumstruktur</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Nach Pfad sortieren</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Nach Status sortieren</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">Ändere URL des Submoduls</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">Submodul:</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Show as File and Dir List</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Show as Path List</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Show as Filesystem Tree</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Sort by Path</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Sort by Status</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">Change Submodule's URL</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">Submodule:</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/es_ES.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Mostrar como Lista de Archivos y Directorios</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Mostrar como Lista de Rutas</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Mostrar como Árbol de Sistema de Archivos</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Ordenar por ruta</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Ordenar por estado</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">Cambiar la URL del Submódulo</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">Submódulo:</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/fr_FR.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Afficher comme liste de dossiers/fichiers</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Afficher comme liste de chemins</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Afficher comme arborescence</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Trier par chemin</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Trier par statut</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">Changer l'URL du sous-module</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">Sous-module :</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL :</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/it_IT.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Mostra come elenco di file e cartelle</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Mostra come elenco di percorsi</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Mostra come albero del filesystem</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Ordina per percorso</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Ordina per stato</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">Cambia l'URL del Sottomodulo</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">Sottomodulo:</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/ja_JP.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">ファイルとディレクトリの一覧で表示</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">パスの一覧で表示</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">ファイルシステムのツリーで表示</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">パスで並び替え</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">ステータスで並び替え</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">サブモジュールの URL を変更</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">サブモジュール:</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/pt_BR.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Exibir como Lista de Arquivos e Diretórios</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Exibir como Lista de Caminhos</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Exibir como Árvore de Sistema de Arquivos</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Ordenar por caminho</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Ordenar por status</x:String>
<x:String x:Key="Text.Checkout" xml:space="preserve">Checkout Branch</x:String>
<x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Checkout Commit</x:String>
<x:String x:Key="Text.Checkout.Commit.Target" xml:space="preserve">Commit:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/ru_RU.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Показывать в виде списка файлов и каталогов</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Показывать в виде списка путей</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Показывать в виде дерева файловой системы</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">Сортировать по пути</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">Сортировать по статусу</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">Изменить URL-адрес подмодуля</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">Подмодуль:</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">URL-адрес:</x:String>
Expand Down
2 changes: 2 additions & 0 deletions src/Resources/Locales/zh_CN.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">文件名+路径列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">全路径列表模式</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">文件目录树形结构模式</x:String>
<x:String x:Key="Text.ChangeSortMode.Path" xml:space="preserve">按路径排序</x:String>
<x:String x:Key="Text.ChangeSortMode.Status" xml:space="preserve">按状态排序</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl" xml:space="preserve">修改子模块远程地址</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.Submodule" xml:space="preserve">子模块 :</x:String>
<x:String x:Key="Text.ChangeSubmoduleUrl.URL" xml:space="preserve">远程地址 :</x:String>
Expand Down
44 changes: 36 additions & 8 deletions src/ViewModels/ChangeTreeNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public ChangeTreeNode(string path, bool isExpanded)
IsExpanded = isExpanded;
}

public static List<ChangeTreeNode> Build(IList<Models.Change> changes, HashSet<string> folded, bool compactFolders)
public static List<ChangeTreeNode> Build(IList<Models.Change> changes, HashSet<string> folded, Models.ChangeSortMode sortMode = Models.ChangeSortMode.Path, bool isUnstagedContext = false, bool compactFolders = false)
{
var nodes = new List<ChangeTreeNode>();
var folders = new Dictionary<string, ChangeTreeNode>();
Expand Down Expand Up @@ -98,7 +98,7 @@ public static List<ChangeTreeNode> Build(IList<Models.Change> changes, HashSet<s
Compact(node);
}

SortAndSetDepth(nodes, 0);
Sort(nodes, sortMode, isUnstagedContext);

folders.Clear();
return nodes;
Expand Down Expand Up @@ -142,21 +142,49 @@ private static void Compact(ChangeTreeNode node)
Compact(node);
}

private static void SortAndSetDepth(List<ChangeTreeNode> nodes, int depth)
private static void Sort(List<ChangeTreeNode> 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;
Expand Down
29 changes: 29 additions & 0 deletions src/ViewModels/Preferences.cs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,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;
Expand Down Expand Up @@ -850,6 +874,11 @@ private bool RemoveInvalidRepositoriesRecursive(List<RepositoryNode> 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;
private uint _statisticsSampleColor = 0xFF00FF00;
Expand Down
Loading