Эх сурвалжийг харах

FilteredSolutionsExtension: minor fixes (not buildable)

Dalibor Votruba 1 жил өмнө
parent
commit
2496f0e4d1

+ 74 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/DebugHelper.cs

@@ -0,0 +1,74 @@
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.Shell.Interop;
+using System;
+using System.IO;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    public static class DebugHelper
+    {
+        private static string _logPath = Path.Combine(Path.GetTempPath(), "FilteredSolutionsExtension.log");
+
+        public static void Log(string message)
+        {
+            try
+            {
+                var timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
+                var logEntry = $"[{timestamp}] {message}\n";
+                File.AppendAllText(_logPath, logEntry);
+            }
+            catch
+            {
+                // Ignore logging errors
+            }
+        }
+
+        public static void ShowMessage(string title, string message)
+        {
+            try
+            {
+                ThreadHelper.ThrowIfNotOnUIThread();
+                
+                var serviceProvider = ServiceProvider.GlobalProvider;
+                var uiShell = serviceProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
+                var clsid = Guid.Empty;
+                int result;
+                
+                uiShell?.ShowMessageBox(
+                    0,
+                    ref clsid,
+                    title,
+                    message,
+                    string.Empty,
+                    0,
+                    OLEMSGBUTTON.OLEMSGBUTTON_OK,
+                    OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
+                    OLEMSGICON.OLEMSGICON_INFO,
+                    0,
+                    out result);
+            }
+            catch
+            {
+                // Fallback to system message box
+                System.Windows.MessageBox.Show(message, title);
+            }
+        }
+
+        public static void ClearLog()
+        {
+            try
+            {
+                if (File.Exists(_logPath))
+                {
+                    File.Delete(_logPath);
+                }
+            }
+            catch
+            {
+                // Ignore
+            }
+        }
+
+        public static string GetLogPath() => _logPath;
+    }
+}

+ 5 - 1
FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtension.csproj

@@ -45,17 +45,20 @@
     <WarningLevel>4</WarningLevel>
   </PropertyGroup>
   <ItemGroup>
+    <Compile Include="DebugHelper.cs" />
     <Compile Include="FileUtils.cs" />
     <Compile Include="FilteredSolutionGenerator.cs" />
+    <Compile Include="ManualFilterCommand.cs" />
     <Compile Include="ProjectFilterDialog.xaml.cs">
       <DependentUpon>ProjectFilterDialog.xaml</DependentUpon>
     </Compile>
     <Compile Include="ProjectInfo.cs" />
+    <Compile Include="ProjectNode.cs" />
     <Compile Include="ProjectTreeItem.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="FilteredSolutionsExtensionPackage.cs" />
     <Compile Include="SolutionEventsHandler.cs" />
-    <Compile Include="SolutionOpeningInterceptor.cs" />
+    <None Include="SolutionOpeningInterceptor.cs" />
     <Compile Include="SolutionParser.cs" />
   </ItemGroup>
   <ItemGroup>
@@ -68,6 +71,7 @@
     <Reference Include="PresentationCore" />
     <Reference Include="PresentationFramework" />
     <Reference Include="System" />
+    <Reference Include="System.Design" />
     <Reference Include="System.Xaml" />
   </ItemGroup>
   <ItemGroup>

+ 114 - 8
FilteredSolutionsExtension/FilteredSolutionsExtension/FilteredSolutionsExtensionPackage.cs

@@ -2,34 +2,140 @@
 using Microsoft.VisualStudio.Shell.Interop;
 using Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension;
 using System;
+using System.ComponentModel.Design;
 using System.Runtime.InteropServices;
 using System.Threading;
 using Task = System.Threading.Tasks.Task;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
-{ [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
+{
+    [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)]
     [Guid(FilteredSolutionsExtensionPackage.PackageGuidString)]
-    [ProvideMenuResource("Menus.ctmenu", 1)]
     [ProvideAutoLoad(UIContextGuids80.NoSolution, PackageAutoLoadFlags.BackgroundLoad)]
-    [ProvideAutoLoad(UIContextGuids80.SolutionExists, PackageAutoLoadFlags.BackgroundLoad)]
+    [ProvideAutoLoad(UIContextGuids80.EmptySolution, PackageAutoLoadFlags.BackgroundLoad)]
+    [ProvideMenuResource("Menus.ctmenu", 1)]
     public sealed class FilteredSolutionsExtensionPackage : AsyncPackage
     {
         public const string PackageGuidString = "12345678-1234-1234-1234-123456789abc";
-        private SolutionOpeningInterceptor _solutionInterceptor;
+        public const int FilterSolutionCommandId = 0x0100;
+        public static readonly Guid CommandSet = new Guid("12345678-1234-1234-1234-123456789abd");
+
+        private SolutionEventsHandler _solutionEventHandler;
+        private uint _solutionEventsCookie;
 
         protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress<ServiceProgressData> progress)
         {
             await this.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
-            _solutionInterceptor = new SolutionOpeningInterceptor(this);
-            await _solutionInterceptor.InitializeAsync();
+            // Initialize command
+            var commandService = await GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
+            if (commandService != null)
+            {
+                var menuCommandID = new CommandID(CommandSet, FilterSolutionCommandId);
+                var menuItem = new MenuCommand(ExecuteFilterCommand, menuCommandID);
+                commandService.AddCommand(menuItem);
+            }
+
+            // Initialize solution events
+            var solutionService = await GetServiceAsync(typeof(SVsSolution)) as IVsSolution;
+            if (solutionService != null)
+            {
+                _solutionEventHandler = new SolutionEventsHandler(this);
+                solutionService.AdviseSolutionEvents(_solutionEventHandler, out _solutionEventsCookie);
+            }
+        }
+
+        private void ExecuteFilterCommand(object sender, EventArgs e)
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+
+            // This can be used for manual filtering if needed
+            var dte = GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
+            if (dte?.Solution?.FullName != null)
+            {
+                ShowFilterDialog(dte.Solution.FullName);
+            }
+        }
+
+        public void ShowFilterDialog(string solutionPath)
+        {
+            try
+            {
+                var solutionInfo = new SolutionInfo(solutionPath);
+                var dialog = new ProjectFilterDialog(solutionInfo);
+                var result = dialog.ShowDialog();
+
+                if (result == true && !string.IsNullOrEmpty(dialog.FilteredSolutionPath))
+                {
+                    // Open the filtered solution
+                    var dte = GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
+                    dte?.Solution?.Close();
+                    dte?.Solution?.Open(dialog.FilteredSolutionPath);
+                }
+            }
+            catch (Exception ex)
+            {
+                var uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell;
+                var clsid = Guid.Empty;
+                int result;
+                uiShell?.ShowMessageBox(
+                    0,
+                    ref clsid,
+                    "Solution Filter Extension",
+                    $"Error: {ex.Message}",
+                    string.Empty,
+                    0,
+                    OLEMSGBUTTON.OLEMSGBUTTON_OK,
+                    OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
+                    OLEMSGICON.OLEMSGICON_CRITICAL,
+                    0,
+                    out result);
+            }
+        }
+
+        public void ShowFilterDialog(SolutionInfo solutionInfo)
+        {
+            try
+            {
+                var dialog = new ProjectFilterDialog(solutionInfo);
+                var result = dialog.ShowDialog();
+
+                if (result == true && !string.IsNullOrEmpty(dialog.FilteredSolutionPath))
+                {
+                    // Open the filtered solution
+                    var dte = GetService(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
+                    dte?.Solution?.Close();
+                    dte?.Solution?.Open(dialog.FilteredSolutionPath);
+                }
+            }
+            catch (Exception ex)
+            {
+                var uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell;
+                var clsid = Guid.Empty;
+                int result;
+                uiShell?.ShowMessageBox(
+                    0,
+                    ref clsid,
+                    "Solution Filter Extension",
+                    $"Error: {ex.Message}",
+                    string.Empty,
+                    0,
+                    OLEMSGBUTTON.OLEMSGBUTTON_OK,
+                    OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST,
+                    OLEMSGICON.OLEMSGICON_CRITICAL,
+                    0,
+                    out result);
+            }
         }
 
         protected override void Dispose(bool disposing)
         {
-            if (disposing)
+            if (disposing && _solutionEventsCookie != 0)
             {
-                _solutionInterceptor?.Dispose();
+                ThreadHelper.ThrowIfNotOnUIThread();
+                var solutionService = GetService(typeof(SVsSolution)) as IVsSolution;
+                solutionService?.UnadviseSolutionEvents(_solutionEventsCookie);
+                _solutionEventsCookie = 0;
             }
             base.Dispose(disposing);
         }

+ 93 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/ManualFilterCommand.cs

@@ -0,0 +1,93 @@
+using Microsoft.VisualStudio.Shell;
+using System;
+using System.ComponentModel.Design;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+    internal sealed class ManualFilterCommand
+    {
+        public const int CommandId = 0x0101;
+        public static readonly Guid CommandSet = new Guid("12345678-1234-1234-1234-123456789abd");
+
+        private readonly AsyncPackage _package;
+
+        private ManualFilterCommand(AsyncPackage package, OleMenuCommandService commandService)
+        {
+            _package = package ?? throw new ArgumentNullException(nameof(package));
+            commandService = commandService ?? throw new ArgumentNullException(nameof(commandService));
+
+            var menuCommandID = new CommandID(CommandSet, CommandId);
+            var menuItem = new OleMenuCommand(Execute, menuCommandID);
+            menuItem.BeforeQueryStatus += OnBeforeQueryStatus;
+            commandService.AddCommand(menuItem);
+        }
+
+        public static ManualFilterCommand Instance { get; private set; }
+
+        private Microsoft.VisualStudio.Shell.IAsyncServiceProvider ServiceProvider => _package;
+
+        public static async System.Threading.Tasks.Task InitializeAsync(AsyncPackage package)
+        {
+            await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(package.DisposalToken);
+
+            var commandService = await package.GetServiceAsync(typeof(IMenuCommandService)) as OleMenuCommandService;
+            Instance = new ManualFilterCommand(package, commandService);
+        }
+
+        private void OnBeforeQueryStatus(object sender, EventArgs e)
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+            
+            var command = sender as OleMenuCommand;
+            if (command != null)
+            {
+                 var dte = (EnvDTE.DTE) _package.GetService(typeof(EnvDTE.DTE));
+                command.Visible = command.Enabled = 
+                    dte?.Solution != null && 
+                    !string.IsNullOrEmpty(dte.Solution.FullName) &&
+                    dte.Solution.Projects?.Count > 1;
+            }
+        }
+
+        private void Execute(object sender, EventArgs e)
+        {
+            ThreadHelper.ThrowIfNotOnUIThread();
+
+            try
+            {
+                DebugHelper.Log("Manual filter command executed");
+                
+                 var dte = (EnvDTE.DTE) _package.GetService(typeof(EnvDTE.DTE));
+                var solutionInfo = SolutionInfo.FromCurrentSolution(dte);
+                
+                if (solutionInfo != null)
+                {
+                    DebugHelper.Log($"Opening filter dialog for: {solutionInfo.FullPath}");
+                    
+                    var dialog = new ProjectFilterDialog(solutionInfo);
+                    var result = dialog.ShowDialog();
+
+                    if (result == true && !string.IsNullOrEmpty(dialog.FilteredSolutionPath))
+                    {
+                        DebugHelper.Log($"Opening filtered solution: {dialog.FilteredSolutionPath}");
+                        dte.Solution.Close();
+                        dte.Solution.Open(dialog.FilteredSolutionPath);
+                    }
+                    else
+                    {
+                        DebugHelper.Log("Filter dialog cancelled or no filtered solution created");
+                    }
+                }
+                else
+                {
+                    DebugHelper.ShowMessage("Solution Filter", "No solution is currently open.");
+                }
+            }
+            catch (Exception ex)
+            {
+                DebugHelper.Log($"Error in manual filter command: {ex}");
+                DebugHelper.ShowMessage("Error", $"Error filtering solution: {ex.Message}");
+            }
+        }
+    }
+}

+ 107 - 117
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectFilterDialog.xaml.cs

@@ -1,183 +1,173 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.ComponentModel;
 using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
-using System.ComponentModel;
-using System.Collections.ObjectModel;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
-    public partial class ProjectFilterDialog : Window
+    public partial class ProjectFilterDialog : Window, INotifyPropertyChanged
     {
         private readonly SolutionInfo _solutionInfo;
-        private readonly ObservableCollection<ProjectTreeItem> _projectItems;
-        private readonly ObservableCollection<ProjectTreeItem> _filteredItems;
+        private readonly SolutionParser _solutionParser;
+        private string _filterPattern = "";
         
-        public List<ProjectInfo> SelectedProjects { get; private set; } = new List<ProjectInfo>();
+        public string FilteredSolutionPath { get; private set; }
+        public ObservableCollection<ProjectNode> Projects { get; set; }
+
+        public string FilterPattern
+        {
+            get => _filterPattern;
+            set
+            {
+                _filterPattern = value;
+                OnPropertyChanged();
+            }
+        }
 
         public ProjectFilterDialog(SolutionInfo solutionInfo)
         {
             InitializeComponent();
-            _solutionInfo = solutionInfo;
-            _projectItems = new ObservableCollection<ProjectTreeItem>(CreateProjectTreeItems());
-            _filteredItems = new ObservableCollection<ProjectTreeItem>(_projectItems);
+            DataContext = this;
             
-            ProjectTreeView.ItemsSource = _filteredItems;
-            Title = $"Select Projects - {System.IO.Path.GetFileNameWithoutExtension(_solutionInfo.SolutionPath)}";
+            _solutionInfo = solutionInfo ?? throw new ArgumentNullException(nameof(solutionInfo));
+            _solutionParser = new SolutionParser();
+            Projects = new ObservableCollection<ProjectNode>();
             
-            // Subscribe to property changes for dependency handling
-            foreach (var item in _projectItems)
-            {
-                item.PropertyChanged += ProjectItem_PropertyChanged;
-            }
+            Title = $"Filter Projects - {_solutionInfo.Name}";
+            
+            // Load projects asynchronously
+            Loaded += async (s, e) => await LoadProjectsAsync();
         }
 
-        private void ProjectItem_PropertyChanged(object sender, PropertyChangedEventArgs e)
+        private async Task LoadProjectsAsync()
         {
-            if (e.PropertyName == nameof(ProjectTreeItem.IsChecked))
+            try
             {
-                var item = sender as ProjectTreeItem;
-                if (item != null)
+                // Show loading indicator
+                ProjectTreeView.Visibility = Visibility.Hidden;
+                var loadingLabel = new System.Windows.Controls.Label
                 {
-                    if (item.IsChecked)
-                    {
-                        CheckDependencies(item.ProjectInfo);
-                    }
-                    else
+                    Content = "Loading projects...",
+                    HorizontalAlignment = HorizontalAlignment.Center,
+                    VerticalAlignment = VerticalAlignment.Center,
+                    FontSize = 14
+                };
+                
+                var grid = (Grid)Content;
+                Grid.SetRow(loadingLabel, 2);
+                grid.Children.Add(loadingLabel);
+
+                var projects = await _solutionParser.ParseSolutionAsync(_solutionInfo.FullPath);
+                
+                Application.Current.Dispatcher.Invoke(() =>
+                {
+                    Projects.Clear();
+                    foreach (var project in projects)
                     {
-                        UncheckDependents(item.ProjectInfo);
+                        Projects.Add(project);
                     }
-                }
+                    
+                    // Build dependency tree
+                    _solutionParser.BuildDependencyTree(Projects.ToList());
+                    
+                    // Remove loading indicator and show tree
+                    grid.Children.Remove(loadingLabel);
+                    ProjectsTreeView.Visibility = Visibility.Visible;
+                });
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Error loading solution: {ex.Message}\n\nPlease try opening the solution normally and use the File menu to filter projects.", 
+                    "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+                DialogResult = false;
             }
         }
 
-        private List<ProjectTreeItem> CreateProjectTreeItems()
+        private void SelectAll_Click(object sender, RoutedEventArgs e)
         {
-            return _solutionInfo.Projects.Select(p => new ProjectTreeItem(p)).ToList();
+            foreach (var project in Projects)
+            {
+                project.IsSelected = true;
+            }
         }
 
-        private void SelectAllButton_Click(object sender, RoutedEventArgs e)
+        private void ClearAll_Click(object sender, RoutedEventArgs e)
         {
-            foreach (var item in _projectItems)
+            foreach (var project in Projects)
             {
-                item.IsChecked = true;
+                project.IsSelected = false;
             }
         }
 
-        private void ClearAllButton_Click(object sender, RoutedEventArgs e)
+        private void Filter_Click(object sender, RoutedEventArgs e)
         {
-            foreach (var item in _projectItems)
-            {
-                item.IsChecked = false;
-            }
+            ApplyFilter();
         }
 
-        private void FilterButton_Click(object sender, RoutedEventArgs e)
+        private void ApplyFilter()
         {
-            var pattern = FilterTextBox.Text.Trim();
-            _filteredItems.Clear();
-            
-            if (string.IsNullOrEmpty(pattern))
-            {
-                foreach (var item in _projectItems)
-                {
-                    _filteredItems.Add(item);
-                }
-            }
-            else
+            if (string.IsNullOrWhiteSpace(FilterPattern))
             {
-                var matchingItems = _projectItems.Where(item => 
-                    MatchesPattern(item.ProjectInfo.DisplayName, pattern));
-                
-                foreach (var item in matchingItems)
+                foreach (var project in Projects)
                 {
-                    _filteredItems.Add(item);
+                    project.IsVisible = true;
                 }
+                return;
             }
-        }
 
-        private bool MatchesPattern(string text, string pattern)
-        {
-            if (string.IsNullOrEmpty(pattern)) return true;
-            
-            // Simple wildcard matching
-            pattern = pattern.Replace("*", ".*").Replace("?", ".");
-            return System.Text.RegularExpressions.Regex.IsMatch(text, pattern, 
+            var pattern = FilterPattern.Replace("*", ".*");
+            var regex = new System.Text.RegularExpressions.Regex(pattern, 
                 System.Text.RegularExpressions.RegexOptions.IgnoreCase);
-        }
 
-        private void OkButton_Click(object sender, RoutedEventArgs e)
-        {
-            var checkedItems = _projectItems.Where(item => item.IsChecked).ToList();
-            SelectedProjects = checkedItems.Select(item => item.ProjectInfo).ToList();
-            
-            // Ensure all dependencies are included (should already be checked due to auto-checking)
-            var finalProjects = new HashSet<ProjectInfo>(SelectedProjects);
-            foreach (var project in SelectedProjects.ToList())
+            foreach (var project in Projects)
             {
-                AddDependencies(project, finalProjects);
+                project.IsVisible = regex.IsMatch(project.Name);
             }
-            
-            SelectedProjects = finalProjects.ToList();
-            DialogResult = true;
-            Close();
         }
 
-        private void AddDependencies(ProjectInfo project, HashSet<ProjectInfo> finalProjects)
+        private async void Ok_Click(object sender, RoutedEventArgs e)
         {
-            foreach (var depGuid in project.Dependencies)
+            try
             {
-                var depProject = _solutionInfo.Projects.FirstOrDefault(p => 
-                    p.ProjectGuid.Equals(depGuid, StringComparison.OrdinalIgnoreCase));
-                
-                if (depProject != null && !finalProjects.Contains(depProject))
+                var selectedProjects = Projects.Where(p => p.IsSelected).ToList();
+                if (!selectedProjects.Any())
                 {
-                    finalProjects.Add(depProject);
-                    AddDependencies(depProject, finalProjects);
+                    MessageBox.Show("Please select at least one project.", "Warning", 
+                        MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
                 }
+
+                // Include dependencies
+                var projectsWithDependencies = _solutionParser.GetProjectsWithDependencies(selectedProjects);
+                
+                // Create filtered solution
+                FilteredSolutionPath = await _solutionParser.CreateFilteredSolutionAsync(
+                    _solutionInfo.FullPath, projectsWithDependencies);
+                
+                DialogResult = true;
+            }
+            catch (Exception ex)
+            {
+                MessageBox.Show($"Error creating filtered solution: {ex.Message}", "Error", 
+                    MessageBoxButton.OK, MessageBoxImage.Error);
             }
         }
 
-        private void CancelButton_Click(object sender, RoutedEventArgs e)
+        private void Cancel_Click(object sender, RoutedEventArgs e)
         {
             DialogResult = false;
-            Close();
         }
 
-        private void CheckDependencies(ProjectInfo project)
-        {
-            foreach (var depGuid in project.Dependencies)
-            {
-                var depItem = _projectItems.FirstOrDefault(item => 
-                    item.ProjectInfo.ProjectGuid.Equals(depGuid, StringComparison.OrdinalIgnoreCase));
-                
-                if (depItem != null && !depItem.IsChecked)
-                {
-                    depItem.PropertyChanged -= ProjectItem_PropertyChanged; // Temporarily unsubscribe
-                    depItem.IsChecked = true;
-                    depItem.PropertyChanged += ProjectItem_PropertyChanged; // Resubscribe
-                    CheckDependencies(depItem.ProjectInfo);
-                }
-            }
-        }
+        public event PropertyChangedEventHandler PropertyChanged;
 
-        private void UncheckDependents(ProjectInfo project)
+        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
         {
-            foreach (var item in _projectItems)
-            {
-                if (item.ProjectInfo.Dependencies.Contains(project.ProjectGuid) && item.IsChecked)
-                {
-                    item.PropertyChanged -= ProjectItem_PropertyChanged; // Temporarily unsubscribe
-                    item.IsChecked = false;
-                    item.PropertyChanged += ProjectItem_PropertyChanged; // Resubscribe
-                    UncheckDependents(item.ProjectInfo);
-                }
-            }
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
-
-        // Remove the old event handlers as we're using PropertyChanged now
-        private void ProjectTreeItem_Checked(object sender, RoutedEventArgs e) { }
-        private void ProjectTreeItem_Unchecked(object sender, RoutedEventArgs e) { }
     }
 }

+ 78 - 7
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectInfo.cs

@@ -1,23 +1,94 @@
+using System;
 using System.Collections.Generic;
+using System.IO;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
     public class ProjectInfo
     {
         public string Name { get; set; }
-        public string Path { get; set; }
+        public string FullPath { get; set; }
+        public string RelativePath { get; set; }
         public string ProjectGuid { get; set; }
         public string TypeGuid { get; set; }
-        public List<string> Dependencies { get; set; } = new List<string>();
-        public bool IsSelected { get; set; } = true;
+        public List<string> Dependencies { get; set; }
 
-        public string DisplayName => System.IO.Path.GetFileNameWithoutExtension(Name);
+        public ProjectInfo()
+        {
+            Dependencies = new List<string>();
+        }
     }
 
     public class SolutionInfo
     {
-        public string SolutionPath { get; set; }
-        public List<ProjectInfo> Projects { get; set; } = new List<ProjectInfo>();
-        public Dictionary<string, List<string>> ProjectDependencies { get; set; } = new Dictionary<string, List<string>>();
+        public string FullPath { get; set; }
+        public string Name { get; set; }
+        public string Directory { get; set; }
+        public List<ProjectInfo> Projects { get; set; }
+
+        public SolutionInfo()
+        {
+            Projects = new List<ProjectInfo>();
+        }
+
+        public SolutionInfo(string solutionPath) : this()
+        {
+            FullPath = solutionPath;
+            Name = Path.GetFileNameWithoutExtension(solutionPath);
+            Directory = Path.GetDirectoryName(solutionPath);
+        }
+
+        // Factory method to create SolutionInfo from current VS solution
+        public static SolutionInfo FromCurrentSolution(EnvDTE.DTE dte)
+        {
+            if (dte?.Solution == null || string.IsNullOrEmpty(dte.Solution.FullName))
+                return null;
+
+            var solutionInfo = new SolutionInfo(dte.Solution.FullName);
+
+            // Populate project information from DTE
+            if (dte.Solution.Projects != null)
+            {
+                foreach (EnvDTE.Project project in dte.Solution.Projects)
+                {
+                    try
+                    {
+                        if (!string.IsNullOrEmpty(project.FullName) &&
+                            !string.Equals(project.Kind, EnvDTE.Constants.vsProjectKindSolutionItems, StringComparison.OrdinalIgnoreCase))
+                        {
+                            var projectInfo = new ProjectInfo
+                            {
+                                Name = project.Name,
+                                FullPath = project.FullName,
+                                RelativePath = GetRelativePath(solutionInfo.Directory, project.FullName),
+                                ProjectGuid = project.Kind // This might need adjustment based on actual GUID location
+                            };
+                            solutionInfo.Projects.Add(projectInfo);
+                        }
+                    }
+                    catch
+                    {
+                        // Skip projects that can't be accessed
+                    }
+                }
+            }
+
+            return solutionInfo;
+        }
+
+        private static string GetRelativePath(string fromPath, string toPath)
+        {
+            try
+            {
+                var fromUri = new Uri(fromPath + Path.DirectorySeparatorChar);
+                var toUri = new Uri(toPath);
+                var relativeUri = fromUri.MakeRelativeUri(toUri);
+                return Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar);
+            }
+            catch
+            {
+                return toPath; // Fallback to absolute path
+            }
+        }
     }
 }

+ 76 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectNode.cs

@@ -0,0 +1,76 @@
+using System.Collections.ObjectModel;
+using System.ComponentModel;
+using System.Runtime.CompilerServices;
+
+namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
+{
+   public class ProjectNode : INotifyPropertyChanged
+    {
+        private bool _isSelected = true;
+        private bool _isVisible = true;
+
+        public string Name { get; set; }
+        public string Path { get; set; }
+        public string ProjectGuid { get; set; }
+        public string TypeGuid { get; set; }
+        public ObservableCollection<ProjectNode> Dependencies { get; set; }
+        public ObservableCollection<ProjectNode> Dependents { get; set; }
+
+        public bool IsSelected
+        {
+            get => _isSelected;
+            set
+            {
+                if (_isSelected != value)
+                {
+                    _isSelected = value;
+                    OnPropertyChanged();
+                    UpdateDependencies(value);
+                }
+            }
+        }
+
+        public bool IsVisible
+        {
+            get => _isVisible;
+            set
+            {
+                _isVisible = value;
+                OnPropertyChanged();
+            }
+        }
+
+        public ProjectNode()
+        {
+            Dependencies = new ObservableCollection<ProjectNode>();
+            Dependents = new ObservableCollection<ProjectNode>();
+        }
+
+        private void UpdateDependencies(bool isSelected)
+        {
+            if (isSelected)
+            {
+                // When selecting, select all dependencies
+                foreach (var dependency in Dependencies)
+                {
+                    dependency.IsSelected = true;
+                }
+            }
+            else
+            {
+                // When deselecting, deselect all dependents
+                foreach (var dependent in Dependents)
+                {
+                    dependent.IsSelected = false;
+                }
+            }
+        }
+
+        public event PropertyChangedEventHandler PropertyChanged;
+
+        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
+        {
+            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
+        }
+    }
+}

+ 1 - 1
FilteredSolutionsExtension/FilteredSolutionsExtension/ProjectTreeItem.cs

@@ -20,7 +20,7 @@ namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
             }
         }
 
-        public string DisplayName => ProjectInfo.DisplayName;
+        public string DisplayName => ProjectInfo.Name;
 
         public ProjectTreeItem(ProjectInfo projectInfo)
         {

+ 131 - 63
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionEventsHandler.cs

@@ -3,112 +3,180 @@ using Microsoft.VisualStudio.Shell;
 using Microsoft.VisualStudio.Shell.Interop;
 using System;
 using System.IO;
+using System.Linq;
 using System.Threading.Tasks;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
-    public class SolutionEventsHandler : IVsSolutionEvents, IDisposable
+    public class SolutionEventsHandler : IVsSolutionEvents3, IVsSolutionLoadEvents
     {
-        private readonly AsyncPackage _package;
-        private IVsSolution _solution;
-        private uint _solutionEventsCookie;
+        private readonly FilteredSolutionsExtensionPackage _package;
+        private bool _isProcessingSolution = false;
 
-        public SolutionEventsHandler(AsyncPackage package)
+        public SolutionEventsHandler(FilteredSolutionsExtensionPackage package)
         {
             _package = package;
         }
 
-        public async Task InitializeAsync()
+        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
         {
-            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
+            ThreadHelper.ThrowIfNotOnUIThread();
+
+            if (_isProcessingSolution) return VSConstants.S_OK;
 
-            _solution = await _package.GetServiceAsync(typeof(SVsSolution)) as IVsSolution;
-            if (_solution != null)
+            try
             {
-                _solution.AdviseSolutionEvents(this, out _solutionEventsCookie);
+                 var dte = (EnvDTE.DTE) _package.GetService(typeof(EnvDTE.DTE));
+                if (dte?.Solution != null && !string.IsNullOrEmpty(dte.Solution.FullName))
+                {
+                    var solutionPath = dte.Solution.FullName;
+
+                    // Check if this is already a filtered solution (temp file)
+                    if (solutionPath.Contains("_filtered.sln") || Path.GetTempPath().Contains(Path.GetDirectoryName(solutionPath)))
+                    {
+                        return VSConstants.S_OK;
+                    }
+
+                    // Count projects to see if we should show the dialog
+                    var projectCount = dte.Solution.Projects?.Count ?? 0;
+                    if (projectCount > 1)
+                    {
+                        // Delay to ensure solution is fully loaded
+                        Task.Delay(500).ContinueWith(_ =>
+                        {
+                            ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
+                            {
+                                await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+                                if (!_isProcessingSolution)
+                                {
+                                    _isProcessingSolution = true;
+                                    _package.ShowFilterDialog(solutionPath);
+                                    _isProcessingSolution = false;
+                                }
+                            });
+                        });
+                    }
+                }
+            }
+            catch
+            {
+                _isProcessingSolution = false;
             }
-        }
 
-        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
-        {
-            // Solution already opened, we need to intercept before this
             return VSConstants.S_OK;
         }
 
+        // Alternative approach - try to catch before solution fully opens
         public int OnBeforeOpenSolution(string pszSolutionFilename)
         {
-            ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
-            {
-                await HandleSolutionOpeningAsync(pszSolutionFilename);
-            });
+            ThreadHelper.ThrowIfNotOnUIThread();
 
-            return VSConstants.S_OK;
-        }
+            if (_isProcessingSolution) return VSConstants.S_OK;
 
-        private async Task HandleSolutionOpeningAsync(string solutionPath)
-        {
-            try
+            if (!string.IsNullOrEmpty(pszSolutionFilename) && File.Exists(pszSolutionFilename))
             {
-                var solutionInfo = SolutionParser.ParseSolution(solutionPath);
-                var dialog = new ProjectFilterDialog(solutionInfo);
+                // Check if this is already a filtered solution
+                if (pszSolutionFilename.Contains("_filtered.sln") || Path.GetTempPath().Contains(Path.GetDirectoryName(pszSolutionFilename)))
+                {
+                    return VSConstants.S_OK;
+                }
 
-                if (dialog.ShowDialog() == true && dialog.SelectedProjects.Count > 0)
+                try
                 {
-                    var tempSolutionPath = await CreateFilteredSolutionAsync(solutionPath, dialog.SelectedProjects);
-                    if (!string.IsNullOrEmpty(tempSolutionPath))
+                    // Quick check of project count by parsing solution file
+                    var content = File.ReadAllText(pszSolutionFilename);
+                    var projectLines = content.Split('\n').Where(line => line.Trim().StartsWith("Project(")).ToList();
+
+                    if (projectLines.Count > 1)
                     {
-                        // Close current solution and open filtered one
-                        await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
-                        var dte = await _package.GetServiceAsync(typeof(EnvDTE.DTE)) as EnvDTE.DTE;
-                        dte?.Solution.Open(tempSolutionPath);
+                        _isProcessingSolution = true;
+
+                        // Show dialog immediately
+                        var solutionInfo = new SolutionInfo(pszSolutionFilename);
+                        var dialog = new ProjectFilterDialog(solutionInfo);
+                        var result = dialog.ShowDialog();
+
+                        if (result == true && !string.IsNullOrEmpty(dialog.FilteredSolutionPath))
+                        {
+                            // We need to cancel the current opening and open the filtered one instead
+                            Task.Delay(100).ContinueWith(_ =>
+                            {
+                                ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
+                                {
+                                    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
+                                     var dte = (EnvDTE.DTE) _package.GetService(typeof(EnvDTE.DTE));
+                                    dte?.Solution?.Close();
+                                    dte?.Solution?.Open(dialog.FilteredSolutionPath);
+                                    _isProcessingSolution = false;
+                                });
+                            });
+
+                            return VSConstants.S_FALSE; // Cancel original opening
+                        }
+
+                        _isProcessingSolution = false;
                     }
                 }
+                catch
+                {
+                    _isProcessingSolution = false;
+                }
             }
-            catch (Exception ex)
-            {
-                // Log error
-                System.Diagnostics.Debug.WriteLine($"Error filtering solution: {ex.Message}");
-            }
+
+            return VSConstants.S_OK;
         }
 
-        private async Task<string> CreateFilteredSolutionAsync(string originalSolutionPath, System.Collections.Generic.List<ProjectInfo> selectedProjects)
+        // IVsSolutionLoadEvents implementation
+        public int OnBeforeOpenProject(ref Guid guidProjectID, ref Guid guidProjectType, string pszFileName, IVsSolutionLoadEvents pSolutionLoadEvents)
         {
-            try
-            {
-                var tempDir = Path.Combine(Path.GetTempPath(), "VSProjectFilter", Guid.NewGuid().ToString());
-                Directory.CreateDirectory(tempDir);
+            return VSConstants.S_OK;
+        }
 
-                var tempSolutionPath = Path.Combine(tempDir, Path.GetFileName(originalSolutionPath));
+        public int OnBeforeBackgroundSolutionLoadBegins()
+        {
+            return VSConstants.S_OK;
+        }
 
-                var solutionGenerator = new FilteredSolutionGenerator();
-                await solutionGenerator.GenerateFilteredSolutionAsync(originalSolutionPath, tempSolutionPath, selectedProjects);
+        public int OnQueryBackgroundLoadProjectBatch(out bool pfShouldDelayLoadToNextIdle)
+        {
+            pfShouldDelayLoadToNextIdle = false;
+            return VSConstants.S_OK;
+        }
 
-                return tempSolutionPath;
-            }
-            catch
-            {
-                return null;
-            }
+        public int OnBeforeLoadProjectBatch(bool fIsBackgroundIdleBatch)
+        {
+            return VSConstants.S_OK;
         }
 
-        public void Dispose()
+        public int OnAfterLoadProjectBatch(bool fIsBackgroundIdleBatch)
         {
-            ThreadHelper.ThrowIfNotOnUIThread();
-            if (_solution != null && _solutionEventsCookie != 0)
-            {
-                _solution.UnadviseSolutionEvents(_solutionEventsCookie);
-            }
+            return VSConstants.S_OK;
         }
 
-        // Other IVsSolutionEvents methods (return S_OK for unused events)
-        public int OnAfterCloseSolution(object pUnkReserved) => VSConstants.S_OK;
-        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) => VSConstants.S_OK;
+        public int OnAfterBackgroundSolutionLoadComplete()
+        {
+            return VSConstants.S_OK;
+        }
+
+        // Other IVsSolutionEvents methods
         public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded) => VSConstants.S_OK;
+        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => VSConstants.S_OK;
         public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved) => VSConstants.S_OK;
-        public int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.S_OK;
+        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy) => VSConstants.S_OK;
+        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => VSConstants.S_OK;
         public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy) => VSConstants.S_OK;
-        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel) => VSConstants.S_OK;
         public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel) => VSConstants.S_OK;
-        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel) => VSConstants.S_OK;
+        public int OnBeforeCloseSolution(object pUnkReserved) => VSConstants.S_OK;
+        public int OnAfterCloseSolution(object pUnkReserved) => VSConstants.S_OK;
+
+        public int OnAfterMergeSolution(object pUnkReserved) => VSConstants.S_OK;
+
+        public int OnBeforeOpeningChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
+
+        public int OnAfterOpeningChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
+
+        public int OnBeforeClosingChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
+
+        public int OnAfterClosingChildren(IVsHierarchy pHierarchy) => VSConstants.S_OK;
     }
 }

+ 0 - 179
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionOpeningInterceptor.cs

@@ -1,179 +0,0 @@
-using Microsoft.VisualStudio.Shell;
-using Microsoft.VisualStudio.Shell.Interop;
-using System;
-using System.IO;
-using System.Threading.Tasks;
-using EnvDTE;
-using System.Collections.Generic;
-using System.Linq;
-
-namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
-{
-public class SolutionOpeningInterceptor : IVsRunningDocTableEvents, IDisposable
-{
-    private readonly AsyncPackage _package;
-    private IVsRunningDocumentTable _runningDocumentTable;
-    private uint _rdtCookie;
-    private readonly Dictionary<string, bool> _processedSolutions = new Dictionary<string, bool>();
-
-    public SolutionOpeningInterceptor(AsyncPackage package)
-    {
-        _package = package;
-    }
-
-    public async Task InitializeAsync()
-    {
-        await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
-        
-        _runningDocumentTable = await _package.GetServiceAsync(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable;
-        if (_runningDocumentTable != null)
-        {
-            _runningDocumentTable.AdviseRunningDocTableEvents(this, out _rdtCookie);
-        }
-
-        // Also monitor DTE events for solution opening
-        var dte = await _package.GetServiceAsync(typeof(DTE)) as DTE;
-        if (dte?.Events?.SolutionEvents != null)
-        {
-            dte.Events.SolutionEvents.Opened += SolutionEvents_Opened;
-            dte.Events.SolutionEvents.BeforeClosing += SolutionEvents_BeforeClosing;
-        }
-    }
-
-    private void SolutionEvents_BeforeClosing()
-    {
-        ThreadHelper.ThrowIfNotOnUIThread();
-        // Clear processed solutions when current solution is closing
-        _processedSolutions.Clear();
-    }
-
-    private void SolutionEvents_Opened()
-    {
-        ThreadHelper.JoinableTaskFactory.RunAsync(async () =>
-        {
-            await HandleSolutionOpenedAsync();
-        });
-    }
-
-    private async Task HandleSolutionOpenedAsync()
-    {
-        try
-        {
-            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
-            
-            var dte = await _package.GetServiceAsync(typeof(DTE)) as DTE;
-            if (dte?.Solution?.FullName != null)
-            {
-                var solutionPath = dte.Solution.FullName;
-                
-                // Check if we already processed this solution in this session
-                if (_processedSolutions.ContainsKey(solutionPath))
-                    return;
-
-                _processedSolutions[solutionPath] = true;
-
-                // Only show dialog if solution has multiple projects
-                if (dte.Solution.Projects.Count > 1)
-                {
-                    await ShowProjectFilterDialogAsync(solutionPath, dte);
-                }
-            }
-        }
-        catch (Exception ex)
-        {
-            System.Diagnostics.Debug.WriteLine($"Error in HandleSolutionOpenedAsync: {ex.Message}");
-        }
-    }
-
-    private async Task ShowProjectFilterDialogAsync(string solutionPath, DTE dte)
-    {
-        try
-        {
-            var solutionInfo = SolutionParser.ParseSolution(solutionPath);
-            
-            // Show dialog on UI thread
-            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
-            
-            var dialog = new ProjectFilterDialog(solutionInfo);
-            var result = dialog.ShowDialog();
-            
-            if (result == true && dialog.SelectedProjects.Count > 0 && 
-                dialog.SelectedProjects.Count < solutionInfo.Projects.Count)
-            {
-                await CreateAndOpenFilteredSolutionAsync(solutionPath, dialog.SelectedProjects, dte);
-            }
-        }
-        catch (Exception ex)
-        {
-            System.Diagnostics.Debug.WriteLine($"Error showing project filter dialog: {ex.Message}");
-        }
-    }
-
-    private async Task CreateAndOpenFilteredSolutionAsync(string originalSolutionPath, List<ProjectInfo> selectedProjects, DTE dte)
-    {
-        try
-        {
-            var tempDir = Path.Combine(Path.GetTempPath(), "VSProjectFilter", Guid.NewGuid().ToString());
-            Directory.CreateDirectory(tempDir);
-            
-            var tempSolutionPath = Path.Combine(tempDir, Path.GetFileName(originalSolutionPath));
-            
-            var solutionGenerator = new FilteredSolutionGenerator();
-            await solutionGenerator.GenerateFilteredSolutionAsync(originalSolutionPath, tempSolutionPath, selectedProjects);
-            
-            // Close current solution and open filtered one
-            await _package.JoinableTaskFactory.SwitchToMainThreadAsync();
-            
-            // Mark this solution as processed to avoid showing dialog again
-            _processedSolutions[tempSolutionPath] = true;
-            
-            dte.Solution.Close(false);
-            dte.Solution.Open(tempSolutionPath);
-        }
-        catch (Exception ex)
-        {
-            System.Diagnostics.Debug.WriteLine($"Error creating filtered solution: {ex.Message}");
-        }
-    }
-
-    public void Dispose()
-    {
-        ThreadHelper.ThrowIfNotOnUIThread();
-        if (_runningDocumentTable != null && _rdtCookie != 0)
-        {
-            _runningDocumentTable.UnadviseRunningDocTableEvents(_rdtCookie);
-        }
-    }
-
-    // IVsRunningDocTableEvents implementation
-    public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
-    {
-        return Microsoft.VisualStudio.VSConstants.S_OK;
-    }
-
-    public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
-    {
-        return Microsoft.VisualStudio.VSConstants.S_OK;
-    }
-
-    public int OnAfterSave(uint docCookie)
-    {
-        return Microsoft.VisualStudio.VSConstants.S_OK;
-    }
-
-    public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
-    {
-        return Microsoft.VisualStudio.VSConstants.S_OK;
-    }
-
-    public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
-    {
-        return Microsoft.VisualStudio.VSConstants.S_OK;
-    }
-
-    public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
-    {
-        return Microsoft.VisualStudio.VSConstants.S_OK;
-    }
-}
-}

+ 176 - 63
FilteredSolutionsExtension/FilteredSolutionsExtension/SolutionParser.cs

@@ -1,107 +1,220 @@
 using System;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using System.Text.RegularExpressions;
+using System.Threading.Tasks;
 
 namespace Quadarax.Application.Tools.Vsix.FilteredSolutionsExtension
 {
-    public static class SolutionParser
+  public class SolutionParser
     {
-        private static readonly Regex ProjectRegex = new Regex(
-            @"Project\(""{([^}]+)}""\)\s*=\s*""([^""]+)""\s*,\s*""([^""]+)""\s*,\s*""{([^}]+)}""",
-            RegexOptions.Compiled);
+        public async Task<List<ProjectNode>> ParseSolutionAsync(string solutionPath)
+        {
+            var projects = new List<ProjectNode>();
+            var content = await FileUtils.ReadAllTextAsync(solutionPath);
+            var lines = content.Split('\n');
 
-        private static readonly Regex ProjectDependencyRegex = new Regex(
-            @"{([^}]+)}\s*=\s*{([^}]+)}",
-            RegexOptions.Compiled);
+            foreach (var line in lines)
+            {
+                if (line.StartsWith("Project("))
+                {
+                    var project = ParseProjectLine(line.Trim(), Path.GetDirectoryName(solutionPath));
+                    if (project != null)
+                    {
+                        projects.Add(project);
+                    }
+                }
+            }
 
-        public static SolutionInfo ParseSolution(string solutionPath)
-        {
-            var solutionInfo = new SolutionInfo { SolutionPath = solutionPath };
-            var content = File.ReadAllText(solutionPath);
+            return projects;
+        }
 
-            // Parse projects
-            var projectMatches = ProjectRegex.Matches(content);
-            foreach (Match match in projectMatches)
+        private ProjectNode ParseProjectLine(string line, string solutionDir)
+        {
+            try
             {
-                var typeGuid = match.Groups[1].Value;
-                var name = match.Groups[2].Value;
-                var path = match.Groups[3].Value;
-                var projectGuid = match.Groups[4].Value;
+                // Parse: Project("{TypeGuid}") = "ProjectName", "ProjectPath", "{ProjectGuid}"
+                var parts = line.Split('=');
+                if (parts.Length != 2) return null;
+
+                var leftPart = parts[0].Trim();
+                var rightPart = parts[1].Trim();
+
+                // Extract type GUID
+                var typeGuidStart = leftPart.IndexOf('{');
+                var typeGuidEnd = leftPart.IndexOf('}');
+                if (typeGuidStart == -1 || typeGuidEnd == -1) return null;
+                var typeGuid = leftPart.Substring(typeGuidStart, typeGuidEnd - typeGuidStart + 1);
 
-                // Skip solution folders
-                if (typeGuid.Equals("{2150E333-8FDC-42A3-9474-1A3956D46DE8}", StringComparison.OrdinalIgnoreCase))
-                    continue;
+                // Extract project info from right part
+                var rightParts = rightPart.Split(',');
+                if (rightParts.Length != 3) return null;
 
-                var fullPath = Path.IsPathRooted(path) ? path : Path.Combine(Path.GetDirectoryName(solutionPath), path);
+                var projectName = rightParts[0].Trim().Trim('"');
+                var projectPath = rightParts[1].Trim().Trim('"');
+                var projectGuid = rightParts[2].Trim().Trim('"');
 
-                solutionInfo.Projects.Add(new ProjectInfo
+                var fullPath = Path.IsPathRooted(projectPath) 
+                    ? projectPath 
+                    : Path.Combine(solutionDir, projectPath);
+
+                return new ProjectNode
                 {
-                    Name = name,
+                    Name = projectName,
                     Path = fullPath,
                     ProjectGuid = projectGuid,
                     TypeGuid = typeGuid
-                });
+                };
+            }
+            catch
+            {
+                return null;
             }
-
-            // Parse project dependencies
-            ParseProjectDependencies(content, solutionInfo);
-
-            return solutionInfo;
         }
 
-        private static void ParseProjectDependencies(string content, SolutionInfo solutionInfo)
+        public void BuildDependencyTree(List<ProjectNode> projects)
         {
-            var lines = content.Split('\n');
-            string currentProjectGuid = null;
+            // Simple implementation - in real scenario, you'd parse project references
+            // For now, we'll just create a basic structure
+            foreach (var project in projects)
+            {
+                if (File.Exists(project.Path))
+                {
+                    // Parse project file for references
+                    // This is a simplified version
+                    ParseProjectDependencies(project, projects);
+                }
+            }
+        }
 
-            for (int i = 0; i < lines.Length; i++)
+        private async void ParseProjectDependencies(ProjectNode project, List<ProjectNode> allProjects)
+        {
+            try
             {
-                var line = lines[i].Trim();
+                if (!File.Exists(project.Path)) return;
 
-                if (line.StartsWith("ProjectSection(ProjectDependencies)"))
+                var content = await FileUtils.ReadAllTextAsync(project.Path);
+                
+                // Look for ProjectReference elements
+                var projectRefStart = content.IndexOf("<ProjectReference");
+                while (projectRefStart != -1)
                 {
-                    // Find the project GUID for this section
-                    for (int j = i - 1; j >= 0; j--)
+                    var includeStart = content.IndexOf("Include=\"", projectRefStart);
+                    if (includeStart != -1)
                     {
-                        var projectMatch = ProjectRegex.Match(lines[j]);
-                        if (projectMatch.Success)
+                        includeStart += 9; // Length of "Include=\""
+                        var includeEnd = content.IndexOf("\"", includeStart);
+                        if (includeEnd != -1)
                         {
-                            currentProjectGuid = projectMatch.Groups[4].Value;
-                            break;
+                            var referencePath = content.Substring(includeStart, includeEnd - includeStart);
+                            var fullRefPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(project.Path), referencePath));
+                            
+                            var referencedProject = allProjects.FirstOrDefault(p => 
+                                string.Equals(p.Path, fullRefPath, StringComparison.OrdinalIgnoreCase));
+                            
+                            if (referencedProject != null)
+                            {
+                                project.Dependencies.Add(referencedProject);
+                                referencedProject.Dependents.Add(project);
+                            }
                         }
                     }
-                    continue;
+                    
+                    projectRefStart = content.IndexOf("<ProjectReference", projectRefStart + 1);
                 }
+            }
+            catch
+            {
+                // Ignore parsing errors
+            }
+        }
 
-                if (line == "EndProjectSection" || line.StartsWith("EndProject"))
-                {
-                    currentProjectGuid = null;
-                    continue;
-                }
+        public List<ProjectNode> GetProjectsWithDependencies(List<ProjectNode> selectedProjects)
+        {
+            var result = new HashSet<ProjectNode>();
+            var queue = new Queue<ProjectNode>(selectedProjects);
 
-                if (currentProjectGuid != null)
+            while (queue.Count > 0)
+            {
+                var current = queue.Dequeue();
+                if (result.Add(current))
                 {
-                    var depMatch = ProjectDependencyRegex.Match(line);
-                    if (depMatch.Success)
+                    foreach (var dependency in current.Dependencies)
                     {
-                        var dependentGuid = depMatch.Groups[2].Value;
+                        queue.Enqueue(dependency);
+                    }
+                }
+            }
 
-                        if (!solutionInfo.ProjectDependencies.ContainsKey(currentProjectGuid))
-                        {
-                            solutionInfo.ProjectDependencies[currentProjectGuid] = new List<string>();
-                        }
-                        solutionInfo.ProjectDependencies[currentProjectGuid].Add(dependentGuid);
+            return result.ToList();
+        }
 
-                        // Also add to project info
-                        var project = solutionInfo.Projects.Find(p => p.ProjectGuid.Equals(currentProjectGuid, StringComparison.OrdinalIgnoreCase));
-                        if (project != null)
-                        {
-                            project.Dependencies.Add(dependentGuid);
-                        }
+        public async Task<string> CreateFilteredSolutionAsync(string originalPath, List<ProjectNode> projects)
+        {
+            var content = await FileUtils.ReadAllTextAsync(originalPath);
+            var lines = content.Split('\n').ToList();
+            
+            var filteredLines = new List<string>();
+            var projectGuids = projects.Select(p => p.ProjectGuid).ToHashSet();
+            
+            bool inProjectSection = false;
+            string currentProjectGuid = null;
+
+            foreach (var line in lines)
+            {
+                var trimmedLine = line.Trim();
+                
+                if (trimmedLine.StartsWith("Project("))
+                {
+                    inProjectSection = true;
+                    currentProjectGuid = ExtractProjectGuid(trimmedLine);
+                    
+                    if (projectGuids.Contains(currentProjectGuid))
+                    {
+                        filteredLines.Add(line);
                     }
                 }
+                else if (trimmedLine == "EndProject")
+                {
+                    if (projectGuids.Contains(currentProjectGuid))
+                    {
+                        filteredLines.Add(line);
+                    }
+                    inProjectSection = false;
+                    currentProjectGuid = null;
+                }
+                else if (inProjectSection)
+                {
+                    if (projectGuids.Contains(currentProjectGuid))
+                    {
+                        filteredLines.Add(line);
+                    }
+                }
+                else
+                {
+                    // Include non-project lines (solution config, etc.)
+                    filteredLines.Add(line);
+                }
+            }
+
+            var filteredContent = string.Join("\n", filteredLines);
+            var filteredPath = Path.Combine(
+                Path.GetTempPath(), 
+                $"{Path.GetFileNameWithoutExtension(originalPath)}_filtered.sln");
+                
+            await FileUtils.WriteAllTextAsync(filteredPath, filteredContent);
+            return filteredPath;
+        }
+
+        private string ExtractProjectGuid(string projectLine)
+        {
+            var parts = projectLine.Split(',');
+            if (parts.Length >= 3)
+            {
+                return parts[2].Trim().Trim('"');
             }
+            return string.Empty;
         }
     }
 }

+ 20 - 0
FilteredSolutionsExtension/FilteredSolutionsExtension/directory_structure.txt

@@ -0,0 +1,20 @@
+Properties
++ AssemblyInfo.cs
+Resources
++ FilterSolutionsCommand.png
+DebugHelper.cs
+FileUtils.cs
+FilteredSolutionGenerator.cs
+FilteredSolutionsExtension.csproj
+FilteredSolutionsExtensionPackage.cs
+ManualFilterCommand.cs
+ProjectFilterDialog.xaml
+ProjectFilterDialog.xaml.cs
+ProjectInfo.cs
+ProjectNode.cs
+ProjectTreeItem.cs
+readme.md
+releasenote.txt
+SolutionEventsHandler.cs
+SolutionParser.cs
+source.extension.vsixmanifest