diff --git a/CodeContextGenerator/App.xaml.cs b/CodeContextGenerator/App.xaml.cs index 606ff08..0cf5e1c 100644 --- a/CodeContextGenerator/App.xaml.cs +++ b/CodeContextGenerator/App.xaml.cs @@ -1,8 +1,8 @@ -using System.Windows; -using CodeContextGenerator.Interfaces; +using CodeContextGenerator.Interfaces; using CodeContextGenerator.Services; using CodeContextGenerator.ViewModels; using Microsoft.Extensions.DependencyInjection; +using System.Windows; namespace CodeContextGenerator; diff --git a/CodeContextGenerator/Converters/BooleanToVisibilityConverter.cs b/CodeContextGenerator/Converters/BooleanToVisibilityConverter.cs index 7a27bbe..29674cf 100644 --- a/CodeContextGenerator/Converters/BooleanToVisibilityConverter.cs +++ b/CodeContextGenerator/Converters/BooleanToVisibilityConverter.cs @@ -1,24 +1,26 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Windows; using System.Windows.Data; -namespace CodeContextGenerator.Converters +namespace CodeContextGenerator.Converters; + +public class BooleanToVisibilityConverter : IValueConverter { - public class BooleanToVisibilityConverter : IValueConverter + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + bool boolValue = value is true; + + // Проверяем параметр для инвертирования + if (parameter is string paramStr && paramStr.Equals("Inverted", StringComparison.OrdinalIgnoreCase)) { - if (value is bool boolValue) - { - return boolValue ? Visibility.Visible : Visibility.Collapsed; - } - return Visibility.Collapsed; + boolValue = !boolValue; } - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } + return boolValue ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); } } \ No newline at end of file diff --git a/CodeContextGenerator/Models/FileItem.cs b/CodeContextGenerator/Models/FileItem.cs index caabda2..d3904bc 100644 --- a/CodeContextGenerator/Models/FileItem.cs +++ b/CodeContextGenerator/Models/FileItem.cs @@ -15,6 +15,7 @@ public partial class FileItem : ObservableObject private bool isDirectory; [ObservableProperty] + [NotifyPropertyChangedFor(nameof(HasSelectedChildren))] private bool? isSelected; [ObservableProperty] @@ -22,6 +23,11 @@ public partial class FileItem : ObservableObject public ObservableCollection Children { get; } = new ObservableCollection(); + // Событие, вызываемое при изменении состояния выбора + public event EventHandler SelectionChanged; + + public bool HasSelectedChildren => Children.Any(c => c.IsSelected == true || c.HasSelectedChildren); + partial void OnIsSelectedChanged(bool? oldValue, bool? newValue) { if (newValue.HasValue) @@ -29,6 +35,7 @@ public partial class FileItem : ObservableObject UpdateChildrenSelection(newValue.Value); } UpdateParentSelection(); + NotifySelectionChanged(); } private void UpdateChildrenSelection(bool value) @@ -37,7 +44,13 @@ public partial class FileItem : ObservableObject foreach (var child in Children) { + // Гарантируем вызов события для каждого ребенка + var oldValue = child.IsSelected; child.IsSelected = value; + if (oldValue != child.IsSelected) + { + child.RaiseSelectionChanged(); + } } } @@ -50,7 +63,41 @@ public partial class FileItem : ObservableObject var noneSelected = children.All(c => c.IsSelected == false); var hasIndeterminate = children.Any(c => c.IsSelected == null); - Parent.IsSelected = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null)); - Parent.UpdateParentSelection(); + bool? newParentState = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null)); + /*bool? newParentState; + if (hasIndeterminate) + { + newParentState = null; + } + else if (allSelected) + { + newParentState = true; + } + else if (noneSelected) + { + newParentState = false; + } + else + { + newParentState = null; + }*/ + + if (Parent.IsSelected != newParentState) + { + Parent.IsSelected = newParentState; + Parent.RaiseSelectionChanged(); + } + } + + private void NotifySelectionChanged() + { + RaiseSelectionChanged(); + } + + // Публичный метод для гарантии вызова события + public void RaiseSelectionChanged() + { + // Вызываем событие для текущего элемента + SelectionChanged?.Invoke(this, EventArgs.Empty); } } \ No newline at end of file diff --git a/CodeContextGenerator/Services/FileScannerService.cs b/CodeContextGenerator/Services/FileScannerService.cs index 425fe35..19c2e27 100644 --- a/CodeContextGenerator/Services/FileScannerService.cs +++ b/CodeContextGenerator/Services/FileScannerService.cs @@ -9,7 +9,7 @@ public class FileScannerService : IFileScannerService private static readonly string[] ExcludedDirectories = { "bin", "obj", ".git", "packages", ".vs", "Properties", "node_modules", ".vscode", ".idea", "Debug", "Release", - "wwwroot", "dist", "build", "node_modules" + "wwwroot", "dist", "build", ".gitignore", ".dockerignore" }; private static readonly string[] IncludedExtensions = { ".cs", ".xaml" }; @@ -25,13 +25,13 @@ public class FileScannerService : IFileScannerService var directories = Directory.GetDirectories(path) .Where(d => !ExcludedDirectories.Any(ex => - d.EndsWith(ex, System.StringComparison.OrdinalIgnoreCase) || - Path.GetFileName(d).Equals(ex, System.StringComparison.OrdinalIgnoreCase))) + d.EndsWith(ex, StringComparison.OrdinalIgnoreCase) || + Path.GetFileName(d).Equals(ex, StringComparison.OrdinalIgnoreCase))) .ToList(); var files = Directory.GetFiles(path) .Where(f => IncludedExtensions.Any(ext => - f.EndsWith(ext, System.StringComparison.OrdinalIgnoreCase))) + f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) .ToList(); int totalItems = directories.Count + files.Count; @@ -55,7 +55,7 @@ public class FileScannerService : IFileScannerService await BuildDirectoryTreeAsync(dir, dirItem, progress, cancellationToken); // Добавляем директорию только если в ней есть файлы или поддиректории с файлами - if (dirItem.Children.Any()) + if (dirItem.Children.Count > 0 || files.Count > 0) { parentItem.Children.Add(dirItem); } diff --git a/CodeContextGenerator/Services/ProjectLoaderService.cs b/CodeContextGenerator/Services/ProjectLoaderService.cs index 86e340e..f23cdb0 100644 --- a/CodeContextGenerator/Services/ProjectLoaderService.cs +++ b/CodeContextGenerator/Services/ProjectLoaderService.cs @@ -1,6 +1,7 @@ using CodeContextGenerator.Interfaces; using CodeContextGenerator.Models; using System.IO; +using System.Text.RegularExpressions; using System.Windows; namespace CodeContextGenerator.Services; @@ -63,7 +64,7 @@ public class ProjectLoaderService : IProjectLoaderService try { - var projectPaths = ParseSolutionProjects(solutionPath); + var projectPaths = ParseSolutionProjects(solutionPath, solutionDir); int totalProjects = projectPaths.Count; int processedProjects = 0; @@ -94,7 +95,10 @@ public class ProjectLoaderService : IProjectLoaderService } processedProjects++; - progress?.Report((int)((processedProjects * 100.0) / totalProjects)); + if (totalProjects > 0 && progress != null) + { + progress.Report((int)((processedProjects * 100.0) / totalProjects)); + } } } catch (Exception ex) @@ -105,46 +109,36 @@ public class ProjectLoaderService : IProjectLoaderService return solutionItem; } - private async Task LoadCsprojAsync(string csprojPath, IProgress progress, CancellationToken cancellationToken) - { - string projectDir = Path.GetDirectoryName(csprojPath); - string projectName = Path.GetFileNameWithoutExtension(csprojPath); - - var projectItem = new FileItem - { - Name = projectName, - FullName = projectDir, - IsDirectory = true, - IsSelected = false - }; - - await _fileScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken); - return projectItem; - } - - private List ParseSolutionProjects(string solutionPath) + // Улучшенный парсинг .sln файлов + private List ParseSolutionProjects(string solutionPath, string solutionDir) { var projects = new List(); - string solutionDir = Path.GetDirectoryName(solutionPath); + var projectRegex = new Regex(@"Project\(""[^""]*""\)\s*=\s*""([^""]*)"",\s*""([^""]*)""", RegexOptions.IgnoreCase); try { foreach (string line in File.ReadAllLines(solutionPath)) { - if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase)) + var match = projectRegex.Match(line); + if (match.Success && match.Groups.Count >= 3) { - var parts = line.Split(new[] { '"' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length >= 3) + string projectName = match.Groups[1].Value; + string relativePath = match.Groups[2].Value; + + // Пропускаем служебные проекты + if (projectName.Contains("Solution Items") || + relativePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) || + relativePath.EndsWith(".suo", StringComparison.OrdinalIgnoreCase)) { - string relativePath = parts[2].Trim(); - if (relativePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)) - { - string absolutePath = Path.GetFullPath(Path.Combine(solutionDir, relativePath)); - if (File.Exists(absolutePath)) - { - projects.Add(absolutePath); - } - } + continue; + } + + string absolutePath = Path.GetFullPath(Path.Combine(solutionDir, relativePath)); + + // Проверяем, что это .csproj файл + if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath)) + { + projects.Add(absolutePath); } } } @@ -157,6 +151,23 @@ public class ProjectLoaderService : IProjectLoaderService return projects; } + private async Task LoadCsprojAsync(string csprojPath, IProgress progress, CancellationToken cancellationToken) + { + string projectDir = Path.GetDirectoryName(csprojPath); + string projectName = Path.GetFileNameWithoutExtension(csprojPath); + + var projectItem = new FileItem + { + Name = $"{projectName} (Проект)", + FullName = projectDir, + IsDirectory = true, + IsSelected = false + }; + + await _fileScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken); + return projectItem; + } + public string GetDefaultOutputFileName(string projectPath) { if (Directory.Exists(projectPath)) diff --git a/CodeContextGenerator/Services/UIService.cs b/CodeContextGenerator/Services/UIService.cs index e2d68ec..e2f652f 100644 --- a/CodeContextGenerator/Services/UIService.cs +++ b/CodeContextGenerator/Services/UIService.cs @@ -7,20 +7,32 @@ namespace CodeContextGenerator.Services; public class UIService : IUIService { + private readonly ISettingsService _settingsService; + + public UIService(ISettingsService settingsService) + { + _settingsService = settingsService; + } + public string ShowFolderBrowserDialog(string initialDirectory = null) { - // Используем OpenFileDialog для выбора папки - это стандартный способ в WPF + // Используем специальный трюк для выбора папки в WPF var dialog = new OpenFileDialog { Title = "Выберите папку с проектом", - Filter = "Папки|*.folder", // Фильтр для отображения только папок - FileName = "select_folder", // Имя файла для обхода проверки существования файла + Filter = "Папки|*.dummy", // Фильтр для отображения только папок + FileName = "выберите_папку", // Специальное имя файла CheckFileExists = false, CheckPathExists = true, - ValidateNames = false, // Отключаем валидацию имен для выбора папок + ValidateNames = false, DereferenceLinks = true }; + if (string.IsNullOrEmpty(initialDirectory) || !Directory.Exists(initialDirectory)) + { + initialDirectory = _settingsService.GetLastProjectPath(); + } + if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory)) { dialog.InitialDirectory = initialDirectory; @@ -29,8 +41,9 @@ public class UIService : IUIService var result = dialog.ShowDialog(); if (result == true) { - // Возвращаем папку, а не файл - return Path.GetDirectoryName(dialog.FileName); + // Возвращаем директорию, а не путь к файлу + string selectedFolder = Path.GetDirectoryName(dialog.FileName); + return selectedFolder; } return null; @@ -42,11 +55,17 @@ public class UIService : IUIService var dialog = new OpenFileDialog { - Title = "Выберите файл решения, проекта или папку", - Filter = "Все поддерживаемые файлы (*.sln;*.csproj;*.razor)|*.sln;*.csproj;*.razor|Файлы решений (*.sln)|*.sln|Файлы проектов (*.csproj)|*.csproj|Файлы Razor (*.razor)|*.razor|Все файлы (*.*)|*.*", + Title = "Выберите файл решения или проекта", + Filter = "Все поддерживаемые файлы (*.sln;*.csproj)|*.sln;*.csproj|Файлы решений Visual Studio (*.sln)|*.sln|Файлы проектов C# (*.csproj)|*.csproj|Все файлы (*.*)|*.*", Multiselect = false }; + var lastPath = _settingsService.GetLastProjectPath(); + if (!string.IsNullOrEmpty(lastPath) && Directory.Exists(lastPath)) + { + dialog.InitialDirectory = lastPath; + } + bool? result = dialog.ShowDialog(); if (result == true) { diff --git a/CodeContextGenerator/ViewModels/MainViewModel.cs b/CodeContextGenerator/ViewModels/MainViewModel.cs index e41cfe9..6357192 100644 --- a/CodeContextGenerator/ViewModels/MainViewModel.cs +++ b/CodeContextGenerator/ViewModels/MainViewModel.cs @@ -1,208 +1,282 @@ using CodeContextGenerator.Interfaces; using CodeContextGenerator.Models; +using CodeContextGenerator.Services; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; +using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using System.Windows; -namespace CodeContextGenerator.ViewModels; - -public partial class MainViewModel : ObservableObject +namespace CodeContextGenerator.ViewModels { - private readonly IProjectLoaderService _projectLoaderService; - private readonly IFileScannerService _fileScannerService; - private readonly IContextFileGenerator _contextFileGenerator; - private readonly IUIService _uiService; - private readonly ISettingsService _settingsService; - - private CancellationTokenSource _cancellationTokenSource; - - [ObservableProperty] - private FileItem rootDirectory; - - [ObservableProperty] - private string selectedProjectPath; - - [ObservableProperty] - private bool isProcessing; - - [ObservableProperty] - private int progressValue; - - [ObservableProperty] - private string progressText; - - [ObservableProperty] - private bool isProjectLoaded; - - public MainViewModel( - IProjectLoaderService projectLoaderService, - IFileScannerService fileScannerService, - IContextFileGenerator contextFileGenerator, - IUIService uiService, - ISettingsService settingsService) + public partial class MainViewModel : ObservableObject { - _projectLoaderService = projectLoaderService; - _fileScannerService = fileScannerService; - _contextFileGenerator = contextFileGenerator; - _uiService = uiService; - _settingsService = settingsService; - } + private readonly IProjectLoaderService _projectLoaderService; + private readonly IFileScannerService _fileScannerService; + private readonly IContextFileGenerator _contextFileGenerator; + private readonly IUIService _uiService; + private readonly ISettingsService _settingsService; - public bool CanSelectProject => !IsProcessing; - public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory); + private CancellationTokenSource _cancellationTokenSource; - [RelayCommand(CanExecute = nameof(CanSelectProject))] - private void SelectProject() - { - var initialDir = _settingsService.GetLastProjectPath(); + [ObservableProperty] + private FileItem rootDirectory; - // Сначала пробуем выбрать файл проекта/решения - if (_uiService.ShowOpenProjectFileDialog(out var filePath)) + [ObservableProperty] + private string selectedProjectPath; + + [ObservableProperty] + private bool isProcessing; + + [ObservableProperty] + private int progressValue; + + [ObservableProperty] + private string progressText; + + [ObservableProperty] + private bool isProjectLoaded; + + // Явное объявление команд для гарантии их создания + public IRelayCommand SelectProjectCommand { get; } + public IAsyncRelayCommand GenerateContextFileCommand { get; } + public IRelayCommand CancelProcessingCommand { get; } + public IRelayCommand ExitApplicationCommand { get; } + + public MainViewModel( + IProjectLoaderService projectLoaderService, + IFileScannerService fileScannerService, + IContextFileGenerator contextFileGenerator, + IUIService uiService, + ISettingsService settingsService) { - LoadProject(filePath); - return; + _projectLoaderService = projectLoaderService; + _fileScannerService = fileScannerService; + _contextFileGenerator = contextFileGenerator; + _uiService = uiService; + _settingsService = settingsService; + + // Явная инициализация команд + SelectProjectCommand = new RelayCommand(SelectProject, CanSelectProject); + GenerateContextFileCommand = new AsyncRelayCommand(GenerateContextFileAsync, CanGenerate); + CancelProcessingCommand = new RelayCommand(CancelProcessing); + ExitApplicationCommand = new RelayCommand(ExitApplication); } - // Если отменили выбор файла - предлагаем выбрать папку - var folderPath = _uiService.ShowFolderBrowserDialog(initialDir); - if (!string.IsNullOrEmpty(folderPath)) - { - LoadProject(folderPath); - } - } + private bool CanSelectProject() => !IsProcessing; - [RelayCommand(CanExecute = nameof(CanGenerate))] - private async Task GenerateContextFileAsync() - { - var selectedFiles = _fileScannerService.GetSelectedFiles(RootDirectory); - if (selectedFiles.Count == 0) + private bool CanGenerate() { - _uiService.ShowMessage("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxImage.Warning); - return; + bool result = !IsProcessing && + IsProjectLoaded && + RootDirectory != null && + HasSelectedFiles(RootDirectory); + + // Отладочная информация + System.Diagnostics.Debug.WriteLine($"CanGenerate: {result}, IsProcessing: {IsProcessing}, IsProjectLoaded: {IsProjectLoaded}, RootDirectory: {(RootDirectory != null)}, HasSelectedFiles: {HasSelectedFiles(RootDirectory)}"); + + return result; } - var defaultFileName = _projectLoaderService.GetDefaultOutputFileName(SelectedProjectPath); - var initialDir = _settingsService.GetLastProjectPath(); - - if (!_uiService.ShowSaveFileDialog(defaultFileName, initialDir, out var savePath)) - return; - - IsProcessing = true; - ProgressText = "Генерация контекстного файла..."; - ProgressValue = 0; - - try + private void SelectProject() { - _cancellationTokenSource = new CancellationTokenSource(); - var progress = new Progress(value => + var initialDir = !string.IsNullOrEmpty(SelectedProjectPath) && Directory.Exists(SelectedProjectPath) + ? Path.GetDirectoryName(SelectedProjectPath) + : _settingsService.GetLastProjectPath(); + + if (_uiService.ShowOpenProjectFileDialog(out var filePath)) { - ProgressValue = value; - ProgressText = $"Генерация: {value}%"; - }); + LoadProject(filePath); + return; + } - await _contextFileGenerator.GenerateContextFileAsync( - selectedFiles, - savePath, - Path.GetDirectoryName(SelectedProjectPath), - progress, - _cancellationTokenSource.Token); - - _uiService.ShowMessage($"Файл успешно создан:\n{savePath}", "Успех"); - } - catch (OperationCanceledException) - { - ProgressText = "Генерация отменена"; - } - catch (IOException ex) - { - _uiService.ShowMessage($"Ошибка доступа к файлу: {ex.Message}\nПожалуйста, закройте файлы или предоставьте необходимые права доступа.", "Ошибка доступа", MessageBoxImage.Error); - } - catch (System.Exception ex) - { - _uiService.ShowMessage($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxImage.Error); - } - finally - { - IsProcessing = false; - } - } - - [RelayCommand] - private void CancelProcessing() - { - _cancellationTokenSource?.Cancel(); - } - - [RelayCommand] - private void ExitApplication() - { - Application.Current.Shutdown(); - } - - private void LoadProject(string projectPath) - { - SelectedProjectPath = projectPath; - _settingsService.SaveLastProjectPath(Path.GetDirectoryName(projectPath)); - - IsProjectLoaded = false; - LoadProjectAsync(projectPath); - } - - private async void LoadProjectAsync(string projectPath) - { - IsProcessing = true; - ProgressText = "Загрузка проекта..."; - ProgressValue = 0; - - try - { - var progress = new Progress(value => + var folderPath = _uiService.ShowFolderBrowserDialog(initialDir); + if (!string.IsNullOrEmpty(folderPath)) { - ProgressValue = value; - ProgressText = $"Загрузка: {value}%"; - }); + LoadProject(folderPath); + } + } - _cancellationTokenSource = new CancellationTokenSource(); - RootDirectory = await _projectLoaderService.LoadProjectFromPathAsync(projectPath, progress, _cancellationTokenSource.Token); + private async Task GenerateContextFileAsync() + { + var selectedFiles = _fileScannerService.GetSelectedFiles(RootDirectory); + if (selectedFiles.Count == 0) + { + _uiService.ShowMessage("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxImage.Warning); + return; + } - IsProjectLoaded = true; - ProgressText = "Проект загружен успешно"; + var defaultFileName = _projectLoaderService.GetDefaultOutputFileName(SelectedProjectPath); + var initialDir = _settingsService.GetLastProjectPath() ?? Path.GetDirectoryName(SelectedProjectPath); - // Сбрасываем выделение после загрузки - ClearSelections(RootDirectory); - } - catch (OperationCanceledException) - { - ProgressText = "Загрузка отменена"; - } - catch (System.Exception ex) - { - _uiService.ShowMessage($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxImage.Error); - } - finally - { - IsProcessing = false; - } - } + if (!_uiService.ShowSaveFileDialog(defaultFileName, initialDir, out var savePath)) + return; - private void ClearSelections(FileItem item) - { - item.IsSelected = false; - foreach (var child in item.Children) - { - ClearSelections(child); - } - } + IsProcessing = true; + ProgressText = "Генерация контекстного файла..."; + ProgressValue = 0; - private bool HasSelectedFiles(FileItem item) - { - if (item == null) return false; - if (item.IsSelected == true && !item.IsDirectory) return true; - foreach (var child in item.Children) - { - if (HasSelectedFiles(child)) return true; + try + { + _cancellationTokenSource = new CancellationTokenSource(); + var progress = new Progress(value => + { + ProgressValue = value; + ProgressText = $"Генерация: {value}%"; + }); + + await _contextFileGenerator.GenerateContextFileAsync( + selectedFiles, + savePath, + Path.GetDirectoryName(SelectedProjectPath), + progress, + _cancellationTokenSource.Token); + + _uiService.ShowMessage($"Файл успешно создан:\n{savePath}", "Успех"); + } + catch (OperationCanceledException) + { + ProgressText = "Генерация отменена"; + } + catch (IOException ex) + { + _uiService.ShowMessage($"Ошибка доступа к файлу: {ex.Message}\nПожалуйста, закройте файлы или предоставьте необходимые права доступа.", "Ошибка доступа", MessageBoxImage.Error); + } + catch (System.Exception ex) + { + _uiService.ShowMessage($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxImage.Error); + } + finally + { + IsProcessing = false; + // Принудительно обновляем команды после завершения операции + UpdateCommandsCanExecute(); + } + } + + private void CancelProcessing() + { + _cancellationTokenSource?.Cancel(); + ProgressText = "Операция отменена"; + } + + private void ExitApplication() + { + Application.Current.Shutdown(); + } + + private void LoadProject(string projectPath) + { + SelectedProjectPath = projectPath; + _settingsService.SaveLastProjectPath(Path.GetDirectoryName(projectPath)); + + IsProjectLoaded = false; + RootDirectory = null; + UpdateCommandsCanExecute(); + + LoadProjectAsync(projectPath); + } + + private async void LoadProjectAsync(string projectPath) + { + ProgressText = "Загрузка проекта..."; + ProgressValue = 0; + + try + { + var progress = new Progress(value => + { + ProgressValue = value; + ProgressText = $"Загрузка: {value}%"; + }); + + _cancellationTokenSource = new CancellationTokenSource(); + RootDirectory = await _projectLoaderService.LoadProjectFromPathAsync(projectPath, progress, _cancellationTokenSource.Token); + + IsProjectLoaded = true; + ProgressText = "Проект загружен успешно"; + + // Подписываемся на события изменения выбора + SubscribeToSelectionChanges(RootDirectory); + + // Сбрасываем выделение после загрузки + if (RootDirectory != null) + { + ClearSelections(RootDirectory); + } + + // Принудительно обновляем команды + UpdateCommandsCanExecute(); + } + catch (OperationCanceledException) + { + ProgressText = "Загрузка отменена"; + } + catch (System.Exception ex) + { + _uiService.ShowMessage($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxImage.Error); + } + finally + { + IsProcessing = false; + UpdateCommandsCanExecute(); + } + } + + // Рекурсивная подписка на события изменения выбора + private void SubscribeToSelectionChanges(FileItem item) + { + if (item == null) return; + + item.SelectionChanged += (sender, args) => + { + UpdateCommandsCanExecute(); + }; + + foreach (var child in item.Children) + { + SubscribeToSelectionChanges(child); + } + } + + private void ClearSelections(FileItem item) + { + if (item == null) return; + + item.IsSelected = false; + foreach (var child in item.Children) + { + ClearSelections(child); + } + } + + private bool HasSelectedFiles(FileItem item) + { + if (item == null) return false; + + if (!item.IsDirectory && item.IsSelected == true) + return true; + + if (item.IsDirectory) + { + foreach (var child in item.Children) + { + if (HasSelectedFiles(child)) + return true; + } + } + + return false; + } + + // Метод для принудительного обновления всех команд + private void UpdateCommandsCanExecute() + { + (SelectProjectCommand as RelayCommand)?.NotifyCanExecuteChanged(); + (GenerateContextFileCommand as AsyncRelayCommand)?.NotifyCanExecuteChanged(); } - return false; } } \ No newline at end of file diff --git a/CodeContextGenerator/Views/MainWindow.xaml b/CodeContextGenerator/Views/MainWindow.xaml index 500e08f..e194e7e 100644 --- a/CodeContextGenerator/Views/MainWindow.xaml +++ b/CodeContextGenerator/Views/MainWindow.xaml @@ -59,50 +59,51 @@ TextWrapping="Wrap" /> - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - + + -