From 8a693ce19d69025867bf8c0451a2a71e98866f24 Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Fri, 31 Oct 2025 18:21:40 +1000 Subject: [PATCH] sort files in sln explorer --- .../IdeFileExternalChangeHandler.cs | 12 +++++++++++- .../SolutionDiscovery/SharpIdeFolder.cs | 8 ++++---- .../SolutionDiscovery/TreeMapperV2.cs | 8 ++++++-- .../VsPersistence/SharpIdeModels.cs | 18 +++++++++--------- .../SolutionExplorer/SolutionExplorerPanel.cs | 19 ++++++++++++++----- 5 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs b/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs index 1ba4c20..f26d923 100644 --- a/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs +++ b/src/SharpIDE.Application/Features/FileWatching/IdeFileExternalChangeHandler.cs @@ -108,7 +108,17 @@ public class IdeFileExternalChangeHandler } sharpIdeFile = new SharpIdeFile(filePath, Path.GetFileName(filePath), containingFolderOrProject, []); - containingFolderOrProject.Files.Add(sharpIdeFile); + + var correctInsertionPosition = containingFolderOrProject.Files.list.BinarySearch(sharpIdeFile, SharpIdeFileComparer.Instance); + if (correctInsertionPosition < 0) + { + correctInsertionPosition = ~correctInsertionPosition; + } + else + { + throw new InvalidOperationException("File already exists in the containing folder or project"); + } + containingFolderOrProject.Files.Insert(correctInsertionPosition, sharpIdeFile); SolutionModel.AllFiles.Add(sharpIdeFile); await _fileChangedService.SharpIdeFileAdded(sharpIdeFile, await File.ReadAllTextAsync(filePath)); diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs index c26d73d..768a309 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs @@ -11,8 +11,8 @@ public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildShar public required string Path { get; set; } public string ChildNodeBasePath => Path; public required string Name { get; set; } - public ObservableSortedSet Files { get; init; } - public ObservableSortedSet Folders { get; init; } + public ObservableList Files { get; init; } + public ObservableList Folders { get; init; } public bool Expanded { get; set; } [SetsRequiredMembers] @@ -21,8 +21,8 @@ public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildShar Parent = parent; Path = folderInfo.FullName; Name = folderInfo.Name; - Files = new ObservableSortedSet(folderInfo.GetFiles(this, allFiles), SharpIdeFileComparer.Instance); - Folders = new ObservableSortedSet(this.GetSubFolders(this, allFiles, allFolders), SharpIdeFolderComparer.Instance); + Files = new ObservableList(folderInfo.GetFiles(this, allFiles)); + Folders = new ObservableList(this.GetSubFolders(this, allFiles, allFolders)); } public SharpIdeFolder() diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/TreeMapperV2.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/TreeMapperV2.cs index 96cf55a..c8a2d07 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/TreeMapperV2.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/TreeMapperV2.cs @@ -52,7 +52,9 @@ public static class TreeMapperV2 allFolders.Add(subFolder); }); - return subFolders.ToList(); + var sharpIdeFolders = subFolders.ToList(); + sharpIdeFolders.Sort(SharpIdeFolderComparer.Instance); + return sharpIdeFolders; } public static List GetFiles(string csprojectPath, SharpIdeProjectModel sharpIdeProjectModel, ConcurrentBag allFiles) @@ -73,11 +75,13 @@ public static class TreeMapperV2 return []; } - return fileInfos.Select(f => new SharpIdeFile(f.FullName, f.Name, parent, allFiles) + var sharpIdeFiles = fileInfos.Select(f => new SharpIdeFile(f.FullName, f.Name, parent, allFiles) { Path = f.FullName, Name = f.Name, Parent = parent }).ToList(); + sharpIdeFiles.Sort(SharpIdeFileComparer.Instance); + return sharpIdeFiles; } } diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs index 4e065f1..285d988 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs @@ -17,8 +17,8 @@ public interface IExpandableSharpIdeNode public interface IFolderOrProject : IExpandableSharpIdeNode, IChildSharpIdeNode { - public ObservableSortedSet Folders { get; init; } - public ObservableSortedSet Files { get; init; } + public ObservableList Folders { get; init; } + public ObservableList Files { get; init; } public string Name { get; set; } public string ChildNodeBasePath { get; } } @@ -50,9 +50,9 @@ public class SharpIdeSolutionModel : ISharpIdeNode, IExpandableSharpIdeNode public required string DirectoryPath { get; set; } public required ObservableHashSet Projects { get; set; } public required ObservableHashSet SlnFolders { get; set; } - public required HashSet AllProjects { get; set; } - public required HashSet AllFiles { get; set; } - public required HashSet AllFolders { get; set; } + public required HashSet AllProjects { get; set; } // TODO: this isn't thread safe + public required HashSet AllFiles { get; set; } // TODO: this isn't thread safe + public required HashSet AllFolders { get; set; } // TODO: this isn't thread safe public bool Expanded { get; set; } [SetsRequiredMembers] @@ -96,8 +96,8 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi public required string Name { get; set; } public required string FilePath { get; set; } public string ChildNodeBasePath => Path.GetDirectoryName(FilePath)!; - public required ObservableSortedSet Folders { get; init; } - public required ObservableSortedSet Files { get; init; } + public required ObservableList Folders { get; init; } + public required ObservableList Files { get; init; } public bool Expanded { get; set; } public required IExpandableSharpIdeNode Parent { get; set; } public bool Running { get; set; } @@ -110,8 +110,8 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi Parent = parent; Name = projectModel.Model.ActualDisplayName; FilePath = projectModel.FullFilePath; - Files = new ObservableSortedSet(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles), SharpIdeFileComparer.Instance); - Folders = new ObservableSortedSet(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders), SharpIdeFolderComparer.Instance); + Files = new ObservableList(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles)); + Folders = new ObservableList(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders)); MsBuildEvaluationProjectTask = ProjectEvaluation.GetProject(projectModel.FullFilePath); allProjects.Add(this); } diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs index e5f6821..1e71b62 100644 --- a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs @@ -203,7 +203,7 @@ public partial class SolutionExplorerPanel : MarginContainer filesView.ObserveChanged() .SubscribeAwait(async (innerEvent, ct) => await (innerEvent.Action switch { - NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, folderItem, innerEvent.NewItem.Value)), + NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, folderItem, innerEvent.NewItem.Value, innerEvent.NewStartingIndex)), NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value), _ => Task.CompletedTask })).AddTo(this); @@ -236,7 +236,7 @@ public partial class SolutionExplorerPanel : MarginContainer filesView.ObserveChanged() .SubscribeAwait(async (innerEvent, ct) => await (innerEvent.Action switch { - NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, projectItem, innerEvent.NewItem.Value)), + NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, projectItem, innerEvent.NewItem.Value, innerEvent.NewStartingIndex)), NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value), _ => Task.CompletedTask })).AddTo(this); @@ -275,7 +275,7 @@ public partial class SolutionExplorerPanel : MarginContainer filesView.ObserveChanged() .SubscribeAwait(async (innerEvent, ct) => await (innerEvent.Action switch { - NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, folderItem, innerEvent.NewItem.Value)), + NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, folderItem, innerEvent.NewItem.Value, innerEvent.NewStartingIndex)), NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value), _ => Task.CompletedTask })).AddTo(this); @@ -283,9 +283,18 @@ public partial class SolutionExplorerPanel : MarginContainer } [RequiresGodotUiThread] - private TreeItem CreateFileTreeItem(Tree tree, TreeItem parent, SharpIdeFile sharpIdeFile) + private TreeItem CreateFileTreeItem(Tree tree, TreeItem parent, SharpIdeFile sharpIdeFile, int newStartingIndex = -1) { - var fileItem = tree.CreateItem(parent); + // We need to offset the starting index by the number of non-file items (folders/projects) in the parent + // because the newStartingIndex is calculated based on all children, but we are only inserting files here + if (newStartingIndex >= 0) + { + var sharpIdeParent = sharpIdeFile.Parent as IFolderOrProject; + Guard.Against.Null(sharpIdeParent, nameof(sharpIdeParent)); + var folderCount = sharpIdeParent.Folders.Count; + newStartingIndex += folderCount; + } + var fileItem = tree.CreateItem(parent, newStartingIndex); fileItem.SetText(0, sharpIdeFile.Name); fileItem.SetIcon(0, CsharpFileIcon); fileItem.SetCustomColor(0, GetColorForGitStatus(sharpIdeFile.GitStatus));