Compare commits

..

4 Commits

Author SHA1 Message Date
b1d4f3693b move interfaces to separate folder 2025-12-10 22:32:48 +05:00
30256a86c2 iteration 2 2025-12-10 22:25:30 +05:00
5479da163e refactor mvvm, add datacontext 2025-12-10 21:33:06 +05:00
b6ddadc6d3 iteration 1 2025-12-10 18:59:27 +05:00
9 changed files with 301 additions and 453 deletions

View File

@@ -1,8 +1,8 @@
using CodeContextGenerator.Interfaces; using System.Windows;
using CodeContextGenerator.Interfaces;
using CodeContextGenerator.Services; using CodeContextGenerator.Services;
using CodeContextGenerator.ViewModels; using CodeContextGenerator.ViewModels;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using System.Windows;
namespace CodeContextGenerator; namespace CodeContextGenerator;

View File

@@ -1,26 +1,24 @@
using System.Globalization; using System;
using System.Globalization;
using System.Windows; using System.Windows;
using System.Windows.Data; using System.Windows.Data;
namespace CodeContextGenerator.Converters; namespace CodeContextGenerator.Converters
public class BooleanToVisibilityConverter : IValueConverter
{ {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) public class BooleanToVisibilityConverter : IValueConverter
{ {
bool boolValue = value is true; public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
// Проверяем параметр для инвертирования
if (parameter is string paramStr && paramStr.Equals("Inverted", StringComparison.OrdinalIgnoreCase))
{ {
boolValue = !boolValue; if (value is bool boolValue)
{
return boolValue ? Visibility.Visible : Visibility.Collapsed;
}
return Visibility.Collapsed;
} }
return boolValue ? Visibility.Visible : Visibility.Collapsed; public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
} {
throw new NotImplementedException();
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) }
{
throw new NotImplementedException();
} }
} }

View File

@@ -15,7 +15,6 @@ public partial class FileItem : ObservableObject
private bool isDirectory; private bool isDirectory;
[ObservableProperty] [ObservableProperty]
[NotifyPropertyChangedFor(nameof(HasSelectedChildren))]
private bool? isSelected; private bool? isSelected;
[ObservableProperty] [ObservableProperty]
@@ -23,11 +22,6 @@ public partial class FileItem : ObservableObject
public ObservableCollection<FileItem> Children { get; } = new ObservableCollection<FileItem>(); public ObservableCollection<FileItem> Children { get; } = new ObservableCollection<FileItem>();
// Событие, вызываемое при изменении состояния выбора
public event EventHandler SelectionChanged;
public bool HasSelectedChildren => Children.Any(c => c.IsSelected == true || c.HasSelectedChildren);
partial void OnIsSelectedChanged(bool? oldValue, bool? newValue) partial void OnIsSelectedChanged(bool? oldValue, bool? newValue)
{ {
if (newValue.HasValue) if (newValue.HasValue)
@@ -35,7 +29,6 @@ public partial class FileItem : ObservableObject
UpdateChildrenSelection(newValue.Value); UpdateChildrenSelection(newValue.Value);
} }
UpdateParentSelection(); UpdateParentSelection();
NotifySelectionChanged();
} }
private void UpdateChildrenSelection(bool value) private void UpdateChildrenSelection(bool value)
@@ -44,13 +37,7 @@ public partial class FileItem : ObservableObject
foreach (var child in Children) foreach (var child in Children)
{ {
// Гарантируем вызов события для каждого ребенка
var oldValue = child.IsSelected;
child.IsSelected = value; child.IsSelected = value;
if (oldValue != child.IsSelected)
{
child.RaiseSelectionChanged();
}
} }
} }
@@ -63,41 +50,7 @@ public partial class FileItem : ObservableObject
var noneSelected = children.All(c => c.IsSelected == false); var noneSelected = children.All(c => c.IsSelected == false);
var hasIndeterminate = children.Any(c => c.IsSelected == null); var hasIndeterminate = children.Any(c => c.IsSelected == null);
bool? newParentState = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null)); Parent.IsSelected = hasIndeterminate ? null : (allSelected ? true : (noneSelected ? false : null));
/*bool? newParentState; Parent.UpdateParentSelection();
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);
} }
} }

View File

@@ -9,7 +9,7 @@ public class FileScannerService : IFileScannerService
private static readonly string[] ExcludedDirectories = { private static readonly string[] ExcludedDirectories = {
"bin", "obj", ".git", "packages", ".vs", "Properties", "bin", "obj", ".git", "packages", ".vs", "Properties",
"node_modules", ".vscode", ".idea", "Debug", "Release", "node_modules", ".vscode", ".idea", "Debug", "Release",
"wwwroot", "dist", "build", ".gitignore", ".dockerignore" "wwwroot", "dist", "build", "node_modules"
}; };
private static readonly string[] IncludedExtensions = { ".cs", ".xaml" }; private static readonly string[] IncludedExtensions = { ".cs", ".xaml" };
@@ -25,13 +25,13 @@ public class FileScannerService : IFileScannerService
var directories = Directory.GetDirectories(path) var directories = Directory.GetDirectories(path)
.Where(d => !ExcludedDirectories.Any(ex => .Where(d => !ExcludedDirectories.Any(ex =>
d.EndsWith(ex, StringComparison.OrdinalIgnoreCase) || d.EndsWith(ex, System.StringComparison.OrdinalIgnoreCase) ||
Path.GetFileName(d).Equals(ex, StringComparison.OrdinalIgnoreCase))) Path.GetFileName(d).Equals(ex, System.StringComparison.OrdinalIgnoreCase)))
.ToList(); .ToList();
var files = Directory.GetFiles(path) var files = Directory.GetFiles(path)
.Where(f => IncludedExtensions.Any(ext => .Where(f => IncludedExtensions.Any(ext =>
f.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) f.EndsWith(ext, System.StringComparison.OrdinalIgnoreCase)))
.ToList(); .ToList();
int totalItems = directories.Count + files.Count; int totalItems = directories.Count + files.Count;
@@ -55,7 +55,7 @@ public class FileScannerService : IFileScannerService
await BuildDirectoryTreeAsync(dir, dirItem, progress, cancellationToken); await BuildDirectoryTreeAsync(dir, dirItem, progress, cancellationToken);
// Добавляем директорию только если в ней есть файлы или поддиректории с файлами // Добавляем директорию только если в ней есть файлы или поддиректории с файлами
if (dirItem.Children.Count > 0 || files.Count > 0) if (dirItem.Children.Any())
{ {
parentItem.Children.Add(dirItem); parentItem.Children.Add(dirItem);
} }

View File

@@ -1,7 +1,6 @@
using CodeContextGenerator.Interfaces; using CodeContextGenerator.Interfaces;
using CodeContextGenerator.Models; using CodeContextGenerator.Models;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
using System.Windows; using System.Windows;
namespace CodeContextGenerator.Services; namespace CodeContextGenerator.Services;
@@ -64,7 +63,7 @@ public class ProjectLoaderService : IProjectLoaderService
try try
{ {
var projectPaths = ParseSolutionProjects(solutionPath, solutionDir); var projectPaths = ParseSolutionProjects(solutionPath);
int totalProjects = projectPaths.Count; int totalProjects = projectPaths.Count;
int processedProjects = 0; int processedProjects = 0;
@@ -95,10 +94,7 @@ public class ProjectLoaderService : IProjectLoaderService
} }
processedProjects++; processedProjects++;
if (totalProjects > 0 && progress != null) progress?.Report((int)((processedProjects * 100.0) / totalProjects));
{
progress.Report((int)((processedProjects * 100.0) / totalProjects));
}
} }
} }
catch (Exception ex) catch (Exception ex)
@@ -109,36 +105,46 @@ public class ProjectLoaderService : IProjectLoaderService
return solutionItem; return solutionItem;
} }
// Улучшенный парсинг .sln файлов private async Task<FileItem> LoadCsprojAsync(string csprojPath, IProgress<int> progress, CancellationToken cancellationToken)
private List<string> ParseSolutionProjects(string solutionPath, string solutionDir) {
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<string> ParseSolutionProjects(string solutionPath)
{ {
var projects = new List<string>(); var projects = new List<string>();
var projectRegex = new Regex(@"Project\(""[^""]*""\)\s*=\s*""([^""]*)"",\s*""([^""]*)""", RegexOptions.IgnoreCase); string solutionDir = Path.GetDirectoryName(solutionPath);
try try
{ {
foreach (string line in File.ReadAllLines(solutionPath)) foreach (string line in File.ReadAllLines(solutionPath))
{ {
var match = projectRegex.Match(line); if (line.Trim().StartsWith("Project(", StringComparison.OrdinalIgnoreCase))
if (match.Success && match.Groups.Count >= 3)
{ {
string projectName = match.Groups[1].Value; var parts = line.Split(new[] { '"' }, StringSplitOptions.RemoveEmptyEntries);
string relativePath = match.Groups[2].Value; if (parts.Length >= 3)
// Пропускаем служебные проекты
if (projectName.Contains("Solution Items") ||
relativePath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase) ||
relativePath.EndsWith(".suo", StringComparison.OrdinalIgnoreCase))
{ {
continue; string relativePath = parts[2].Trim();
} if (relativePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase))
{
string absolutePath = Path.GetFullPath(Path.Combine(solutionDir, relativePath)); string absolutePath = Path.GetFullPath(Path.Combine(solutionDir, relativePath));
if (File.Exists(absolutePath))
// Проверяем, что это .csproj файл {
if (absolutePath.EndsWith(".csproj", StringComparison.OrdinalIgnoreCase) && File.Exists(absolutePath)) projects.Add(absolutePath);
{ }
projects.Add(absolutePath); }
} }
} }
} }
@@ -151,23 +157,6 @@ public class ProjectLoaderService : IProjectLoaderService
return projects; return projects;
} }
private async Task<FileItem> LoadCsprojAsync(string csprojPath, IProgress<int> 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) public string GetDefaultOutputFileName(string projectPath)
{ {
if (Directory.Exists(projectPath)) if (Directory.Exists(projectPath))

View File

@@ -7,32 +7,20 @@ namespace CodeContextGenerator.Services;
public class UIService : IUIService public class UIService : IUIService
{ {
private readonly ISettingsService _settingsService;
public UIService(ISettingsService settingsService)
{
_settingsService = settingsService;
}
public string ShowFolderBrowserDialog(string initialDirectory = null) public string ShowFolderBrowserDialog(string initialDirectory = null)
{ {
// Используем специальный трюк для выбора папки в WPF // Используем OpenFileDialog для выбора папки - это стандартный способ в WPF
var dialog = new OpenFileDialog var dialog = new OpenFileDialog
{ {
Title = "Выберите папку с проектом", Title = "Выберите папку с проектом",
Filter = "Папки|*.dummy", // Фильтр для отображения только папок Filter = "Папки|*.folder", // Фильтр для отображения только папок
FileName = "выберите_папку", // Специальное имя файла FileName = "select_folder", // Имя файла для обхода проверки существования файла
CheckFileExists = false, CheckFileExists = false,
CheckPathExists = true, CheckPathExists = true,
ValidateNames = false, ValidateNames = false, // Отключаем валидацию имен для выбора папок
DereferenceLinks = true DereferenceLinks = true
}; };
if (string.IsNullOrEmpty(initialDirectory) || !Directory.Exists(initialDirectory))
{
initialDirectory = _settingsService.GetLastProjectPath();
}
if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory)) if (!string.IsNullOrEmpty(initialDirectory) && Directory.Exists(initialDirectory))
{ {
dialog.InitialDirectory = initialDirectory; dialog.InitialDirectory = initialDirectory;
@@ -41,9 +29,8 @@ public class UIService : IUIService
var result = dialog.ShowDialog(); var result = dialog.ShowDialog();
if (result == true) if (result == true)
{ {
// Возвращаем директорию, а не путь к файлу // Возвращаем папку, а не файл
string selectedFolder = Path.GetDirectoryName(dialog.FileName); return Path.GetDirectoryName(dialog.FileName);
return selectedFolder;
} }
return null; return null;
@@ -55,17 +42,11 @@ public class UIService : IUIService
var dialog = new OpenFileDialog var dialog = new OpenFileDialog
{ {
Title = "Выберите файл решения или проекта", Title = "Выберите файл решения, проекта или папку",
Filter = "Все поддерживаемые файлы (*.sln;*.csproj)|*.sln;*.csproj|Файлы решений Visual Studio (*.sln)|*.sln|Файлы проектов C# (*.csproj)|*.csproj|Все файлы (*.*)|*.*", Filter = "Все поддерживаемые файлы (*.sln;*.csproj;*.razor)|*.sln;*.csproj;*.razor|Файлы решений (*.sln)|*.sln|Файлы проектов (*.csproj)|*.csproj|Файлы Razor (*.razor)|*.razor|Все файлы (*.*)|*.*",
Multiselect = false Multiselect = false
}; };
var lastPath = _settingsService.GetLastProjectPath();
if (!string.IsNullOrEmpty(lastPath) && Directory.Exists(lastPath))
{
dialog.InitialDirectory = lastPath;
}
bool? result = dialog.ShowDialog(); bool? result = dialog.ShowDialog();
if (result == true) if (result == true)
{ {

View File

@@ -1,282 +1,208 @@
using CodeContextGenerator.Interfaces; using CodeContextGenerator.Interfaces;
using CodeContextGenerator.Models; using CodeContextGenerator.Models;
using CodeContextGenerator.Services;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Input;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows; using System.Windows;
namespace CodeContextGenerator.ViewModels namespace CodeContextGenerator.ViewModels;
public partial class MainViewModel : ObservableObject
{ {
public partial class MainViewModel : ObservableObject 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)
{ {
private readonly IProjectLoaderService _projectLoaderService; _projectLoaderService = projectLoaderService;
private readonly IFileScannerService _fileScannerService; _fileScannerService = fileScannerService;
private readonly IContextFileGenerator _contextFileGenerator; _contextFileGenerator = contextFileGenerator;
private readonly IUIService _uiService; _uiService = uiService;
private readonly ISettingsService _settingsService; _settingsService = settingsService;
}
private CancellationTokenSource _cancellationTokenSource; public bool CanSelectProject => !IsProcessing;
public bool CanGenerate => !IsProcessing && IsProjectLoaded && HasSelectedFiles(RootDirectory);
[ObservableProperty] [RelayCommand(CanExecute = nameof(CanSelectProject))]
private FileItem rootDirectory; private void SelectProject()
{
var initialDir = _settingsService.GetLastProjectPath();
[ObservableProperty] // Сначала пробуем выбрать файл проекта/решения
private string selectedProjectPath; if (_uiService.ShowOpenProjectFileDialog(out var filePath))
[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)
{ {
_projectLoaderService = projectLoaderService; LoadProject(filePath);
_fileScannerService = fileScannerService; return;
_contextFileGenerator = contextFileGenerator;
_uiService = uiService;
_settingsService = settingsService;
// Явная инициализация команд
SelectProjectCommand = new RelayCommand(SelectProject, CanSelectProject);
GenerateContextFileCommand = new AsyncRelayCommand(GenerateContextFileAsync, CanGenerate);
CancelProcessingCommand = new RelayCommand(CancelProcessing);
ExitApplicationCommand = new RelayCommand(ExitApplication);
} }
private bool CanSelectProject() => !IsProcessing; // Если отменили выбор файла - предлагаем выбрать папку
var folderPath = _uiService.ShowFolderBrowserDialog(initialDir);
private bool CanGenerate() if (!string.IsNullOrEmpty(folderPath))
{ {
bool result = !IsProcessing && LoadProject(folderPath);
IsProjectLoaded &&
RootDirectory != null &&
HasSelectedFiles(RootDirectory);
// Отладочная информация
System.Diagnostics.Debug.WriteLine($"CanGenerate: {result}, IsProcessing: {IsProcessing}, IsProjectLoaded: {IsProjectLoaded}, RootDirectory: {(RootDirectory != null)}, HasSelectedFiles: {HasSelectedFiles(RootDirectory)}");
return result;
}
private void SelectProject()
{
var initialDir = !string.IsNullOrEmpty(SelectedProjectPath) && Directory.Exists(SelectedProjectPath)
? Path.GetDirectoryName(SelectedProjectPath)
: _settingsService.GetLastProjectPath();
if (_uiService.ShowOpenProjectFileDialog(out var filePath))
{
LoadProject(filePath);
return;
}
var folderPath = _uiService.ShowFolderBrowserDialog(initialDir);
if (!string.IsNullOrEmpty(folderPath))
{
LoadProject(folderPath);
}
}
private async Task GenerateContextFileAsync()
{
var selectedFiles = _fileScannerService.GetSelectedFiles(RootDirectory);
if (selectedFiles.Count == 0)
{
_uiService.ShowMessage("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxImage.Warning);
return;
}
var defaultFileName = _projectLoaderService.GetDefaultOutputFileName(SelectedProjectPath);
var initialDir = _settingsService.GetLastProjectPath() ?? Path.GetDirectoryName(SelectedProjectPath);
if (!_uiService.ShowSaveFileDialog(defaultFileName, initialDir, out var savePath))
return;
IsProcessing = true;
ProgressText = "Генерация контекстного файла...";
ProgressValue = 0;
try
{
_cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<int>(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<int>(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();
} }
} }
[RelayCommand(CanExecute = nameof(CanGenerate))]
private async Task GenerateContextFileAsync()
{
var selectedFiles = _fileScannerService.GetSelectedFiles(RootDirectory);
if (selectedFiles.Count == 0)
{
_uiService.ShowMessage("Пожалуйста, выберите хотя бы один файл для обработки.", "Предупреждение", MessageBoxImage.Warning);
return;
}
var defaultFileName = _projectLoaderService.GetDefaultOutputFileName(SelectedProjectPath);
var initialDir = _settingsService.GetLastProjectPath();
if (!_uiService.ShowSaveFileDialog(defaultFileName, initialDir, out var savePath))
return;
IsProcessing = true;
ProgressText = "Генерация контекстного файла...";
ProgressValue = 0;
try
{
_cancellationTokenSource = new CancellationTokenSource();
var progress = new Progress<int>(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;
}
}
[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<int>(value =>
{
ProgressValue = value;
ProgressText = $"Загрузка: {value}%";
});
_cancellationTokenSource = new CancellationTokenSource();
RootDirectory = await _projectLoaderService.LoadProjectFromPathAsync(projectPath, progress, _cancellationTokenSource.Token);
IsProjectLoaded = true;
ProgressText = "Проект загружен успешно";
// Сбрасываем выделение после загрузки
ClearSelections(RootDirectory);
}
catch (OperationCanceledException)
{
ProgressText = "Загрузка отменена";
}
catch (System.Exception ex)
{
_uiService.ShowMessage($"Ошибка при загрузке проекта: {ex.Message}", "Ошибка", MessageBoxImage.Error);
}
finally
{
IsProcessing = false;
}
}
private void ClearSelections(FileItem item)
{
item.IsSelected = false;
foreach (var child in item.Children)
{
ClearSelections(child);
}
}
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;
}
return false;
}
} }

View File

@@ -59,51 +59,50 @@
TextWrapping="Wrap" /> TextWrapping="Wrap" />
</StackPanel> </StackPanel>
<!-- Контейнер для дерева и сообщения --> <Border
<Grid Grid.Row="2"> Grid.Row="2"
<Border Padding="5"
Padding="5" BorderBrush="Gray"
BorderBrush="Gray" BorderThickness="1"
BorderThickness="1" CornerRadius="4"
CornerRadius="4" Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}">
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}"> <ScrollViewer
<ScrollViewer HorizontalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel"
PreviewMouseWheel="ScrollViewer_PreviewMouseWheel" VerticalScrollBarVisibility="Auto">
VerticalScrollBarVisibility="Auto"> <TreeView
<TreeView x:Name="ProjectTree"
x:Name="ProjectTree" ItemsSource="{Binding RootDirectory.Children}"
ItemsSource="{Binding RootDirectory.Children}" VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling">
VirtualizingPanel.VirtualizationMode="Recycling"> <TreeView.ItemTemplate>
<TreeView.ItemTemplate> <HierarchicalDataTemplate ItemsSource="{Binding Children}">
<HierarchicalDataTemplate ItemsSource="{Binding Children}"> <StackPanel MinHeight="24" Orientation="Horizontal">
<StackPanel MinHeight="24" Orientation="Horizontal"> <CheckBox
<CheckBox Margin="2,0,5,0"
Margin="2,0,5,0" VerticalAlignment="Center"
VerticalAlignment="Center" Click="CheckBox_Click"
Click="CheckBox_Click" IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}"
IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged}" IsThreeState="True" />
IsThreeState="True" /> <TextBlock
<TextBlock VerticalAlignment="Center"
VerticalAlignment="Center" Text="{Binding Name}"
Text="{Binding Name}" ToolTip="{Binding FullName}" />
ToolTip="{Binding FullName}" /> </StackPanel>
</StackPanel> </HierarchicalDataTemplate>
</HierarchicalDataTemplate> </TreeView.ItemTemplate>
</TreeView.ItemTemplate> </TreeView>
</TreeView> </ScrollViewer>
</ScrollViewer> </Border>
</Border>
<TextBlock <TextBlock
HorizontalAlignment="Center" Grid.Row="2"
VerticalAlignment="Center" HorizontalAlignment="Center"
FontSize="14" VerticalAlignment="Center"
Foreground="Gray" FontSize="14"
Text="Проект еще не загружен. Выберите файл решения или проекта." Foreground="Gray"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Inverted}" /> Text="Проект еще не загружен. Выберите файл решения или проекта."
</Grid> Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=Collapsed}" />
<ProgressBar <ProgressBar
Grid.Row="3" Grid.Row="3"
@@ -131,12 +130,14 @@
Content="Отмена" Content="Отмена"
Foreground="White" Foreground="White"
Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}" /> Visibility="{Binding IsProcessing, Converter={StaticResource BooleanToVisibilityConverter}}" />
<Button Command="{Binding ExitApplicationCommand}" Content="В главное меню" /> <Button Command="{Binding ExitApplicationCommand}" Content="Закрыть" />
<Button <Button
Background="#FF28A745" Background="#FF28A745"
Command="{Binding GenerateContextFileCommand}" Command="{Binding GenerateContextFileCommand}"
Content="Сформировать" Content="Сформировать"
Foreground="White" /> Foreground="White"
IsEnabled="{Binding CanGenerate}"
Visibility="{Binding IsProjectLoaded, Converter={StaticResource BooleanToVisibilityConverter}}" />
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </Window>

View File

@@ -1,3 +1,3 @@
# CodeContextGenerator # CodeContextGenerator
Генератор контекста для нейросети из проекта Visual Studio / VS Code Генератор контекста из проекта для нейросети