From 5479da163e638274a58bfc816ffd0f10d0e6750d Mon Sep 17 00:00:00 2001 From: golem84 Date: Wed, 10 Dec 2025 21:33:06 +0500 Subject: [PATCH] refactor mvvm, add datacontext --- .../CodeContextGenerator.csproj | 4 + CodeContextGenerator/Models/FileItem.cs | 97 +- .../ViewModels/MainViewModel.cs | 871 ++++++++---------- CodeContextGenerator/Views/MainWindow.xaml | 173 ++-- CodeContextGenerator/Views/MainWindow.xaml.cs | 42 +- 5 files changed, 566 insertions(+), 621 deletions(-) diff --git a/CodeContextGenerator/CodeContextGenerator.csproj b/CodeContextGenerator/CodeContextGenerator.csproj index cdf319c..d9b5680 100644 --- a/CodeContextGenerator/CodeContextGenerator.csproj +++ b/CodeContextGenerator/CodeContextGenerator.csproj @@ -8,6 +8,10 @@ true + + + + True diff --git a/CodeContextGenerator/Models/FileItem.cs b/CodeContextGenerator/Models/FileItem.cs index a076661..f213d2f 100644 --- a/CodeContextGenerator/Models/FileItem.cs +++ b/CodeContextGenerator/Models/FileItem.cs @@ -1,77 +1,66 @@ -using System.Collections.ObjectModel; -using System.ComponentModel; -using System.Runtime.CompilerServices; +using CommunityToolkit.Mvvm.ComponentModel; +using System.Collections.ObjectModel; namespace CodeContextGenerator.Models { - public class FileItem : INotifyPropertyChanged + public partial class FileItem : ObservableObject { - public string Name { get; set; } - public string FullName { get; set; } - public bool IsDirectory { get; set; } - public ObservableCollection Children { get; set; } = new ObservableCollection(); - public FileItem Parent { get; set; } + [ObservableProperty] + private string? name; - private bool? _isSelected; - public bool? IsSelected + [ObservableProperty] + private string? fullName; + + [ObservableProperty] + private bool isDirectory; + + [ObservableProperty] + private bool? isSelected; + + [ObservableProperty] + private FileItem? parent; + + public ObservableCollection Children { get; } = new ObservableCollection(); + + /*partial void OnIsSelectedChanged(bool? oldValue, bool? newValue) { - get => _isSelected; - set + if (newValue.HasValue) { - if (_isSelected != value) - { - _isSelected = value; - OnPropertyChanged(); - UpdateParentSelection(); - - // Если это директория и установлено конкретное значение (не null), применяем ко всем детям - if (IsDirectory && value.HasValue) - { - foreach (var child in Children) - { - child.IsSelected = value.Value; - } - } - } + UpdateChildrenSelection(newValue.Value); } - } + UpdateParentSelection(); + }*/ - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + private void UpdateChildrenSelection(bool value) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + if (!IsDirectory) return; + + foreach (var child in Children) + { + child.IsSelected = value; + } } private void UpdateParentSelection() { if (Parent == null) return; - var children = Parent.Children.ToList(); + var children = Parent.Children; var allSelected = children.All(c => c.IsSelected == true); var noneSelected = children.All(c => c.IsSelected == false); + var hasIndeterminate = children.Any(c => c.IsSelected == null); - // Если есть дети с null - устанавливаем null - bool hasIndeterminate = children.Any(c => c.IsSelected == null); - - if (hasIndeterminate) - { - Parent.IsSelected = null; - } - else if (allSelected) - { - Parent.IsSelected = true; - } - else if (noneSelected) - { - Parent.IsSelected = false; - } - else - { - Parent.IsSelected = null; - } - + Parent.IsSelected = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null)); Parent.UpdateParentSelection(); } + + public void ClearSelection() + { + IsSelected = false; + foreach (var child in Children) + { + child.ClearSelection(); + } + } } } \ No newline at end of file diff --git a/CodeContextGenerator/ViewModels/MainViewModel.cs b/CodeContextGenerator/ViewModels/MainViewModel.cs index d95ec48..f585294 100644 --- a/CodeContextGenerator/ViewModels/MainViewModel.cs +++ b/CodeContextGenerator/ViewModels/MainViewModel.cs @@ -1,522 +1,431 @@ -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Windows; -using System.Windows.Input; -using Microsoft.Win32; -using CodeContextGenerator.Models; +using CodeContextGenerator.Models; using CodeContextGenerator.Services; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.Win32; +using System.ComponentModel; using System.IO; using System.Text; -using System.Threading.Tasks; -using System.Threading; -using System.Collections.Generic; -using System.Linq; +using System.Windows; -namespace CodeContextGenerator.ViewModels +namespace CodeContextGenerator.ViewModels; + +public partial class MainViewModel : ObservableObject, INotifyPropertyChanged { - public class MainViewModel : INotifyPropertyChanged + [ObservableProperty] + private FileItem? _rootDirectory = new(); + [ObservableProperty] + private string _selectedProjectFilePath = string.Empty; + [ObservableProperty] + private bool _isProcessing = false; + [ObservableProperty] + private int _progressValue; + [ObservableProperty] + private string _progressText = string.Empty; + [ObservableProperty] + private string? _lastProjectPath = string.Empty; + [ObservableProperty] + private bool _isProjectLoaded; + + private CancellationTokenSource _cancellationTokenSource = new(); + + public bool CanSelectFolder => !IsProcessing; + public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory); + + //public ICommand SelectProjectFileCommand { get; } + //public ICommand GenerateCommand { get; } + //public ICommand CancelCommand { get; } + //public ICommand ExitCommand { get; } + + public MainViewModel() { - private FileItem _rootDirectory; - private string _selectedProjectFilePath; - private bool _isProcessing; - private int _progressValue; - private string _progressText; - private CancellationTokenSource _cancellationTokenSource; - private string _lastProjectPath; - private bool _isProjectLoaded; + //ExitCommand = new RelayCommand(_ => Application.Current.Shutdown()); + LoadSettings(); + } - public FileItem RootDirectory + private bool HasSelectedFiles(FileItem item) + { + if (item == null) return false; + if (item.IsSelected == true && !item.IsDirectory) return true; + return item.Children.Any(HasSelectedFiles); + } + + //[RelayCommand(CanExecute = nameof(CanSelectFolder))] + [RelayCommand(CanExecute = nameof(CanSelectFolder))] + private void Exit() + { + Application.Current.Shutdown(); + } + + //[RelayCommand(CanExecute = nameof(CanSelectFolder))] + [RelayCommand(CanExecute = nameof(CanGenerate))] + private void Cancel() + { + CancelProcessing(); + } + + [RelayCommand(CanExecute = nameof(CanSelectFolder))] + private async Task SelectProjectFileAsync() + { + var dialog = new OpenFileDialog { - get => _rootDirectory; - set + Title = "Выберите файл решения или проекта", + CheckFileExists = true, + CheckPathExists = true, + Filter = "Файлы решений (*.sln)|*.sln|Файлы проектов (*.csproj)|*.csproj|Все поддерживаемые (*.sln;*.csproj)|*.sln;*.csproj|Все файлы (*.*)|*.*", + InitialDirectory = !string.IsNullOrEmpty(LastProjectPath) && Directory.Exists(LastProjectPath) ? LastProjectPath : null + }; + + if (dialog.ShowDialog() != true) return; + + SelectedProjectFilePath = dialog.FileName; + LastProjectPath = Path.GetDirectoryName(SelectedProjectFilePath); + SaveSettings(); + + IsProjectLoaded = false; + await LoadProjectAsync(SelectedProjectFilePath); + } + + private async Task LoadProjectAsync(string projectFilePath) + { + IsProcessing = true; + ProgressText = "Загрузка проекта..."; + ProgressValue = 0; + + try + { + string? projectDirectory = Path.GetDirectoryName(projectFilePath); + var progress = new Progress(value => { - _rootDirectory = value; - OnPropertyChanged(); + ProgressValue = value; + ProgressText = $"Загрузка: {value}%"; + }); + + _cancellationTokenSource = new CancellationTokenSource(); + + if (projectFilePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase)) + { + await LoadSolutionAsync(projectFilePath, progress, _cancellationTokenSource.Token); } - } - - public string SelectedProjectFilePath - { - get => _selectedProjectFilePath; - set + else if (projectFilePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)) { - _selectedProjectFilePath = value; - OnPropertyChanged(); - } - } - - public bool IsProcessing - { - get => _isProcessing; - set - { - _isProcessing = value; - OnPropertyChanged(); - OnPropertyChanged(nameof(CanSelectFolder)); - OnPropertyChanged(nameof(CanGenerate)); - } - } - - public int ProgressValue - { - get => _progressValue; - set - { - _progressValue = value; - OnPropertyChanged(); - } - } - - public string ProgressText - { - get => _progressText; - set - { - _progressText = value; - OnPropertyChanged(); - } - } - - public bool IsProjectLoaded - { - get => _isProjectLoaded; - set - { - _isProjectLoaded = value; - OnPropertyChanged(); - } - } - - public bool CanSelectFolder => !IsProcessing; - public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory); - - public ICommand SelectProjectFileCommand { get; } - public ICommand GenerateCommand { get; } - public ICommand CancelCommand { get; } - public ICommand ExitCommand { get; } - - public MainViewModel() - { - SelectProjectFileCommand = new RelayCommand(SelectProjectFileAsync); - GenerateCommand = new RelayCommand(GenerateFileAsync, _ => CanGenerate); - CancelCommand = new RelayCommand(CancelProcessing, _ => IsProcessing); - ExitCommand = new RelayCommand(_ => Application.Current.Shutdown()); - - LoadSettings(); - } - - 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; + await LoadProjectDirectoryAsync(projectDirectory, progress, _cancellationTokenSource.Token); } - return false; + IsProjectLoaded = true; + ProgressText = "Проект загружен успешно"; } - - private async void SelectProjectFileAsync(object parameter) + catch (OperationCanceledException) { - if (!CanSelectFolder) return; + ProgressText = "Загрузка отменена"; + RootDirectory = null; + IsProjectLoaded = false; + } + catch (Exception ex) + { + MessageBox.Show($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error); + RootDirectory = null; + IsProjectLoaded = false; + } + finally + { + IsProcessing = false; + } + } - var dialog = new OpenFileDialog + private async Task LoadSolutionAsync(string solutionPath, IProgress progress, CancellationToken cancellationToken) + { + try + { + string? solutionDirectory = Path.GetDirectoryName(solutionPath); + var solutionProjects = ParseSolutionProjects(solutionPath); + + var solutionItem = new FileItem { - Title = "Выберите файл решения или проекта", - CheckFileExists = true, - CheckPathExists = true, - Filter = "Файлы решений Visual Studio (*.sln)|*.sln|Файлы проектов C# (*.csproj)|*.csproj|Все поддерживаемые файлы (*.sln;*.csproj)|*.sln;*.csproj|Все файлы (*.*)|*.*", - Multiselect = false + Name = Path.GetFileName(solutionPath), + FullName = solutionPath, + IsDirectory = true, + IsSelected = false }; - if (!string.IsNullOrEmpty(_lastProjectPath) && Directory.Exists(_lastProjectPath)) - { - dialog.InitialDirectory = _lastProjectPath; - } + int totalProjects = solutionProjects.Count; + int processedProjects = 0; - bool? result = dialog.ShowDialog(); - - if (result == true) - { - string selectedFilePath = dialog.FileName; - string projectDirectory = Path.GetDirectoryName(selectedFilePath); - - if (!string.IsNullOrEmpty(projectDirectory)) - { - _lastProjectPath = projectDirectory; - SaveSettings(); - - SelectedProjectFilePath = selectedFilePath; - IsProjectLoaded = false; - - await LoadProjectAsync(selectedFilePath); - } - } - } - - private async Task LoadProjectAsync(string projectFilePath) - { - IsProcessing = true; - ProgressText = "Загрузка проекта..."; - ProgressValue = 0; - - try - { - string projectDirectory = Path.GetDirectoryName(projectFilePath); - SelectedProjectFilePath = projectFilePath; - - var progress = new Progress(value => - { - ProgressValue = value; - ProgressText = $"Загрузка: {value}%"; - }); - - _cancellationTokenSource = new CancellationTokenSource(); - - // Определяем тип файла - if (projectFilePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase)) - { - await LoadSolutionAsync(projectFilePath, progress, _cancellationTokenSource.Token); - } - else if (projectFilePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase)) - { - await LoadProjectDirectoryAsync(projectDirectory, progress, _cancellationTokenSource.Token); - } - - IsProjectLoaded = true; - ProgressText = "Проект загружен успешно"; - } - catch (OperationCanceledException) - { - ProgressText = "Загрузка отменена"; - RootDirectory = null; - IsProjectLoaded = false; - } - catch (Exception ex) - { - MessageBox.Show($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error); - RootDirectory = null; - IsProjectLoaded = false; - } - finally - { - IsProcessing = false; - } - } - - private async Task LoadSolutionAsync(string solutionPath, IProgress progress, CancellationToken cancellationToken) - { - try - { - string solutionDirectory = Path.GetDirectoryName(solutionPath); - var solutionProjects = ParseSolutionProjects(solutionPath); - - var solutionItem = new FileItem - { - Name = Path.GetFileName(solutionPath), - FullName = solutionPath, - IsDirectory = true, - IsSelected = false - }; - - int totalProjects = solutionProjects.Count; - int processedProjects = 0; - - foreach (var projectPath in solutionProjects) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (File.Exists(projectPath)) - { - string projectDir = Path.GetDirectoryName(projectPath); - string projectName = Path.GetFileName(projectDir); - - var projectItem = new FileItem - { - Name = projectName, - FullName = projectDir, - IsDirectory = true, - Parent = solutionItem, - IsSelected = false - }; - - await ProjectScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken); - - if (projectItem.Children.Any()) - { - solutionItem.Children.Add(projectItem); - } - } - - processedProjects++; - progress?.Report((int)((processedProjects * 100.0) / totalProjects)); - } - - RootDirectory = solutionItem; - - // После загрузки решения - не выбираем ничего по умолчанию - ClearAllSelections(solutionItem); - } - catch (Exception ex) - { - throw new Exception($"Ошибка при обработке решения: {ex.Message}", ex); - } - } - - private List ParseSolutionProjects(string solutionPath) - { - var projects = new List(); - try - { - string solutionDirectory = Path.GetDirectoryName(solutionPath); - - foreach (string line in File.ReadAllLines(solutionPath)) - { - if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase)) - { - var parts = line.Split(new[] { '"' }, StringSplitOptions.RemoveEmptyEntries); - if (parts.Length >= 3) - { - string relativePath = parts[2].Trim(); - string absolutePath = Path.GetFullPath(Path.Combine(solutionDirectory, relativePath)); - - if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath)) - { - projects.Add(absolutePath); - } - } - } - } - } - catch (Exception ex) - { - throw new Exception($"Ошибка парсинга файла решения: {ex.Message}", ex); - } - return projects; - } - - private async Task LoadProjectDirectoryAsync(string projectDirectory, IProgress progress, CancellationToken cancellationToken) - { - try - { - var projectName = Path.GetFileName(projectDirectory); - var rootItem = new FileItem - { - Name = projectName, - FullName = projectDirectory, - IsDirectory = true, - IsSelected = false - }; - - await ProjectScannerService.BuildDirectoryTreeAsync(projectDirectory, rootItem, progress, cancellationToken); - - RootDirectory = rootItem; - - // После загрузки проекта - не выбираем ничего по умолчанию - ClearAllSelections(rootItem); - } - catch (Exception ex) - { - throw new Exception($"Ошибка при загрузке проекта: {ex.Message}", ex); - } - } - - private void ClearAllSelections(FileItem item) - { - item.IsSelected = false; - foreach (var child in item.Children) - { - ClearAllSelections(child); - } - } - - private async void GenerateFileAsync(object parameter) - { - if (!CanGenerate || RootDirectory == null) return; - - var selectedFiles = GetSelectedFiles(RootDirectory); - - if (selectedFiles.Count == 0) - { - MessageBox.Show("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxButton.OK, MessageBoxImage.Warning); - return; - } - - var saveDialog = new SaveFileDialog - { - Title = "Сохранить контекстный файл", - Filter = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*", - FileName = $"{Path.GetFileNameWithoutExtension(SelectedProjectFilePath)}_context.txt", - DefaultExt = ".txt" - }; - - if (!string.IsNullOrEmpty(_lastProjectPath) && Directory.Exists(_lastProjectPath)) - { - saveDialog.InitialDirectory = _lastProjectPath; - } - - bool? result = saveDialog.ShowDialog(); - - if (result == true) - { - IsProcessing = true; - ProgressText = "Генерация контекстного файла..."; - ProgressValue = 0; - - try - { - _cancellationTokenSource = new CancellationTokenSource(); - var progress = new Progress(value => - { - ProgressValue = value; - ProgressText = $"Генерация: {value}%"; - }); - - await GenerateContextFileAsync(selectedFiles, saveDialog.FileName, progress, _cancellationTokenSource.Token); - - MessageBox.Show($"Файл успешно создан:\n{saveDialog.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information); - } - catch (OperationCanceledException) - { - ProgressText = "Генерация отменена"; - } - catch (IOException ex) - { - MessageBox.Show($"Ошибка доступа к файлу: {ex.Message}\nПожалуйста, закройте файлы или предоставьте необходимые права доступа.", "Ошибка доступа", MessageBoxButton.OK, MessageBoxImage.Error); - } - catch (Exception ex) - { - MessageBox.Show($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error); - } - finally - { - IsProcessing = false; - } - } - } - - private List GetSelectedFiles(FileItem rootItem) - { - var selectedFiles = new List(); - CollectSelectedFiles(rootItem, selectedFiles); - return selectedFiles; - } - - private void CollectSelectedFiles(FileItem item, List selectedFiles) - { - if (item.IsDirectory) - { - foreach (var child in item.Children) - { - CollectSelectedFiles(child, selectedFiles); - } - } - else if (item.IsSelected == true) - { - selectedFiles.Add(item.FullName); - } - } - - private async Task GenerateContextFileAsync(List selectedFiles, string outputPath, IProgress progress, CancellationToken cancellationToken) - { - var outputContent = new StringBuilder(); - int totalFiles = selectedFiles.Count; - int processedFiles = 0; - - foreach (var filePath in selectedFiles) + foreach (var projectPath in solutionProjects) { cancellationToken.ThrowIfCancellationRequested(); - try + if (File.Exists(projectPath)) { - string projectDir = Path.GetDirectoryName(SelectedProjectFilePath); - string relativePath = Path.GetRelativePath(projectDir, filePath); - string fileContent = await File.ReadAllTextAsync(filePath, Encoding.UTF8); + string? projectDir = Path.GetDirectoryName(projectPath); + string? projectName = Path.GetFileName(projectDir); - // Обработка комментариев - fileContent = FileProcessorService.ProcessFileContent(fileContent, Path.GetFileName(filePath)); - fileContent = FileProcessorService.RemoveMultiLineComments(fileContent); + var projectItem = new FileItem + { + Name = projectName, + FullName = projectDir, + IsDirectory = true, + Parent = solutionItem, + IsSelected = false + }; - outputContent.AppendLine($"=== Файл: {relativePath} ==="); - outputContent.AppendLine(fileContent); - outputContent.AppendLine(); // Пустая строка между файлами + await ProjectScannerService.BuildDirectoryTreeAsync(projectDir, projectItem, progress, cancellationToken); - processedFiles++; - int progressValue = (int)((processedFiles * 100.0) / totalFiles); - progress?.Report(progressValue); - } - catch (IOException ex) - { - throw new IOException($"Файл '{Path.GetFileName(filePath)}' заблокирован или недоступен: {ex.Message}", ex); + if (projectItem.Children.Any()) + { + solutionItem.Children.Add(projectItem); + } } + + processedProjects++; + progress?.Report((int)((processedProjects * 100.0) / totalProjects)); } - // Сохраняем результат с кодировкой UTF-8 с BOM - var encoding = new UTF8Encoding(true); - await File.WriteAllTextAsync(outputPath, outputContent.ToString(), encoding); + RootDirectory = solutionItem; + + // После загрузки решения - не выбираем ничего по умолчанию + ClearAllSelections(solutionItem); } - - private void CancelProcessing(object parameter) + catch (Exception ex) { - _cancellationTokenSource?.Cancel(); - } - - private void LoadSettings() - { - try - { - _lastProjectPath = Properties.Settings.Default.LastProjectPath; - } - catch - { - _lastProjectPath = null; - } - } - - private void SaveSettings() - { - try - { - Properties.Settings.Default.LastProjectPath = _lastProjectPath; - Properties.Settings.Default.Save(); - } - catch - { - // Игнорируем ошибки сохранения настроек - } - } - - public event PropertyChangedEventHandler? PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) - { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + throw new Exception($"Ошибка при обработке решения: {ex.Message}", ex); } } - public class RelayCommand : ICommand + private static List ParseSolutionProjects(string solutionPath) { - private readonly Action _execute; - private readonly Func _canExecute; - - public event EventHandler? CanExecuteChanged + var projects = new List(); + try { - add => CommandManager.RequerySuggested += value; - remove => CommandManager.RequerySuggested -= value; - } + string? solutionDirectory = Path.GetDirectoryName(solutionPath); - public RelayCommand(Action execute, Func canExecute = null) + foreach (string line in File.ReadAllLines(solutionPath)) + { + if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase)) + { + var parts = line.Split(['"'], StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 3) + { + string relativePath = parts[2].Trim(); + string absolutePath = Path.GetFullPath(Path.Combine(solutionDirectory, relativePath)); + + if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath)) + { + projects.Add(absolutePath); + } + } + } + } + } + catch (Exception ex) { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; + throw new Exception($"Ошибка парсинга файла решения: {ex.Message}", ex); } - - public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); - public void Execute(object parameter) => _execute(parameter); + return projects; } -} \ No newline at end of file + + private async Task LoadProjectDirectoryAsync(string projectDirectory, IProgress progress, CancellationToken cancellationToken) + { + try + { + var projectName = Path.GetFileName(projectDirectory); + var rootItem = new FileItem + { + Name = projectName, + FullName = projectDirectory, + IsDirectory = true, + IsSelected = false + }; + + await ProjectScannerService.BuildDirectoryTreeAsync(projectDirectory, rootItem, progress, cancellationToken); + + RootDirectory = rootItem; + + // После загрузки проекта - не выбираем ничего по умолчанию + ClearAllSelections(rootItem); + } + catch (Exception ex) + { + throw new Exception($"Ошибка при загрузке проекта: {ex.Message}", ex); + } + } + + private static void ClearAllSelections(FileItem item) + { + item.IsSelected = false; + foreach (var child in item.Children) + { + ClearAllSelections(child); + } + } + + [RelayCommand(CanExecute = nameof(CanGenerate))] + private async Task GenerateFileAsync() + { + var selectedFiles = GetSelectedFiles(RootDirectory); + if (selectedFiles.Count == 0) + { + MessageBox.Show("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxButton.OK, MessageBoxImage.Warning); + return; + } + + var saveDialog = new SaveFileDialog + { + Title = "Сохранить контекстный файл", + Filter = "Текстовые файлы (*.txt)|*.txt|Все файлы (*.*)|*.*", + FileName = $"{Path.GetFileNameWithoutExtension(SelectedProjectFilePath)}_context.txt", + DefaultExt = ".txt", + InitialDirectory = LastProjectPath + }; + + if (saveDialog.ShowDialog() != true) return; + + IsProcessing = true; + try + { + _cancellationTokenSource = new CancellationTokenSource(); + var progress = new Progress(value => + { + ProgressValue = value; + ProgressText = $"Генерация: {value}%"; + }); + + await GenerateContextFileAsync(selectedFiles, saveDialog.FileName, progress, _cancellationTokenSource.Token); + MessageBox.Show($"Файл успешно создан:\n{saveDialog.FileName}", "Успех", MessageBoxButton.OK, MessageBoxImage.Information); + } + catch (OperationCanceledException) + { + ProgressText = "Генерация отменена"; + } + catch (IOException ex) + { + MessageBox.Show($"Ошибка доступа к файлу: {ex.Message}", "Ошибка доступа", MessageBoxButton.OK, MessageBoxImage.Error); + } + catch (Exception ex) + { + MessageBox.Show($"Ошибка при генерации файла: {ex.Message}", "Ошибка", MessageBoxButton.OK, MessageBoxImage.Error); + } + finally + { + IsProcessing = false; + } + } + + private List GetSelectedFiles(FileItem rootItem) + { + var selectedFiles = new List(); + CollectSelectedFiles(rootItem, selectedFiles); + return selectedFiles; + } + + private static void CollectSelectedFiles(FileItem item, List selectedFiles) + { + if (item.IsDirectory) + { + foreach (var child in item.Children) + { + CollectSelectedFiles(child, selectedFiles); + } + } + else if (item.IsSelected == true) + { + selectedFiles.Add(item.FullName); + } + } + + private async Task GenerateContextFileAsync(List selectedFiles, string outputPath, IProgress progress, CancellationToken cancellationToken) + { + var outputContent = new StringBuilder(); + int totalFiles = selectedFiles.Count; + int processedFiles = 0; + + foreach (var filePath in selectedFiles) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + string? projectDir = Path.GetDirectoryName(SelectedProjectFilePath); + string? relativePath = Path.GetRelativePath(projectDir, filePath); + string? fileContent = await File.ReadAllTextAsync(filePath, Encoding.UTF8); + + // Обработка комментариев + fileContent = FileProcessorService.ProcessFileContent(fileContent, Path.GetFileName(filePath)); + fileContent = FileProcessorService.RemoveMultiLineComments(fileContent); + + outputContent.AppendLine($"=== Файл: {relativePath} ==="); + outputContent.AppendLine(fileContent); + outputContent.AppendLine(); // Пустая строка между файлами + + processedFiles++; + int progressValue = (int)((processedFiles * 100.0) / totalFiles); + progress?.Report(progressValue); + } + catch (IOException ex) + { + throw new IOException($"Файл '{Path.GetFileName(filePath)}' заблокирован или недоступен: {ex.Message}", ex); + } + } + + // Сохраняем результат с кодировкой UTF-8 с BOM + var encoding = new UTF8Encoding(true); + await File.WriteAllTextAsync(outputPath, outputContent.ToString(), encoding); + } + + [RelayCommand] + private void CancelProcessing() + { + _cancellationTokenSource?.Cancel(); + } + + private void LoadSettings() + { + try + { + LastProjectPath = Properties.Settings.Default.LastProjectPath; + } + catch + { + LastProjectPath = string.Empty; + } + } + + private void SaveSettings() + { + try + { + Properties.Settings.Default.LastProjectPath = LastProjectPath; + Properties.Settings.Default.Save(); + } + catch { } + } + + /*public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + }*/ +} + +/* +public class RelayCommand : ICommand +{ + private readonly Action _execute; + private readonly Func _canExecute; + + public event EventHandler? CanExecuteChanged + { + add => CommandManager.RequerySuggested += value; + remove => CommandManager.RequerySuggested -= value; + } + + public RelayCommand(Action execute, Func canExecute = null) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + + public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter); + public void Execute(object parameter) => _execute(parameter); +}*/ \ No newline at end of file diff --git a/CodeContextGenerator/Views/MainWindow.xaml b/CodeContextGenerator/Views/MainWindow.xaml index 7883c80..cf64e66 100644 --- a/CodeContextGenerator/Views/MainWindow.xaml +++ b/CodeContextGenerator/Views/MainWindow.xaml @@ -1,68 +1,95 @@ - + - - - - - + + + + + - + - -