From 237d4bc7e52fcad681296732c033d3d2d7485c60 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 14 Aug 2025 15:18:36 +0800 Subject: [PATCH] feature: support compact folders display mode in changes tree view (#1674) (#1729) Signed-off-by: leo --- src/Resources/Locales/en_US.axaml | 1 + src/Resources/Locales/zh_CN.axaml | 1 + src/Resources/Locales/zh_TW.axaml | 1 + src/ViewModels/ChangeTreeNode.cs | 60 ++++++++++++++++++------- src/ViewModels/Preferences.cs | 7 +++ src/Views/BranchCompare.axaml | 1 + src/Views/ChangeCollectionView.axaml | 4 +- src/Views/ChangeCollectionView.axaml.cs | 14 +++++- src/Views/CommitChanges.axaml | 1 + src/Views/Preferences.axaml | 7 ++- src/Views/RevisionCompare.axaml | 1 + src/Views/StashesPage.axaml | 1 + src/Views/WorkingCopy.axaml | 2 + 13 files changed, 82 insertions(+), 19 deletions(-) diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index c960f492..de16b5b7 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -526,6 +526,7 @@ APPEARANCE Default Font Editor Tab Width + Enable compact folders in changes tree Font Size Default Editor diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index ac47d8ad..15af237c 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -530,6 +530,7 @@ 外观配置 缺省字体 编辑器制表符宽度 + 在变更列表树中启用紧凑文件夹模式 字体大小 默认 代码编辑器 diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml index c3e0715a..d217ac51 100644 --- a/src/Resources/Locales/zh_TW.axaml +++ b/src/Resources/Locales/zh_TW.axaml @@ -530,6 +530,7 @@ 外觀設定 預設字型 編輯器 Tab 寬度 + 在變更樹中啟用精簡文件夾顯示模式 字型大小 預設 程式碼 diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs index 4d71a153..6130e7c4 100644 --- a/src/ViewModels/ChangeTreeNode.cs +++ b/src/ViewModels/ChangeTreeNode.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; - +using System.IO; using CommunityToolkit.Mvvm.ComponentModel; namespace SourceGit.ViewModels @@ -7,6 +7,7 @@ namespace SourceGit.ViewModels public class ChangeTreeNode : ObservableObject { public string FullPath { get; set; } + public string DisplayName { get; set; } public int Depth { get; private set; } = 0; public Models.Change Change { get; set; } = null; public List Children { get; set; } = new List(); @@ -32,22 +33,22 @@ namespace SourceGit.ViewModels set => SetProperty(ref _isExpanded, value); } - public ChangeTreeNode(Models.Change c, int depth) + public ChangeTreeNode(Models.Change c) { FullPath = c.Path; - Depth = depth; + DisplayName = Path.GetFileName(c.Path); Change = c; IsExpanded = false; } - public ChangeTreeNode(string path, bool isExpanded, int depth) + public ChangeTreeNode(string path, bool isExpanded) { FullPath = path; - Depth = depth; + DisplayName = Path.GetFileName(path); IsExpanded = isExpanded; } - public static List Build(IList changes, HashSet folded) + public static List Build(IList changes, HashSet folded, bool compactFolders) { var nodes = new List(); var folders = new Dictionary(); @@ -57,12 +58,11 @@ namespace SourceGit.ViewModels var sepIdx = c.Path.IndexOf('/'); if (sepIdx == -1) { - nodes.Add(new ChangeTreeNode(c, 0)); + nodes.Add(new ChangeTreeNode(c)); } else { ChangeTreeNode lastFolder = null; - int depth = 0; while (sepIdx != -1) { @@ -73,27 +73,32 @@ namespace SourceGit.ViewModels } else if (lastFolder == null) { - lastFolder = new ChangeTreeNode(folder, !folded.Contains(folder), depth); + lastFolder = new ChangeTreeNode(folder, !folded.Contains(folder)); folders.Add(folder, lastFolder); InsertFolder(nodes, lastFolder); } else { - var cur = new ChangeTreeNode(folder, !folded.Contains(folder), depth); + var cur = new ChangeTreeNode(folder, !folded.Contains(folder)); folders.Add(folder, cur); InsertFolder(lastFolder.Children, cur); lastFolder = cur; } - depth++; sepIdx = c.Path.IndexOf('/', sepIdx + 1); } - lastFolder?.Children.Add(new ChangeTreeNode(c, depth)); + lastFolder?.Children.Add(new ChangeTreeNode(c)); } } - Sort(nodes); + if (compactFolders) + { + foreach (var node in nodes) + Compact(node); + } + + SortAndSetDepth(nodes, 0); folders.Clear(); return nodes; @@ -113,12 +118,37 @@ namespace SourceGit.ViewModels collection.Add(subFolder); } - private static void Sort(List nodes) + private static void Compact(ChangeTreeNode node) + { + var childrenCount = node.Children.Count; + if (childrenCount == 0) + return; + + if (childrenCount > 1) + { + foreach (var c in node.Children) + Compact(c); + return; + } + + var child = node.Children[0]; + if (child.Change != null) + return; + + node.FullPath = $"{node.FullPath}/{child.DisplayName}"; + node.DisplayName = $"{node.DisplayName} / {child.DisplayName}"; + node.IsExpanded = child.IsExpanded; + node.Children = child.Children; + Compact(node); + } + + private static void SortAndSetDepth(List nodes, int depth) { foreach (var node in nodes) { + node.Depth = depth; if (node.IsFolder) - Sort(node.Children); + SortAndSetDepth(node.Children, depth + 1); } nodes.Sort((l, r) => diff --git a/src/ViewModels/Preferences.cs b/src/ViewModels/Preferences.cs index 1a836426..c2335910 100644 --- a/src/ViewModels/Preferences.cs +++ b/src/ViewModels/Preferences.cs @@ -267,6 +267,12 @@ namespace SourceGit.ViewModels set => SetProperty(ref _lfsImageActiveIdx, value); } + public bool EnableCompactFoldersInChangesTree + { + get => _enableCompactFoldersInChangesTree; + set => SetProperty(ref _enableCompactFoldersInChangesTree, value); + } + public Models.ChangeViewMode UnstagedChangeViewMode { get => _unstagedChangeViewMode; @@ -718,6 +724,7 @@ namespace SourceGit.ViewModels private bool _useFullTextDiff = false; private bool _useBlockNavigationInDiffView = false; private int _lfsImageActiveIdx = 0; + private bool _enableCompactFoldersInChangesTree = false; private Models.ChangeViewMode _unstagedChangeViewMode = Models.ChangeViewMode.List; private Models.ChangeViewMode _stagedChangeViewMode = Models.ChangeViewMode.List; diff --git a/src/Views/BranchCompare.axaml b/src/Views/BranchCompare.axaml index b80fefb5..8f338751 100644 --- a/src/Views/BranchCompare.axaml +++ b/src/Views/BranchCompare.axaml @@ -125,6 +125,7 @@ diff --git a/src/Views/ChangeCollectionView.axaml b/src/Views/ChangeCollectionView.axaml index 9ff1486f..66a3ece5 100644 --- a/src/Views/ChangeCollectionView.axaml +++ b/src/Views/ChangeCollectionView.axaml @@ -65,8 +65,8 @@ IsVisible="{Binding !IsFolder}"/> - - + + diff --git a/src/Views/ChangeCollectionView.axaml.cs b/src/Views/ChangeCollectionView.axaml.cs index 8b9f9058..1f7fb2cd 100644 --- a/src/Views/ChangeCollectionView.axaml.cs +++ b/src/Views/ChangeCollectionView.axaml.cs @@ -109,6 +109,15 @@ namespace SourceGit.Views set => SetValue(ViewModeProperty, value); } + public static readonly StyledProperty EnableCompactFoldersProperty = + AvaloniaProperty.Register(nameof(EnableCompactFolders), false); + + public bool EnableCompactFolders + { + get => GetValue(EnableCompactFoldersProperty); + set => SetValue(EnableCompactFoldersProperty, value); + } + public static readonly StyledProperty> ChangesProperty = AvaloniaProperty.Register>(nameof(Changes)); @@ -254,6 +263,9 @@ namespace SourceGit.Views UpdateDataSource(false); else if (change.Property == SelectedChangesProperty) UpdateSelection(); + + if (change.Property == EnableCompactFoldersProperty && ViewMode == Models.ChangeViewMode.Tree) + UpdateDataSource(true); } private void OnRowDataContextChanged(object sender, EventArgs e) @@ -384,7 +396,7 @@ namespace SourceGit.Views } var tree = new ViewModels.ChangeCollectionAsTree(); - tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded); + tree.Tree = ViewModels.ChangeTreeNode.Build(changes, oldFolded, EnableCompactFolders); var rows = new List(); MakeTreeRows(rows, tree.Tree); diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 4a1d95bc..78277bff 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -48,6 +48,7 @@ diff --git a/src/Views/Preferences.axaml b/src/Views/Preferences.axaml index 39055dfe..a01b1080 100644 --- a/src/Views/Preferences.axaml +++ b/src/Views/Preferences.axaml @@ -159,7 +159,7 @@ - + + + diff --git a/src/Views/StashesPage.axaml b/src/Views/StashesPage.axaml index 36d3d088..e5803367 100644 --- a/src/Views/StashesPage.axaml +++ b/src/Views/StashesPage.axaml @@ -128,6 +128,7 @@ diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index a57cc3c2..354545e3 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -129,6 +129,7 @@ SelectionMode="Multiple" Background="{DynamicResource Brush.Contents}" ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=UnstagedChangeViewMode}" + EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleUnstaged}" SelectedChanges="{Binding SelectedUnstaged, Mode=TwoWay}" ContextRequested="OnUnstagedContextRequested" @@ -183,6 +184,7 @@ SelectionMode="Multiple" Background="{DynamicResource Brush.Contents}" ViewMode="{Binding Source={x:Static vm:Preferences.Instance}, Path=StagedChangeViewMode}" + EnableCompactFolders="{Binding Source={x:Static vm:Preferences.Instance}, Path=EnableCompactFoldersInChangesTree}" Changes="{Binding VisibleStaged}" SelectedChanges="{Binding SelectedStaged, Mode=TwoWay}" ContextRequested="OnStagedContextRequested"