Skip to content

Commit 0deb6b8

Browse files
author
LoneWandererProductions
committed
Improvements to the analysis UI and parallel processing
- Added the `SeveritySymbol` property in `Diagnostic.cs`, to display severity emojis based on `Severity`. - Added the `ProgressValue` property in `AnalyzerViewModel.cs` to display analysis progress in the UI. - Optimised `RunAnalyzerAsync` for asynchronous and parallel file analysis with progress updates. - Revised `HandleOpen` to open files using the system’s default application and to handle errors. - UI changes in `MainWindow.xaml`: - Added a progress bar. - Display of severity emojis in the diagnostics table. - Improved layout spacing for buttons. - Improvement to the `CanExecute` method in `AsyncDelegateCommand.cs`, to prevent concurrent command execution. - Revision of comments and XML documentation for better readability and consistency.
1 parent 5e54364 commit 0deb6b8

4 files changed

Lines changed: 117 additions & 33 deletions

File tree

Core.Apps/Diagnostic.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,20 @@ public Diagnostic(string name, DiagnosticSeverity severity, string filePath, int
8585
/// </value>
8686
public string Message { get; }
8787

88+
/// <summary>
89+
/// Gets the severity symbol.
90+
/// </summary>
91+
/// <value>
92+
/// The severity symbol.
93+
/// </value>
94+
public string SeveritySymbol => Severity switch
95+
{
96+
DiagnosticSeverity.Error => "\U0001F534", // 🔴
97+
DiagnosticSeverity.Warning => "\U0001F7E1", // 🟡
98+
DiagnosticSeverity.Info => "\U0001F535", // 🔵
99+
_ => "\u26AA" // ⚪
100+
};
101+
88102
/// <summary>
89103
/// Converts to string.
90104
/// </summary>

Core.Viewer/AnalyzerViewModel.cs

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ public sealed class AnalyzerViewModel : ViewModelBase
3434
/// </summary>
3535
private string _targetDirectory;
3636

37+
/// <summary>
38+
/// The progress value
39+
/// </summary>
40+
private double _progressValue;
41+
3742
/// <summary>
3843
/// The selected analyzer
3944
/// </summary>
@@ -80,6 +85,18 @@ public ICodeAnalyzer? SelectedAnalyzer
8085
set => SetProperty(ref _selectedAnalyzer, value);
8186
}
8287

88+
/// <summary>
89+
/// Gets or sets the progress value.
90+
/// </summary>
91+
/// <value>
92+
/// The progress value.
93+
/// </value>
94+
public double ProgressValue
95+
{
96+
get => _progressValue;
97+
set => SetProperty(ref _progressValue, value);
98+
}
99+
83100
/// <summary>
84101
/// Diagnostics to be displayed in the UI, filtered as needed.
85102
/// </summary>
@@ -129,38 +146,50 @@ private void LoadAnalyzers()
129146

130147
/// <summary>
131148
/// Runs analyzers on all C# files in the target directory.
132-
/// If <see cref="SelectedAnalyzer"/> is set, only that analyzer is run.
133-
/// Results are stored in <see cref="DiagnosticsView"/> and filtered according to <see cref="FilterText"/>.
149+
/// If <see cref="SelectedAnalyzer" /> is set, only that analyzer is run.
150+
/// Results are stored in <see cref="DiagnosticsView" /> and filtered according to <see cref="FilterText" />.
134151
/// </summary>
135152
private async Task RunAnalyzerAsync()
136153
{
137-
if (!Directory.Exists(TargetDirectory))
138-
return;
139-
140-
IEnumerable<ICodeAnalyzer> analyzersToRun = SelectedAnalyzer is not null
141-
? new[] { SelectedAnalyzer }
142-
: _analyzers;
154+
if (!Directory.Exists(TargetDirectory)) return;
143155

144156
_currentDiagnostics.Clear();
157+
var files = SafeEnumerateFiles(TargetDirectory, CoreResources.ResourceCsExtension).ToList();
158+
int totalFiles = files.Count;
159+
int processedFiles = 0;
145160

146-
// Run file enumeration and analysis on a background thread
147-
var files = await Task.Run(() => SafeEnumerateFiles(TargetDirectory, CoreResources.ResourceCsExtension));
161+
// Progress<T> captures the SynchronizationContext (the UI thread) automatically
162+
var progress = new Progress<double>(val => ProgressValue = val);
148163

149-
foreach (var file in files)
164+
// Call directly. Parallel.ForEachAsync is already async-aware.
165+
await Parallel.ForEachAsync(files, async (file, ct) =>
150166
{
151-
// Read file and analyze in background thread
152-
var content = await Task.Run(() => File.ReadAllText(file));
167+
var content = await File.ReadAllTextAsync(file);
168+
169+
// Determine the set of analyzers to run for THIS file
170+
var analyzersToRun = SelectedAnalyzer is not null
171+
? new[] { SelectedAnalyzer }
172+
: _analyzers;
153173

154174
foreach (var analyzer in analyzersToRun)
155175
{
156-
var diagnostics = await Task.Run(() => analyzer.Analyze(file, content));
157-
_currentDiagnostics.AddRange(diagnostics);
176+
var results = analyzer.Analyze(file, content);
177+
178+
lock (_currentDiagnostics)
179+
{
180+
_currentDiagnostics.AddRange(results);
181+
}
158182
}
159-
}
160183

161-
ApplyFilter(); // This can stay on UI thread
162-
}
184+
// Update progress
185+
var currentProcessed = Interlocked.Increment(ref processedFiles);
186+
((IProgress<double>)progress).Report((double)currentProcessed / totalFiles * 100);
187+
});
163188

189+
// Code here resumes on the UI thread automatically
190+
ApplyFilter();
191+
ProgressValue = 0;
192+
}
164193

165194
/// <summary>
166195
/// Safes the enumerate files.
@@ -263,14 +292,31 @@ private void HandleIgnore(string name)
263292
/// <param name="name">The name.</param>
264293
private void HandleOpen(string name)
265294
{
266-
// Minimal implementation: just show a message box with the file paths
267-
var files = DiagnosticsView
268-
.Where(d => d.Diagnostic.Name == name)
269-
.Select(d => d.Diagnostic.FilePath)
270-
.Distinct();
271-
272-
var message = string.Join(Environment.NewLine, files);
273-
System.Windows.MessageBox.Show(message, $"Open files for {name}");
295+
// Find all diagnostics for this specific name
296+
var diagnosticItems = DiagnosticsView.Where(d => d.Diagnostic.Name == name).ToList();
297+
298+
if (!diagnosticItems.Any()) return;
299+
300+
// For simplicity, if there are multiple, let's open the first one.
301+
// Or you could loop through them to open all.
302+
var target = diagnosticItems.First().Diagnostic;
303+
304+
try
305+
{
306+
// Many editors support: /file/path:line_number
307+
// e.g., "code -g C:\path\file.cs:15" or just passing the path
308+
var psi = new ProcessStartInfo
309+
{
310+
FileName = target.FilePath,
311+
UseShellExecute = true // Opens with the default system application
312+
};
313+
314+
Process.Start(psi);
315+
}
316+
catch (Exception ex)
317+
{
318+
System.Windows.MessageBox.Show($"Could not open file: {ex.Message}", "Error");
319+
}
274320
}
275321

276322
/// <summary>

Core.Viewer/MainWindow.xaml

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919
<Grid.RowDefinitions>
2020
<RowDefinition Height="Auto" />
2121
<RowDefinition Height="*" />
22+
<RowDefinition Height="20" />
2223
</Grid.RowDefinitions>
24+
<ProgressBar Value="{Binding ProgressValue}" Minimum="0" Maximum="100" Grid.Row="2" />
2325

2426
<!-- Analyzer selection and controls -->
25-
<StackPanel Orientation="Horizontal" Margin="5">
27+
<StackPanel Orientation="Horizontal" Margin="5,5,5,5" Grid.ColumnSpan="2">
2628
<ComboBox ItemsSource="{Binding Analyzers}"
2729
SelectedItem="{Binding SelectedAnalyzer}"
2830
Width="200" DisplayMemberPath="Name" />
@@ -33,20 +35,39 @@
3335
</StackPanel>
3436

3537
<!-- Diagnostics display -->
36-
<ListView Grid.Row="1" ItemsSource="{Binding DiagnosticsView}">
38+
<ListView Grid.Row="1" ItemsSource="{Binding DiagnosticsView}" Grid.ColumnSpan="2">
3739
<ListView.View>
3840
<GridView>
41+
<GridViewColumn Header="Status" Width="50">
42+
<GridViewColumn.CellTemplate>
43+
<DataTemplate>
44+
<TextBlock Text="{Binding Diagnostic.SeveritySymbol}"
45+
HorizontalAlignment="Center"
46+
VerticalAlignment="Center"
47+
FontFamily="Segoe UI Emoji"
48+
FontSize="14"/>
49+
</DataTemplate>
50+
</GridViewColumn.CellTemplate>
51+
</GridViewColumn>
52+
3953
<GridViewColumn Header="Analyzer" DisplayMemberBinding="{Binding Diagnostic.Name}" />
4054
<GridViewColumn Header="File" DisplayMemberBinding="{Binding Diagnostic.FilePath}" />
4155
<GridViewColumn Header="Message" DisplayMemberBinding="{Binding Diagnostic.Message}" />
42-
<GridViewColumn Header="Actions">
56+
57+
<GridViewColumn Header="Actions" Width="200">
4358
<GridViewColumn.CellTemplate>
4459
<DataTemplate>
4560
<StackPanel Orientation="Horizontal">
46-
<Button Content="Open File" Command="{Binding OpenFileCommand}" />
47-
<Button Content="Fix" Command="{Binding FixCommand}"
48-
Visibility="{Binding CanFix, Converter={StaticResource BoolToVisibility}}" />
49-
<Button Content="Ignore" Command="{Binding IgnoreCommand}" />
61+
<Button Content="Open File"
62+
Command="{Binding OpenFileCommand}"
63+
Margin="2"/>
64+
<Button Content="Fix"
65+
Command="{Binding FixCommand}"
66+
Visibility="{Binding CanFix, Converter={StaticResource BoolToVisibility}}"
67+
Margin="2"/>
68+
<Button Content="Ignore"
69+
Command="{Binding IgnoreCommand}"
70+
Margin="2"/>
5071
</StackPanel>
5172
</DataTemplate>
5273
</GridViewColumn.CellTemplate>

ViewModel/AsyncDelegateCommand.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public async void Execute(object? parameter)
9999
/// <returns>True if the command can execute, otherwise false.</returns>
100100
public bool CanExecute(object parameter)
101101
{
102+
// The command CANNOT execute if it is already running
103+
if (_isExecuting) return false;
104+
102105
return _canExecute?.Invoke((T)parameter) ?? true;
103106
}
104107

0 commit comments

Comments
 (0)