diff --git a/.editorconfig b/.editorconfig index e4eee44..6761551 100644 --- a/.editorconfig +++ b/.editorconfig @@ -253,3 +253,21 @@ dotnet_naming_style.begins_with__.capitalization = camel_case dotnet_diagnostic.CA1014.severity = none dotnet_diagnostic.CA2007.severity = none dotnet_diagnostic.IDE0070.severity = none + +# IDE0028: Simplify collection initialization +dotnet_diagnostic.IDE0028.severity = warning + +# IDE0290: Use primary constructor +dotnet_diagnostic.IDE0290.severity = none + +# IDE0300: Simplify collection initialization +dotnet_diagnostic.IDE0300.severity = warning + +# IDE0301: Simplify collection initialization +dotnet_diagnostic.IDE0301.severity = warning + +# IDE0303: Simplify collection initialization +dotnet_diagnostic.IDE0303.severity = warning + +# IDE0305: Simplify collection initialization +dotnet_diagnostic.IDE0305.severity = warning diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a83c977..c6fd744 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: jobs: build: - runs-on: windows-2022 + runs-on: windows-2025-vs2026 env: Configuration: Release @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Setup .NET SDK - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 - name: Build run: dotnet build diff --git a/.github/workflows/publish-manually.yml b/.github/workflows/publish-manually.yml index 14f9440..3e789ba 100644 --- a/.github/workflows/publish-manually.yml +++ b/.github/workflows/publish-manually.yml @@ -9,7 +9,7 @@ on: jobs: publish: - runs-on: windows-2022 + runs-on: windows-2025-vs2026 env: Configuration: Release @@ -17,16 +17,16 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: ref: ${{ github.event.inputs.version }} - name: Setup MSBuild - uses: microsoft/setup-msbuild@v2 + uses: microsoft/setup-msbuild@v3 - name: Publish run: msbuild source/ProjectFilter/ProjectFilter.csproj /t:PublishToMarketplace /Restore /v:Minimal /p:BuildBeforePublish=true /p:PersonalAccessToken=${{ secrets.PUBLISHER_TOKEN }} env: # This workflow should be used when the `VsixPublisher` from the `Microsoft.VSSDK.BuildTools` # package fails, so we'll use the `VsixPublisher` that is installed with Visual Studio. - VsixPublisher: "C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VSSDK\\VisualStudioIntegration\\Tools\\Bin\\VsixPublisher.exe" + VsixPublisher: "C:\\Program Files\\Microsoft Visual Studio\\18\\Enterprise\\VSSDK\\VisualStudioIntegration\\Tools\\Bin\\VsixPublisher.exe" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cf675f9..4dcd333 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,7 +6,7 @@ on: jobs: publish: - runs-on: windows-2022 + runs-on: windows-2025-vs2026 env: Configuration: Release @@ -14,10 +14,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v7 - name: Setup MSBuild - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v6 - name: Publish working-directory: source/ProjectFilter diff --git a/CHANGELOG.md b/CHANGELOG.md index b37956a..8687131 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 2.7.0 (2026-09-06) + +- ✨ New option to hide folders that only contain unloaded projects and miscellaneous files. + - # 2.6.1 (2025-08-19) - 🔧 Dependency updates. diff --git a/Directory.Build.props b/Directory.Build.props index cc40040..a93d14d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,11 +1,12 @@ - 2.6.1 + 2.7.0 net48 enable - 10.0 + latest AllEnabledByDefault + true diff --git a/global.json b/global.json index d20023b..5d13fe0 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "10.0.203" + "version": "10.0.400" } } diff --git a/sample/Small/Small.sln b/sample/Small/Small.sln index 2599d0a..e1fdae3 100644 --- a/sample/Small/Small.sln +++ b/sample/Small/Small.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.1.31911.260 +# Visual Studio Version 18 +VisualStudioVersion = 18.10.12120.281 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "source", "source", "{8BB688F8-2B48-4692-9DFC-A199435AB13D}" EndProject @@ -17,6 +17,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Beta", "source\Beta\Beta.csproj", "{00020190-70EA-4CC3-B3C1-CBD9720BD7A7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Gamma", "Gamma", "{2715BEFA-EFBC-4AF8-966F-2395B8035457}" + ProjectSection(SolutionItems) = preProject + source\Gamma\Directory.Build.props = source\Gamma\Directory.Build.props + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gamma.Abstractions", "source\Gamma\Gamma.Abstractions\Gamma.Abstractions.csproj", "{3F39F3CF-2E4E-4CB0-AFF1-A11658E24EF1}" EndProject diff --git a/sample/Small/source/Gamma/Directory.Build.props b/sample/Small/source/Gamma/Directory.Build.props new file mode 100644 index 0000000..f2514e0 --- /dev/null +++ b/sample/Small/source/Gamma/Directory.Build.props @@ -0,0 +1,4 @@ + + + diff --git a/source/ProjectFilter/Commands/FilterProjectsCommand.cs b/source/ProjectFilter/Commands/FilterProjectsCommand.cs index ef4f08e..aef685a 100644 --- a/source/ProjectFilter/Commands/FilterProjectsCommand.cs +++ b/source/ProjectFilter/Commands/FilterProjectsCommand.cs @@ -63,6 +63,7 @@ protected async override Task ExecuteAsync(OleMenuCmdEventArgs e) { vm.LoadProjectDependencies = solutionSettings?.LoadProjectDependencies ?? globalSettings.LoadProjectDependencies; vm.UseRegularExpressions = solutionSettings?.UseRegularExpressions ?? globalSettings.UseRegularExpressions; vm.ExpandLoadedProjects = solutionSettings?.ExpandLoadedProjects ?? globalSettings.ExpandLoadedProjects; + vm.HideSolutionFoldersWithoutLoadedProjects = solutionSettings?.HideSolutionFoldersWithoutLoadedProjects ?? globalSettings.HideSolutionFoldersWithoutLoadedProjects; dialog = new FilterDialog { DataContext = vm @@ -76,7 +77,8 @@ protected async override Task ExecuteAsync(OleMenuCmdEventArgs e) { solutionSettings = new SolutionSettings { LoadProjectDependencies = vm.LoadProjectDependencies, UseRegularExpressions = vm.UseRegularExpressions, - ExpandLoadedProjects = vm.ExpandLoadedProjects + ExpandLoadedProjects = vm.ExpandLoadedProjects, + HideSolutionFoldersWithoutLoadedProjects = vm.HideSolutionFoldersWithoutLoadedProjects }; PopulateNodeSettings(vm.Items, solutionSettings.Nodes); @@ -87,6 +89,7 @@ protected async override Task ExecuteAsync(OleMenuCmdEventArgs e) { globalSettings.LoadProjectDependencies = vm.LoadProjectDependencies; globalSettings.UseRegularExpressions = vm.UseRegularExpressions; globalSettings.ExpandLoadedProjects = vm.ExpandLoadedProjects; + globalSettings.HideSolutionFoldersWithoutLoadedProjects = vm.HideSolutionFoldersWithoutLoadedProjects; await globalSettings.SaveAsync(); if (result) { diff --git a/source/ProjectFilter/Services/ExtensionSettings.cs b/source/ProjectFilter/Services/ExtensionSettings.cs index a19990b..2477d2d 100644 --- a/source/ProjectFilter/Services/ExtensionSettings.cs +++ b/source/ProjectFilter/Services/ExtensionSettings.cs @@ -13,6 +13,7 @@ public ExtensionSettings() { LoadProjectDependencies = true; UseRegularExpressions = false; ExpandLoadedProjects = true; + HideSolutionFoldersWithoutLoadedProjects = false; } @@ -24,4 +25,7 @@ public ExtensionSettings() { public bool ExpandLoadedProjects { get; set; } + + public bool HideSolutionFoldersWithoutLoadedProjects { get; set; } + } diff --git a/source/ProjectFilter/Services/FilterOptions.cs b/source/ProjectFilter/Services/FilterOptions.cs index 5af2e26..7a484c0 100644 --- a/source/ProjectFilter/Services/FilterOptions.cs +++ b/source/ProjectFilter/Services/FilterOptions.cs @@ -12,12 +12,14 @@ public FilterOptions( IEnumerable projectsToLoad, IEnumerable projectsToUnload, bool loadProjectDependencies, - bool expandLoadedProjects + bool expandLoadedProjects, + bool hideSolutionFoldersWithoutLoadedProjects ) { - ProjectsToLoad = projectsToLoad.ToList(); - ProjectsToUnload = projectsToUnload.ToList(); + ProjectsToLoad = [.. projectsToLoad]; + ProjectsToUnload = [.. projectsToUnload]; LoadProjectDependencies = loadProjectDependencies; ExpandLoadedProjects = expandLoadedProjects; + HideSolutionFoldersWithoutLoadedProjects = hideSolutionFoldersWithoutLoadedProjects; } @@ -32,4 +34,7 @@ bool expandLoadedProjects public bool ExpandLoadedProjects{ get; } + + public bool HideSolutionFoldersWithoutLoadedProjects { get; } + } diff --git a/source/ProjectFilter/Services/FilterService.State.cs b/source/ProjectFilter/Services/FilterService.State.cs index 7a48c3f..3a5fafc 100644 --- a/source/ProjectFilter/Services/FilterService.State.cs +++ b/source/ProjectFilter/Services/FilterService.State.cs @@ -24,13 +24,13 @@ public State(IWaitDialog waitDialog, ThreadedWaitDialogProgressData initialProgr _currentProgress = initialProgress; Solution = solution; - _projectsToLoad = new HashSet(); - _loadedProjects = new HashSet(); + _projectsToLoad = []; + _loadedProjects = []; - _projectsToUnload = new HashSet(); - _unloadedProjects = new HashSet(); + _projectsToUnload = []; + _unloadedProjects = []; - ProjectsVisitedWhileLoading = new HashSet(); + ProjectsVisitedWhileLoading = []; RequiresProjectDependencyCalculation = true; } @@ -109,7 +109,7 @@ private void UpdateProgressSteps() { public IEnumerable GetLoadedProjects() { - return _loadedProjects.ToList(); + return [.. _loadedProjects]; } diff --git a/source/ProjectFilter/Services/FilterService.cs b/source/ProjectFilter/Services/FilterService.cs index 9d88821..9d3bfc8 100644 --- a/source/ProjectFilter/Services/FilterService.cs +++ b/source/ProjectFilter/Services/FilterService.cs @@ -62,8 +62,8 @@ public async Task ApplyAsync(FilterOptions options) { expandedFolders = new HashSet(); } - projectsToUnload = options.ProjectsToUnload.ToList(); - projectsToLoad = options.ProjectsToLoad.ToList(); + projectsToUnload = [.. options.ProjectsToUnload]; + projectsToLoad = [.. options.ProjectsToLoad]; // Work out which projects actually need to be unloaded // so that we can calculate an accurate progress. If a @@ -121,6 +121,15 @@ public async Task ApplyAsync(FilterOptions options) { // remain hidden because the user cancelled half way through. await solutionExplorer.HideUnloadedProjectsAsync(); + // Hiding unloaded projects will hide the solution folders that those hidden + // projects are in as long as there is nothing else in those folders. If all + // of the projects in a solution folder have been unloaded, but the folder contains + // other solution item (like a readme file or editorconfig file), the folder + // will not be hidden. We have an option that allows those folders to be hidden. + if (options.HideSolutionFoldersWithoutLoadedProjects) { + await solutionExplorer.HideSolutionFoldersWithoutLoadedProjectsAsync(); + } + // Expand the projects if we are supposed to. For some reason, Visual Studio seems to expand // the projects anyway, so if we are not supposed to expand them, then we will collapse them. if (options.ExpandLoadedProjects) { @@ -262,7 +271,7 @@ private static async Task> GetProjectDependenciesAsync(Guid id solutionBuildManager = (IVsSolutionBuildManager2)await VS.Services.GetSolutionBuildManagerAsync(); - output = new List(); + output = []; if (state.Solution.TryGetHierarchy(identifier, out IVsHierarchy hierarchy)) { IVsHierarchy[] dependencies; @@ -282,7 +291,7 @@ private static async Task> GetProjectDependenciesAsync(Guid id state.RequiresProjectDependencyCalculation = false; } - dependencies = Array.Empty(); + dependencies = []; // First we need to ask for the dependencies without specifying an // array so that we can find out how many dependencies there are. @@ -364,7 +373,7 @@ private static async Task> GetProjectsToCollapseAsync(ISolutio // All of the projects that were loaded should be collapsed. - projects = state.GetLoadedProjects().ToHashSet(); + projects = [.. state.GetLoadedProjects()]; // Any solution folders that are now expanded // and were not originally expanded should also be collapsed. diff --git a/source/ProjectFilter/Services/HierarchyNode.cs b/source/ProjectFilter/Services/HierarchyNode.cs index ec1db95..bbb5fe6 100644 --- a/source/ProjectFilter/Services/HierarchyNode.cs +++ b/source/ProjectFilter/Services/HierarchyNode.cs @@ -21,7 +21,7 @@ ImageMoniker expandedIcon Name = name; CollapsedIcon = collapsedIcon; ExpandedIcon = expandedIcon; - _children = new List(); + _children = []; IsFolder = false; IsLoaded = false; diff --git a/source/ProjectFilter/Services/HierarchyProvider.cs b/source/ProjectFilter/Services/HierarchyProvider.cs index d61a190..817ebd8 100644 --- a/source/ProjectFilter/Services/HierarchyProvider.cs +++ b/source/ProjectFilter/Services/HierarchyProvider.cs @@ -23,7 +23,7 @@ public async Task> GetHierarchyAsync() { nodes = await GetNodesAsync(); - mapping = new Dictionary(); + mapping = []; foreach (var item in nodes) { mapping[item.Hierarchy.Identifier] = item.Hierarchy; @@ -34,7 +34,7 @@ public async Task> GetHierarchyAsync() { // to sort each collection of children individually. nodes.Sort((x, y) => CompareNodes(x.Hierarchy, y.Hierarchy)); - roots = new List(); + roots = []; // Add child nodes to the parents and find the root nodes. foreach (var node in nodes) { @@ -64,7 +64,7 @@ public async Task> GetHierarchyAsync() { imageService = (IVsImageService2)await VS.Services.GetImageServiceAsync(); hierarchyItemManager = await VS.GetMefServiceAsync(); - output = new List<(HierarchyNode Hierarchy, Guid Parent)>(); + output = []; foreach (var hierarchy in solution.GetAllProjectHierarchies(ProjectStateFilter.All)) { if (!TryGetIdentifier(solution, hierarchy, out Guid identifier)) { diff --git a/source/ProjectFilter/Services/IExtensionSettings.cs b/source/ProjectFilter/Services/IExtensionSettings.cs index 97c984d..b3706c0 100644 --- a/source/ProjectFilter/Services/IExtensionSettings.cs +++ b/source/ProjectFilter/Services/IExtensionSettings.cs @@ -17,6 +17,9 @@ public interface IExtensionSettings { bool ExpandLoadedProjects { get; set; } + bool HideSolutionFoldersWithoutLoadedProjects { get; set; } + + Task LoadAsync(); diff --git a/source/ProjectFilter/Services/ISolutionExplorer.cs b/source/ProjectFilter/Services/ISolutionExplorer.cs index f521ba0..aa6ce0c 100644 --- a/source/ProjectFilter/Services/ISolutionExplorer.cs +++ b/source/ProjectFilter/Services/ISolutionExplorer.cs @@ -16,6 +16,9 @@ public interface ISolutionExplorer { Task HideUnloadedProjectsAsync(); + Task HideSolutionFoldersWithoutLoadedProjectsAsync(); + + Task ExpandAsync(IEnumerable projects); diff --git a/source/ProjectFilter/Services/ISolutionExplorerNode.cs b/source/ProjectFilter/Services/ISolutionExplorerNode.cs new file mode 100644 index 0000000..9b25df3 --- /dev/null +++ b/source/ProjectFilter/Services/ISolutionExplorerNode.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + + +namespace ProjectFilter.Services; + + +public interface ISolutionExplorerNode { + + bool IsFolder { get; } + + + bool IsProject { get; } + + + bool IsLoaded { get; } + + + IReadOnlyList Children { get; } + +} diff --git a/source/ProjectFilter/Services/SolutionExplorer.cs b/source/ProjectFilter/Services/SolutionExplorer.cs index 2947d3c..af279f5 100644 --- a/source/ProjectFilter/Services/SolutionExplorer.cs +++ b/source/ProjectFilter/Services/SolutionExplorer.cs @@ -8,6 +8,8 @@ using System; using System.Collections.Generic; using System.ComponentModel.Design; +using System.Diagnostics; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Task = System.Threading.Tasks.Task; @@ -21,6 +23,7 @@ public class SolutionExplorer : ISolutionExplorer { private static readonly CommandID HideUnloadedProjectsCommand = new(VSConstants.CMDSETID.StandardCommandSet15_guid, 1654); private static readonly CommandID ShowUnloadedProjectsCommand = new(VSConstants.CMDSETID.StandardCommandSet15_guid, 1653); private static readonly CommandID UnhideFoldersCommand = KnownCommands.Project_UnhideFolders; + private static readonly CommandID HideSolutionFolderCommand = new(VSConstants.CMDSETID.StandardCommandSet2K_guid, 1608); public async Task IsEmptyAsync() { @@ -79,8 +82,9 @@ public async Task HideUnloadedProjectsAsync() { selection = await solutionExplorer.GetSelectionAsync(); solutionExplorer.SetSelection(solution); - // The commands that we need to execute only work when Solution Explorer - // has the focus. Remember the current window, then focus Solution Explorer. + // The commands that we need to execute may only work when + // Solution Explorer has the focus. Play it safe and remember + // the current window, then focus Solution Explorer. window = await VS.Windows.GetCurrentWindowAsync(); solutionExplorer.Frame.Show(); @@ -118,6 +122,127 @@ public async Task HideUnloadedProjectsAsync() { } + public async Task HideSolutionFoldersWithoutLoadedProjectsAsync() { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + + SolutionExplorerWindow? solutionExplorer; + + + solutionExplorer = await VS.Windows.GetSolutionExplorerWindowAsync(); + + if (solutionExplorer is not null) { + IVsSolution solution; + IHierarchyProvider provider; + IEnumerable nodes; + IEnumerable selection; + WindowFrame? window; + bool focused; + + + solution = await VS.Services.GetSolutionAsync(); + provider = await VS.GetRequiredServiceAsync(); + nodes = await provider.GetHierarchyAsync(); + + window = null; + focused = false; + + // We need to execute some commands that operate on the solution folders, + // which means we may need to change the selected item solution explorer. + // Remember the current selection so that we can restore it. + selection = await solutionExplorer.GetSelectionAsync(); + + // We only want to hide folders that contain projects where all of the projects are + // unloaded. We don't want to hide folders that only contain miscellaneous files like + // the "Solution Items" folder. The nodes we get from the `IHierarchyProvider` only + // include nodes that contain projects either within them directly or somewhere within + // a descendant folder. That means if we encounter a folder that doesn't have loaded + // projects within it, then we know it must contain only unloaded projects. + foreach (IHierarchyNode node in GetSolutionFoldersWithoutLoadedProjects(nodes)) { + if (ErrorHandler.Succeeded(solution.GetProjectOfGuid(node.Identifier, out IVsHierarchy hierarchy))) { + SolutionItem? folder; + + + folder = await SolutionItem.FromHierarchyAsync(hierarchy, VSConstants.VSITEMID_ROOT); + + if (folder is not null) { + folder.GetItemInfo(out IVsHierarchy vsHierarchy, out uint itemID, out var _); + + if (vsHierarchy is IVsUIHierarchy uiHierarchy) { + // The commands that we need to execute may only work when + // Solution Explorer has the focus. Play it safe and remember + // the current window, then focus Solution Explorer. + if (!focused) { + window = await VS.Windows.GetCurrentWindowAsync(); + solutionExplorer.Frame.Show(); + focused = true; + } + + solutionExplorer.SetSelection(folder); + ExecuteCommand(uiHierarchy, itemID, HideSolutionFolderCommand); + } + } + } + } + + // Restore the original selection. + solutionExplorer.SetSelection(selection); + + // Restore the previously-active window. + if (window is not null) { + await window.ShowAsync(); + } + } + } + + + public static List GetSolutionFoldersWithoutLoadedProjects(IEnumerable nodes) { + List folders; + + + folders = []; + + foreach (IHierarchyNode child in nodes) { + if (child.IsFolder) { + Visit(child, folders, out _); + } + } + + return folders; + + static void Visit( + IHierarchyNode folder, + List foldersWithoutLoadedProjects, + out bool hasLoadedProjectsWithin + ) { + bool hasLoadedProjectsDirectly; + bool hasLoadedProjectsInDescendants; + + + hasLoadedProjectsDirectly = false; + hasLoadedProjectsInDescendants = false; + + foreach (IHierarchyNode child in folder.Children) { + if (child.IsFolder) { + Visit(child, foldersWithoutLoadedProjects, out bool hasLoadedProjectsWithinFolder); + + if (hasLoadedProjectsWithinFolder) { + hasLoadedProjectsInDescendants = true; + } + + } else if (child.IsLoaded) { + hasLoadedProjectsDirectly = true; + } + } + + hasLoadedProjectsWithin = hasLoadedProjectsDirectly || hasLoadedProjectsInDescendants; + + if (!hasLoadedProjectsWithin) { + foldersWithoutLoadedProjects.Add(folder); + } + } + } + + private static void ExecuteCommand(IVsUIHierarchy hierarchy, uint itemID, CommandID command) { ThreadHelper.ThrowIfNotOnUIThread(); @@ -160,7 +285,7 @@ private static async Task ExpandOrCollapseAsync(IEnumerable projects, Acti solution = (IVsSolution4)await VS.Services.GetSolutionAsync(); - items = new List(); + items = []; foreach (var project in projects) { if (solution.TryGetHierarchy(project, out IVsHierarchy hierarchy)) { @@ -190,7 +315,7 @@ public async Task> GetExpandedFoldersAsync() { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - expanded = new HashSet(); + expanded = []; solutionExplorer = await VS.Windows.GetSolutionExplorerWindowAsync(); solution = await VS.Services.GetSolutionAsync() as IVsSolution4; @@ -238,4 +363,49 @@ out uint state return false; } + + [DebuggerDisplay("{SolutionItem.Name,nq}")] + private class Node : ISolutionExplorerNode { + + private List? _children; + + + public Node(SolutionItem solutionItem) { + SolutionItem = solutionItem; + } + + + public SolutionItem SolutionItem { get; } + + + public bool IsFolder { + get { + return SolutionItem.Type == SolutionItemType.SolutionFolder; + } + } + + + public bool IsProject { + get { + return SolutionItem.Type == SolutionItemType.Project; + } + } + + + public bool IsLoaded { + get { + return SolutionItem is Project project && project.IsLoaded; + } + } + + + public IReadOnlyList Children { + get { + // TODO: The children do not include unloaded projects. + return _children ??= [.. SolutionItem.Children.Where((x) => x is not null).Select(x => new Node(x!))]; + } + } + + } + } diff --git a/source/ProjectFilter/Services/SolutionNodeSettings.cs b/source/ProjectFilter/Services/SolutionNodeSettings.cs index 67be3ee..7cf5d59 100644 --- a/source/ProjectFilter/Services/SolutionNodeSettings.cs +++ b/source/ProjectFilter/Services/SolutionNodeSettings.cs @@ -7,7 +7,7 @@ namespace ProjectFilter.Services; public class SolutionNodeSettings { public SolutionNodeSettings() { - Children = new Dictionary(); + Children = []; } diff --git a/source/ProjectFilter/Services/SolutionSettings.cs b/source/ProjectFilter/Services/SolutionSettings.cs index b0c6104..be44f3c 100644 --- a/source/ProjectFilter/Services/SolutionSettings.cs +++ b/source/ProjectFilter/Services/SolutionSettings.cs @@ -10,7 +10,8 @@ public SolutionSettings() { LoadProjectDependencies = true; UseRegularExpressions = false; ExpandLoadedProjects = true; - Nodes = new Dictionary(); + HideSolutionFoldersWithoutLoadedProjects = null; + Nodes = []; } @@ -23,6 +24,9 @@ public SolutionSettings() { public bool ExpandLoadedProjects { get; set; } + public bool? HideSolutionFoldersWithoutLoadedProjects { get; set; } + + public Dictionary Nodes { get; } } diff --git a/source/ProjectFilter/UI/FilterDialog.xaml b/source/ProjectFilter/UI/FilterDialog.xaml index 0124f8b..23ef096 100644 --- a/source/ProjectFilter/UI/FilterDialog.xaml +++ b/source/ProjectFilter/UI/FilterDialog.xaml @@ -48,6 +48,8 @@ + + @@ -100,6 +102,17 @@ /> + + + + +