sort files in sln explorer

This commit is contained in:
Matt Parker
2025-10-31 18:21:40 +10:00
parent 150f9613dc
commit 8a693ce19d
5 changed files with 44 additions and 21 deletions

View File

@@ -108,7 +108,17 @@ public class IdeFileExternalChangeHandler
} }
sharpIdeFile = new SharpIdeFile(filePath, Path.GetFileName(filePath), containingFolderOrProject, []); 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); SolutionModel.AllFiles.Add(sharpIdeFile);
await _fileChangedService.SharpIdeFileAdded(sharpIdeFile, await File.ReadAllTextAsync(filePath)); await _fileChangedService.SharpIdeFileAdded(sharpIdeFile, await File.ReadAllTextAsync(filePath));

View File

@@ -11,8 +11,8 @@ public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildShar
public required string Path { get; set; } public required string Path { get; set; }
public string ChildNodeBasePath => Path; public string ChildNodeBasePath => Path;
public required string Name { get; set; } public required string Name { get; set; }
public ObservableSortedSet<SharpIdeFile> Files { get; init; } public ObservableList<SharpIdeFile> Files { get; init; }
public ObservableSortedSet<SharpIdeFolder> Folders { get; init; } public ObservableList<SharpIdeFolder> Folders { get; init; }
public bool Expanded { get; set; } public bool Expanded { get; set; }
[SetsRequiredMembers] [SetsRequiredMembers]
@@ -21,8 +21,8 @@ public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildShar
Parent = parent; Parent = parent;
Path = folderInfo.FullName; Path = folderInfo.FullName;
Name = folderInfo.Name; Name = folderInfo.Name;
Files = new ObservableSortedSet<SharpIdeFile>(folderInfo.GetFiles(this, allFiles), SharpIdeFileComparer.Instance); Files = new ObservableList<SharpIdeFile>(folderInfo.GetFiles(this, allFiles));
Folders = new ObservableSortedSet<SharpIdeFolder>(this.GetSubFolders(this, allFiles, allFolders), SharpIdeFolderComparer.Instance); Folders = new ObservableList<SharpIdeFolder>(this.GetSubFolders(this, allFiles, allFolders));
} }
public SharpIdeFolder() public SharpIdeFolder()

View File

@@ -52,7 +52,9 @@ public static class TreeMapperV2
allFolders.Add(subFolder); allFolders.Add(subFolder);
}); });
return subFolders.ToList(); var sharpIdeFolders = subFolders.ToList();
sharpIdeFolders.Sort(SharpIdeFolderComparer.Instance);
return sharpIdeFolders;
} }
public static List<SharpIdeFile> GetFiles(string csprojectPath, SharpIdeProjectModel sharpIdeProjectModel, ConcurrentBag<SharpIdeFile> allFiles) public static List<SharpIdeFile> GetFiles(string csprojectPath, SharpIdeProjectModel sharpIdeProjectModel, ConcurrentBag<SharpIdeFile> allFiles)
@@ -73,11 +75,13 @@ public static class TreeMapperV2
return []; 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, Path = f.FullName,
Name = f.Name, Name = f.Name,
Parent = parent Parent = parent
}).ToList(); }).ToList();
sharpIdeFiles.Sort(SharpIdeFileComparer.Instance);
return sharpIdeFiles;
} }
} }

View File

@@ -17,8 +17,8 @@ public interface IExpandableSharpIdeNode
public interface IFolderOrProject : IExpandableSharpIdeNode, IChildSharpIdeNode public interface IFolderOrProject : IExpandableSharpIdeNode, IChildSharpIdeNode
{ {
public ObservableSortedSet<SharpIdeFolder> Folders { get; init; } public ObservableList<SharpIdeFolder> Folders { get; init; }
public ObservableSortedSet<SharpIdeFile> Files { get; init; } public ObservableList<SharpIdeFile> Files { get; init; }
public string Name { get; set; } public string Name { get; set; }
public string ChildNodeBasePath { get; } public string ChildNodeBasePath { get; }
} }
@@ -50,9 +50,9 @@ public class SharpIdeSolutionModel : ISharpIdeNode, IExpandableSharpIdeNode
public required string DirectoryPath { get; set; } public required string DirectoryPath { get; set; }
public required ObservableHashSet<SharpIdeProjectModel> Projects { get; set; } public required ObservableHashSet<SharpIdeProjectModel> Projects { get; set; }
public required ObservableHashSet<SharpIdeSolutionFolder> SlnFolders { get; set; } public required ObservableHashSet<SharpIdeSolutionFolder> SlnFolders { get; set; }
public required HashSet<SharpIdeProjectModel> AllProjects { get; set; } public required HashSet<SharpIdeProjectModel> AllProjects { get; set; } // TODO: this isn't thread safe
public required HashSet<SharpIdeFile> AllFiles { get; set; } public required HashSet<SharpIdeFile> AllFiles { get; set; } // TODO: this isn't thread safe
public required HashSet<SharpIdeFolder> AllFolders { get; set; } public required HashSet<SharpIdeFolder> AllFolders { get; set; } // TODO: this isn't thread safe
public bool Expanded { get; set; } public bool Expanded { get; set; }
[SetsRequiredMembers] [SetsRequiredMembers]
@@ -96,8 +96,8 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi
public required string Name { get; set; } public required string Name { get; set; }
public required string FilePath { get; set; } public required string FilePath { get; set; }
public string ChildNodeBasePath => Path.GetDirectoryName(FilePath)!; public string ChildNodeBasePath => Path.GetDirectoryName(FilePath)!;
public required ObservableSortedSet<SharpIdeFolder> Folders { get; init; } public required ObservableList<SharpIdeFolder> Folders { get; init; }
public required ObservableSortedSet<SharpIdeFile> Files { get; init; } public required ObservableList<SharpIdeFile> Files { get; init; }
public bool Expanded { get; set; } public bool Expanded { get; set; }
public required IExpandableSharpIdeNode Parent { get; set; } public required IExpandableSharpIdeNode Parent { get; set; }
public bool Running { get; set; } public bool Running { get; set; }
@@ -110,8 +110,8 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi
Parent = parent; Parent = parent;
Name = projectModel.Model.ActualDisplayName; Name = projectModel.Model.ActualDisplayName;
FilePath = projectModel.FullFilePath; FilePath = projectModel.FullFilePath;
Files = new ObservableSortedSet<SharpIdeFile>(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles), SharpIdeFileComparer.Instance); Files = new ObservableList<SharpIdeFile>(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles));
Folders = new ObservableSortedSet<SharpIdeFolder>(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders), SharpIdeFolderComparer.Instance); Folders = new ObservableList<SharpIdeFolder>(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders));
MsBuildEvaluationProjectTask = ProjectEvaluation.GetProject(projectModel.FullFilePath); MsBuildEvaluationProjectTask = ProjectEvaluation.GetProject(projectModel.FullFilePath);
allProjects.Add(this); allProjects.Add(this);
} }

View File

@@ -203,7 +203,7 @@ public partial class SolutionExplorerPanel : MarginContainer
filesView.ObserveChanged() filesView.ObserveChanged()
.SubscribeAwait(async (innerEvent, ct) => await (innerEvent.Action switch .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), NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
_ => Task.CompletedTask _ => Task.CompletedTask
})).AddTo(this); })).AddTo(this);
@@ -236,7 +236,7 @@ public partial class SolutionExplorerPanel : MarginContainer
filesView.ObserveChanged() filesView.ObserveChanged()
.SubscribeAwait(async (innerEvent, ct) => await (innerEvent.Action switch .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), NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
_ => Task.CompletedTask _ => Task.CompletedTask
})).AddTo(this); })).AddTo(this);
@@ -275,7 +275,7 @@ public partial class SolutionExplorerPanel : MarginContainer
filesView.ObserveChanged() filesView.ObserveChanged()
.SubscribeAwait(async (innerEvent, ct) => await (innerEvent.Action switch .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), NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
_ => Task.CompletedTask _ => Task.CompletedTask
})).AddTo(this); })).AddTo(this);
@@ -283,9 +283,18 @@ public partial class SolutionExplorerPanel : MarginContainer
} }
[RequiresGodotUiThread] [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.SetText(0, sharpIdeFile.Name);
fileItem.SetIcon(0, CsharpFileIcon); fileItem.SetIcon(0, CsharpFileIcon);
fileItem.SetCustomColor(0, GetColorForGitStatus(sharpIdeFile.GitStatus)); fileItem.SetCustomColor(0, GetColorForGitStatus(sharpIdeFile.GitStatus));