Display sln explorer items

This commit is contained in:
Matt Parker
2025-08-01 00:11:28 +10:00
parent 5dc929f97a
commit d5605f0860
7 changed files with 180 additions and 78 deletions

View File

@@ -0,0 +1,18 @@
namespace SharpIDE.Application.Features.SolutionDiscovery;
public class SharpIdeFile
{
public required string Path { get; set; }
public required string Name { get; set; }
}
public class SharpIdeFolder
{
public required string Path { get; set; }
public required string Name { get; set; }
public required List<SharpIdeFile> Files { get; set; }
public required List<SharpIdeFolder> Folders { get; set; }
// public required int Depth { get; set; }
public bool Expanded { get; set; }
}

View File

@@ -0,0 +1,86 @@
using System.Collections.Concurrent;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
namespace SharpIDE.Application.Features.SolutionDiscovery;
public static class TreeMapperV2
{
public static List<SharpIdeFolder> GetSubFolders(string csprojectPath)
{
var projectDirectory = Path.GetDirectoryName(csprojectPath)!;
var rootFolder = new SharpIdeFolder
{
Path = projectDirectory,
Name = null!,
Files = [],
Folders = []
};
var subFolders = rootFolder.GetSubFolders();
return subFolders;
}
public static List<SharpIdeFolder> GetSubFolders(this SharpIdeFolder folder)
{
var directoryInfo = new DirectoryInfo(folder.Path);
ConcurrentBag<SharpIdeFolder> subFolders = [];
List<DirectoryInfo> subFolderInfos;
try
{
subFolderInfos = directoryInfo.EnumerateDirectories("*", new EnumerationOptions
{
IgnoreInaccessible = false,
AttributesToSkip = FileAttributes.ReparsePoint
}).ToList();
}
catch (UnauthorizedAccessException)
{
return subFolders.ToList();
}
Parallel.ForEach(subFolderInfos, subFolderInfo =>
{
var subFolder = new SharpIdeFolder
{
Path = subFolderInfo.FullName,
Name = subFolderInfo.Name,
Files = GetFiles(subFolderInfo),
Folders = new SharpIdeFolder
{
Path = subFolderInfo.FullName,
Name = subFolderInfo.Name,
Files = [],
Folders = []
}.GetSubFolders()
};
subFolders.Add(subFolder);
});
return subFolders.ToList();
}
public static List<SharpIdeFile> GetFiles(string csprojectPath)
{
var projectDirectory = Path.GetDirectoryName(csprojectPath)!;
var directoryInfo = new DirectoryInfo(projectDirectory);
return GetFiles(directoryInfo);
}
public static List<SharpIdeFile> GetFiles(DirectoryInfo directoryInfo)
{
List<FileInfo> fileInfos;
try
{
fileInfos = directoryInfo.EnumerateFiles().ToList();
}
catch (UnauthorizedAccessException)
{
return [];
}
return fileInfos.Select(f => new SharpIdeFile
{
Path = f.FullName,
Name = f.Name
}).ToList();
}
}

View File

@@ -21,4 +21,5 @@ public class IntermediateProjectModel
{ {
public required SolutionProjectModel Model { get; set; } public required SolutionProjectModel Model { get; set; }
public required string FullFilePath { get; set; } public required string FullFilePath { get; set; }
public required Guid Id { get; set; }
} }

View File

@@ -17,4 +17,6 @@ public class SharpIdeProjectModel
{ {
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 required List<SharpIdeFolder> Folders { get; set; }
public required List<SharpIdeFile> Files { get; set; }
} }

View File

@@ -27,11 +27,14 @@ public static class VsPersistenceMapper
return solutionModel; return solutionModel;
} }
private static SharpIdeProjectModel GetSharpIdeProjectModel(IntermediateProjectModel projectModel) => new SharpIdeProjectModel() private static SharpIdeProjectModel GetSharpIdeProjectModel(IntermediateProjectModel projectModel) => new SharpIdeProjectModel
{ {
Name = projectModel.Model.DisplayName!, Name = projectModel.Model.ActualDisplayName,
FilePath = projectModel.Model.FilePath, FilePath = projectModel.Model.FilePath,
}; Files = TreeMapperV2.GetFiles(projectModel.FullFilePath),
Folders = TreeMapperV2.GetSubFolders(projectModel.FullFilePath)
};
private static SharpIdeSolutionFolder GetSharpIdeSolutionFolder(IntermediateSlnFolderModel folderModel) => new SharpIdeSolutionFolder() private static SharpIdeSolutionFolder GetSharpIdeSolutionFolder(IntermediateSlnFolderModel folderModel) => new SharpIdeSolutionFolder()
{ {
@@ -49,7 +52,7 @@ public static class VsPersistenceMapper
var rootFolders = vsSolution.SolutionFolders var rootFolders = vsSolution.SolutionFolders
.Where(f => f.Parent is null) .Where(f => f.Parent is null)
.Select(f => BuildFolderTree(f, vsSolution.SolutionFolders, vsSolution.SolutionProjects)) .Select(f => BuildFolderTree(f, solutionFilePath, vsSolution.SolutionFolders, vsSolution.SolutionProjects))
.ToList(); .ToList();
var solutionModel = new IntermediateSolutionModel var solutionModel = new IntermediateSolutionModel
@@ -59,19 +62,20 @@ public static class VsPersistenceMapper
Projects = vsSolution.SolutionProjects.Where(p => p.Parent is null).Select(s => new IntermediateProjectModel Projects = vsSolution.SolutionProjects.Where(p => p.Parent is null).Select(s => new IntermediateProjectModel
{ {
Model = s, Model = s,
FullFilePath = Path.GetFullPath(s.FilePath) Id = s.Id,
FullFilePath = new DirectoryInfo(Path.Join(Path.GetDirectoryName(solutionFilePath), s.FilePath)).FullName
}).ToList(), }).ToList(),
SolutionFolders = rootFolders SolutionFolders = rootFolders
}; };
return solutionModel; return solutionModel;
} }
private static IntermediateSlnFolderModel BuildFolderTree(SolutionFolderModel folder, private static IntermediateSlnFolderModel BuildFolderTree(SolutionFolderModel folder, string solutionFilePath,
IReadOnlyList<SolutionFolderModel> allSolutionFolders, IReadOnlyList<SolutionProjectModel> allSolutionProjects) IReadOnlyList<SolutionFolderModel> allSolutionFolders, IReadOnlyList<SolutionProjectModel> allSolutionProjects)
{ {
var childFolders = allSolutionFolders var childFolders = allSolutionFolders
.Where(f => f.Parent == folder) .Where(f => f.Parent == folder)
.Select(f => BuildFolderTree(f, allSolutionFolders, allSolutionProjects)) .Select(f => BuildFolderTree(f, solutionFilePath, allSolutionFolders, allSolutionProjects))
.ToList(); .ToList();
var projectsInFolder = allSolutionProjects var projectsInFolder = allSolutionProjects
@@ -79,7 +83,8 @@ public static class VsPersistenceMapper
.Select(s => new IntermediateProjectModel .Select(s => new IntermediateProjectModel
{ {
Model = s, Model = s,
FullFilePath = Path.GetFullPath(s.FilePath) Id = s.Id,
FullFilePath = new DirectoryInfo(Path.Join(Path.GetDirectoryName(solutionFilePath), s.FilePath)).FullName
}) })
.ToList(); .ToList();

View File

@@ -1,18 +1,23 @@
@using Ardalis.GuardClauses @using SharpIDE.Application.Features.SolutionDiscovery
@using Microsoft.Build.Construction
@using SharpIDE.Application.Features.SolutionDiscovery
@using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence @using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence
@if (_solutionFile is null) @if (SolutionModel is null)
{ {
return; return;
} }
<MudTreeView T="SharpIdeSolutionModel" Dense="true">
<MudTreeView T="ProjectInSolution" Dense="true" > <MudTreeViewItem TextTypo="Typo.body2" Expanded="true" Icon="@Icons.Material.Filled.Folder" IconColor="Color.Primary" Value="@SolutionModel" Text="@SolutionModel.Name">
@foreach(var project in _rootNodes) <MudTreeView Dense="true">
{ @foreach (var folder in SolutionModel.Folders)
@GetProjectFragment(project) {
} @GetSolutionFolderFragment(folder)
}
@foreach(var project in SolutionModel.Projects)
{
@GetProjectFragment(project)
}
</MudTreeView>
</MudTreeViewItem>
</MudTreeView> </MudTreeView>
@@ -23,71 +28,56 @@
[Parameter, EditorRequired] [Parameter, EditorRequired]
public SharpIdeSolutionModel SolutionModel { get; set; } = null!; public SharpIdeSolutionModel SolutionModel { get; set; } = null!;
private SolutionFile _solutionFile = null!; private RenderFragment GetSolutionFolderFragment(SharpIdeSolutionFolder slnFolder) =>
private List<ProjectInSolution> _rootNodes = [];
private Dictionary<string, Folder?> _folders = new();
private RenderFragment GetProjectFragment(ProjectInSolution project) =>
@<text> @<text>
<MudTreeViewItem TextTypo="Typo.body2" Icon="@Icons.Material.Filled.Folder" IconColor="Color.Primary" Value="project" Text="@project.ProjectName"> <MudTreeViewItem TextTypo="Typo.body2" Icon="@Icons.Material.Filled.Folder" IconColor="Color.Primary" Value="@slnFolder" Text="@slnFolder.Name">
@foreach(var child in _solutionFile.ProjectsByGuid.Values.Where(s => s.ParentProjectGuid == project.ProjectGuid).OrderBy(s => s.ProjectName)) @foreach(var childFolder in slnFolder.Folders)
{ {
@GetProjectFragment(child) @GetSolutionFolderFragment(childFolder)
} }
@if (_folders.GetValueOrDefault(project.ProjectGuid, null) is {} value) @foreach(var childProject in slnFolder.Projects)
{ {
@GetFolderFragment(value) @GetProjectFragment(childProject)
} }
</MudTreeViewItem> </MudTreeViewItem>
</text>; </text>;
private RenderFragment GetFolderFragment(Folder folder) => private RenderFragment GetProjectFragment(SharpIdeProjectModel project) =>
@<text> @<text>
@foreach (var subFolder in folder.Folders) <MudTreeViewItem TextTypo="Typo.body2" Icon="@Icons.Material.Filled.Folder" IconColor="Color.Primary" Text="@project.Name" Value="project">
{ @foreach (var folder in project.Folders)
<MudTreeViewItem TextTypo="Typo.body2" @bind-Expanded="subFolder.Expanded" T="ProjectInSolution" Text="@subFolder.Name"> {
@if (subFolder.Expanded) <MudTreeViewItem TextTypo="Typo.body2" Icon="@Icons.Material.Filled.Folder" IconColor="Color.Default" @bind-Expanded="folder.Expanded" Text="@folder.Name" Value="@folder">
{ @GetFolderFragment(folder)
@GetFolderFragment(subFolder) </MudTreeViewItem>
} }
</MudTreeViewItem> @foreach(var file in project.Files)
} {
@foreach (var file in folder.Files) @GetFileFragment(file)
{ }
<MudTreeViewItem TextTypo="Typo.body2" T="ProjectInSolution" Text="@file.Name" /> </MudTreeViewItem>
} </text>;
</text>;
protected override async Task OnInitializedAsync() private RenderFragment GetFolderFragment(SharpIdeFolder folder) =>
{ @<text>
Guard.Against.NullOrWhiteSpace(SolutionFilePath); @foreach (var subFolder in folder.Folders)
await Task.Run(() => LoadSolution(SolutionFilePath)); {
} <MudTreeViewItem TextTypo="Typo.body2" Icon="@Icons.Material.Filled.Folder" IconColor="Color.Default" @bind-Expanded="subFolder.Expanded" Text="@subFolder.Name" Value="@subFolder">
@if (subFolder.Expanded)
{
@GetFolderFragment(subFolder)
}
</MudTreeViewItem>
}
@foreach (var file in folder.Files)
{
@GetFileFragment(file)
}
</text>;
private void LoadSolution(string solutionPath) private RenderFragment GetFileFragment(SharpIdeFile file) =>
{ @<text>
var solutionFile = GetNodesInSolution.ParseSolutionFileFromPath(solutionPath); <MudTreeViewItem T="SharpIdeFile" TextTypo="Typo.body2" Text="@file.Name"/>
ArgumentNullException.ThrowIfNull(solutionFile); </text>;
_solutionFile = solutionFile;
var rootNodes = solutionFile.ProjectsByGuid.Values.Where(p => p.ParentProjectGuid == null).OrderBy(s => s.ProjectName).ToList();
_rootNodes = rootNodes;
var folders2 = _solutionFile.ProjectsByGuid.Values
.Where(s => s.ProjectType == SolutionProjectType.KnownToBeMSBuildFormat)
//.Take(2)
.Select(s =>
{
var rootFolder = new Folder
{
Name = Path.GetFileNameWithoutExtension(s.AbsolutePath),
Path = Path.GetDirectoryName(s.AbsolutePath)!,
IsPseudoFolder = false
};
rootFolder.Folders = rootFolder.GetSubFolders();
return (s, rootFolder);
})
.ToDictionary(s => s.s.ProjectGuid, s => s.Item2);
_folders = folders2!;
}
} }

View File

@@ -22,7 +22,7 @@
@* @Body *@ @* @Body *@
@if (_solutionFilePath is not null) @if (_solutionFilePath is not null)
{ {
<CodeViewer FilePath="C:\Users\matth\Documents\Git\SharpIDE.Photino\src\SharpIDE.Photino\Program.cs" /> <CodeViewer FilePath="C:\Users\Matthew\Documents\Git\SharpIDE.Photino\src\SharpIDE.Photino\Program.cs" />
} }
</MudContainer> </MudContainer>
</MudMainContent> </MudMainContent>
@@ -48,7 +48,7 @@
var solutionFilePath = (string)result.Data!; var solutionFilePath = (string)result.Data!;
_solutionFilePath = solutionFilePath; _solutionFilePath = solutionFilePath;
await BuildService.BuildSolutionAsync(_solutionFilePath); //await BuildService.BuildSolutionAsync(_solutionFilePath);
var solutionModel = await VsPersistenceMapper.GetSolutionModel(_solutionFilePath); var solutionModel = await VsPersistenceMapper.GetSolutionModel(_solutionFilePath);
_solutionModel = solutionModel; _solutionModel = solutionModel;
} }