populate sln explorer tree in background thread
This commit is contained in:
@@ -119,7 +119,7 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi
|
|||||||
DirectoryPath = Path.GetDirectoryName(projectModel.FullFilePath)!;
|
DirectoryPath = Path.GetDirectoryName(projectModel.FullFilePath)!;
|
||||||
Files = new ObservableList<SharpIdeFile>(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles));
|
Files = new ObservableList<SharpIdeFile>(TreeMapperV2.GetFiles(projectModel.FullFilePath, this, allFiles));
|
||||||
Folders = new ObservableList<SharpIdeFolder>(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders));
|
Folders = new ObservableList<SharpIdeFolder>(TreeMapperV2.GetSubFolders(projectModel.FullFilePath, this, allFiles, allFolders));
|
||||||
MsBuildEvaluationProjectTask = ProjectEvaluation.GetProject(projectModel.FullFilePath);
|
MsBuildEvaluationProjectTask = Task.Run(() => ProjectEvaluation.GetProject(projectModel.FullFilePath));
|
||||||
allProjects.Add(this);
|
allProjects.Add(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using Ardalis.GuardClauses;
|
|||||||
using Godot;
|
using Godot;
|
||||||
using ObservableCollections;
|
using ObservableCollections;
|
||||||
using R3;
|
using R3;
|
||||||
|
using SharpIDE.Application;
|
||||||
using SharpIDE.Application.Features.Analysis;
|
using SharpIDE.Application.Features.Analysis;
|
||||||
using SharpIDE.Application.Features.NavigationHistory;
|
using SharpIDE.Application.Features.NavigationHistory;
|
||||||
using SharpIDE.Application.Features.SolutionDiscovery;
|
using SharpIDE.Application.Features.SolutionDiscovery;
|
||||||
@@ -27,6 +28,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
public Texture2D SlnIcon { get; set; } = null!;
|
public Texture2D SlnIcon { get; set; } = null!;
|
||||||
|
|
||||||
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
|
||||||
|
private PanelContainer _panelContainer = null!;
|
||||||
private Tree _tree = null!;
|
private Tree _tree = null!;
|
||||||
private TreeItem _rootItem = null!;
|
private TreeItem _rootItem = null!;
|
||||||
|
|
||||||
@@ -35,8 +37,11 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
private (List<IFileOrFolder>, ClipboardOperation)? _itemsOnClipboard;
|
private (List<IFileOrFolder>, ClipboardOperation)? _itemsOnClipboard;
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
_tree = GetNode<Tree>("Tree");
|
_panelContainer = GetNode<PanelContainer>("PanelContainer");
|
||||||
|
_tree = GetNode<Tree>("%Tree");
|
||||||
_tree.ItemMouseSelected += TreeOnItemMouseSelected;
|
_tree.ItemMouseSelected += TreeOnItemMouseSelected;
|
||||||
|
// Remove the tree from the scene tree for now, we will add it back when we bind to a solution
|
||||||
|
_panelContainer.RemoveChild(_tree);
|
||||||
GodotGlobalEvents.Instance.FileExternallySelected.Subscribe(OnFileExternallySelected);
|
GodotGlobalEvents.Instance.FileExternallySelected.Subscribe(OnFileExternallySelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,10 +130,15 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BindToSolution() => BindToSolution(SolutionModel);
|
public async Task BindToSolution() => await BindToSolution(SolutionModel);
|
||||||
[RequiresGodotUiThread]
|
[RequiresGodotUiThread]
|
||||||
public void BindToSolution(SharpIdeSolutionModel solution)
|
public async Task BindToSolution(SharpIdeSolutionModel solution)
|
||||||
{
|
{
|
||||||
|
await Task.CompletedTask.ConfigureAwait(ConfigureAwaitOptions.ForceYielding);
|
||||||
|
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(SolutionExplorerPanel)}.{nameof(BindToSolution)}");
|
||||||
|
|
||||||
|
// Solutions with hundreds of thousands of files can cause the ui to freeze as the tree is populated
|
||||||
|
// the Tree has been removed from the scene tree in _Ready, so we can operate on it off the ui thread, then add it back
|
||||||
_tree.Clear();
|
_tree.Clear();
|
||||||
|
|
||||||
// Root
|
// Root
|
||||||
@@ -147,7 +157,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => e.NewItem.View.Value = CreateProjectTreeItem(_tree, _rootItem, e.NewItem.Value)),
|
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => e.NewItem.View.Value = CreateProjectTreeItem(_tree, _rootItem, e.NewItem.Value)),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(e.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(e.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
|
|
||||||
// Observe Solution Folders
|
// Observe Solution Folders
|
||||||
var foldersView = solution.SlnFolders.CreateView(y => new TreeItemContainer());
|
var foldersView = solution.SlnFolders.CreateView(y => new TreeItemContainer());
|
||||||
@@ -158,10 +168,14 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => e.NewItem.View.Value = CreateSlnFolderTreeItem(_tree, _rootItem, e.NewItem.Value)),
|
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => e.NewItem.View.Value = CreateSlnFolderTreeItem(_tree, _rootItem, e.NewItem.Value)),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(e.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(e.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
|
|
||||||
rootItem.SetCollapsedRecursive(true);
|
rootItem.SetCollapsedRecursive(true);
|
||||||
rootItem.Collapsed = false;
|
rootItem.Collapsed = false;
|
||||||
|
await this.InvokeAsync(() =>
|
||||||
|
{
|
||||||
|
_panelContainer.AddChild(_tree);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[RequiresGodotUiThread]
|
[RequiresGodotUiThread]
|
||||||
@@ -182,7 +196,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateSlnFolderTreeItem(_tree, folderItem, innerEvent.NewItem.Value)),
|
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateSlnFolderTreeItem(_tree, folderItem, innerEvent.NewItem.Value)),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
|
|
||||||
var projectsView = slnFolder.Projects.CreateView(y => new TreeItemContainer());
|
var projectsView = slnFolder.Projects.CreateView(y => new TreeItemContainer());
|
||||||
projectsView.Unfiltered.ToList().ForEach(s => s.View.Value = CreateProjectTreeItem(_tree, folderItem, s.Value));
|
projectsView.Unfiltered.ToList().ForEach(s => s.View.Value = CreateProjectTreeItem(_tree, folderItem, s.Value));
|
||||||
@@ -192,7 +206,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateProjectTreeItem(_tree, folderItem, innerEvent.NewItem.Value)),
|
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateProjectTreeItem(_tree, folderItem, innerEvent.NewItem.Value)),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
|
|
||||||
var filesView = slnFolder.Files.CreateView(y => new TreeItemContainer());
|
var filesView = slnFolder.Files.CreateView(y => new TreeItemContainer());
|
||||||
filesView.Unfiltered.ToList().ForEach(s => s.View.Value = CreateFileTreeItem(_tree, folderItem, s.Value));
|
filesView.Unfiltered.ToList().ForEach(s => s.View.Value = CreateFileTreeItem(_tree, folderItem, s.Value));
|
||||||
@@ -202,7 +216,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Add => this.InvokeAsync(() => innerEvent.NewItem.View.Value = CreateFileTreeItem(_tree, folderItem, innerEvent.NewItem.Value, innerEvent.NewStartingIndex)),
|
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);
|
})).AddToDeferred(this);
|
||||||
return folderItem;
|
return folderItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +239,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
|
|
||||||
// Observe project files
|
// Observe project files
|
||||||
var filesView = projectModel.Files.CreateView(y => new TreeItemContainer());
|
var filesView = projectModel.Files.CreateView(y => new TreeItemContainer());
|
||||||
@@ -237,7 +251,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
return projectItem;
|
return projectItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,7 +267,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
.Skip(1).SubscribeOnThreadPool().ObserveOnThreadPool().SubscribeAwait(async (s, ct) =>
|
.Skip(1).SubscribeOnThreadPool().ObserveOnThreadPool().SubscribeAwait(async (s, ct) =>
|
||||||
{
|
{
|
||||||
await this.InvokeAsync(() => folderItem.SetText(0, s));
|
await this.InvokeAsync(() => folderItem.SetText(0, s));
|
||||||
}).AddTo(this);
|
}).AddToDeferred(this);
|
||||||
|
|
||||||
// Observe subfolders
|
// Observe subfolders
|
||||||
var subFoldersView = sharpIdeFolder.Folders.CreateView(y => new TreeItemContainer());
|
var subFoldersView = sharpIdeFolder.Folders.CreateView(y => new TreeItemContainer());
|
||||||
@@ -266,7 +280,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
|
|
||||||
// Observe files
|
// Observe files
|
||||||
var filesView = sharpIdeFolder.Files.CreateView(y => new TreeItemContainer());
|
var filesView = sharpIdeFolder.Files.CreateView(y => new TreeItemContainer());
|
||||||
@@ -278,7 +292,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
NotifyCollectionChangedAction.Move => MoveTreeItem(_tree, innerEvent.NewItem.View, innerEvent.NewItem.Value, innerEvent.OldStartingIndex, innerEvent.NewStartingIndex),
|
||||||
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
NotifyCollectionChangedAction.Remove => FreeTreeItem(innerEvent.OldItem.View.Value),
|
||||||
_ => Task.CompletedTask
|
_ => Task.CompletedTask
|
||||||
})).AddTo(this);
|
})).AddToDeferred(this);
|
||||||
return folderItem;
|
return folderItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,7 +322,7 @@ public partial class SolutionExplorerPanel : MarginContainer
|
|||||||
fileItem.SetText(0, s);
|
fileItem.SetText(0, s);
|
||||||
fileItem.SetIconsForFileExtension(sharpIdeFile);
|
fileItem.SetIconsForFileExtension(sharpIdeFile);
|
||||||
});
|
});
|
||||||
}).AddTo(this);
|
}).AddToDeferred(this);
|
||||||
|
|
||||||
return fileItem;
|
return fileItem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=8 format=3 uid="uid://cy1bb32g7j7dr"]
|
[gd_scene load_steps=9 format=3 uid="uid://cy1bb32g7j7dr"]
|
||||||
|
|
||||||
[ext_resource type="Script" uid="uid://bai53k7ongbxw" path="res://Features/SolutionExplorer/SolutionExplorerPanel.cs" id="1_gjy0r"]
|
[ext_resource type="Script" uid="uid://bai53k7ongbxw" path="res://Features/SolutionExplorer/SolutionExplorerPanel.cs" id="1_gjy0r"]
|
||||||
[ext_resource type="Texture2D" uid="uid://do0edciarrnp0" path="res://Features/SolutionExplorer/Resources/CsharpFile.svg" id="2_8ymw0"]
|
[ext_resource type="Texture2D" uid="uid://do0edciarrnp0" path="res://Features/SolutionExplorer/Resources/CsharpFile.svg" id="2_8ymw0"]
|
||||||
@@ -7,6 +7,12 @@
|
|||||||
[ext_resource type="Texture2D" uid="uid://cqt30ma6xgder" path="res://Features/SolutionExplorer/Resources/Csproj.svg" id="5_r1qfc"]
|
[ext_resource type="Texture2D" uid="uid://cqt30ma6xgder" path="res://Features/SolutionExplorer/Resources/Csproj.svg" id="5_r1qfc"]
|
||||||
[ext_resource type="Texture2D" uid="uid://btlxqx3c08fjj" path="res://Features/SolutionExplorer/Resources/SlnIcon.svg" id="6_idvpu"]
|
[ext_resource type="Texture2D" uid="uid://btlxqx3c08fjj" path="res://Features/SolutionExplorer/Resources/SlnIcon.svg" id="6_idvpu"]
|
||||||
|
|
||||||
|
[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_idvpu"]
|
||||||
|
content_margin_left = 4.0
|
||||||
|
content_margin_top = 4.0
|
||||||
|
content_margin_right = 4.0
|
||||||
|
content_margin_bottom = 5.0
|
||||||
|
|
||||||
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_idvpu"]
|
[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_idvpu"]
|
||||||
content_margin_left = 4.0
|
content_margin_left = 4.0
|
||||||
content_margin_top = 4.0
|
content_margin_top = 4.0
|
||||||
@@ -41,12 +47,17 @@ SlnFolderIcon = ExtResource("4_8ymw0")
|
|||||||
CsprojIcon = ExtResource("5_r1qfc")
|
CsprojIcon = ExtResource("5_r1qfc")
|
||||||
SlnIcon = ExtResource("6_idvpu")
|
SlnIcon = ExtResource("6_idvpu")
|
||||||
|
|
||||||
[node name="Tree" type="Tree" parent="."]
|
[node name="PanelContainer" type="PanelContainer" parent="."]
|
||||||
|
layout_mode = 2
|
||||||
|
|
||||||
|
[node name="Tree" type="Tree" parent="PanelContainer"]
|
||||||
|
unique_name_in_owner = true
|
||||||
layout_mode = 2
|
layout_mode = 2
|
||||||
theme_override_colors/font_color = Color(0.830335, 0.830335, 0.830335, 1)
|
theme_override_colors/font_color = Color(0.830335, 0.830335, 0.830335, 1)
|
||||||
theme_override_constants/v_separation = 1
|
theme_override_constants/v_separation = 1
|
||||||
theme_override_constants/inner_item_margin_left = 2
|
theme_override_constants/inner_item_margin_left = 2
|
||||||
theme_override_constants/draw_guides = 0
|
theme_override_constants/draw_guides = 0
|
||||||
|
theme_override_styles/panel = SubResource("StyleBoxEmpty_idvpu")
|
||||||
theme_override_styles/cursor = SubResource("StyleBoxFlat_idvpu")
|
theme_override_styles/cursor = SubResource("StyleBoxFlat_idvpu")
|
||||||
theme_override_styles/cursor_unfocused = SubResource("StyleBoxFlat_idvpu")
|
theme_override_styles/cursor_unfocused = SubResource("StyleBoxFlat_idvpu")
|
||||||
allow_rmb_select = true
|
allow_rmb_select = true
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ public partial class IdeRoot : Control
|
|||||||
_fileExternalChangeHandler.SolutionModel = solutionModel;
|
_fileExternalChangeHandler.SolutionModel = solutionModel;
|
||||||
_fileChangedService.SolutionModel = solutionModel;
|
_fileChangedService.SolutionModel = solutionModel;
|
||||||
_sharpIdeSolutionModificationService.SolutionModel = solutionModel;
|
_sharpIdeSolutionModificationService.SolutionModel = solutionModel;
|
||||||
Callable.From(_solutionExplorerPanel.BindToSolution).CallDeferred();
|
_ = Task.GodotRun(_solutionExplorerPanel.BindToSolution);
|
||||||
_roslynAnalysis.StartLoadingSolutionInWorkspace(solutionModel);
|
_roslynAnalysis.StartLoadingSolutionInWorkspace(solutionModel);
|
||||||
_fileWatcher.StartWatching(solutionModel);
|
_fileWatcher.StartWatching(solutionModel);
|
||||||
|
|
||||||
|
|||||||
@@ -29,4 +29,23 @@ public static class GodotNodeExtensions
|
|||||||
node.TreeExited += () => disposable.Dispose();
|
node.TreeExited += () => disposable.Dispose();
|
||||||
return disposable;
|
return disposable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void AddToDeferred<T>(this T disposable, Node node) where T : IDisposable
|
||||||
|
{
|
||||||
|
Callable.From(() =>
|
||||||
|
{
|
||||||
|
// Note: Dispose when tree exited, so if node is not inside tree, dispose immediately.
|
||||||
|
if (!node.IsInsideTree())
|
||||||
|
{
|
||||||
|
if (!node.IsNodeReady()) // Before enter tree
|
||||||
|
{
|
||||||
|
GD.PrintErr("AddTo does not support to use before enter tree.");
|
||||||
|
}
|
||||||
|
|
||||||
|
disposable.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
node.TreeExited += () => disposable.Dispose();
|
||||||
|
}).CallDeferred();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user