receive rather than fetch project diagnostics for file

This commit is contained in:
Matt Parker
2025-11-23 13:43:32 +10:00
parent 71a833aa35
commit 29d1c10518
6 changed files with 47 additions and 22 deletions

View File

@@ -300,7 +300,7 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
_logger.LogInformation("RoslynAnalysis: Solution diagnostics updated in {ElapsedMilliseconds}ms", timer.ElapsedMilliseconds); _logger.LogInformation("RoslynAnalysis: Solution diagnostics updated in {ElapsedMilliseconds}ms", timer.ElapsedMilliseconds);
} }
public async Task<ImmutableArray<Diagnostic>> GetProjectDiagnostics(SharpIdeProjectModel projectModel, CancellationToken cancellationToken = default) public async Task<ImmutableArray<SharpIdeDiagnostic>> GetProjectDiagnostics(SharpIdeProjectModel projectModel, CancellationToken cancellationToken = default)
{ {
using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetProjectDiagnostics)}"); using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetProjectDiagnostics)}");
await _solutionLoadedTcs.Task; await _solutionLoadedTcs.Task;
@@ -308,8 +308,14 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
var compilation = await project.GetCompilationAsync(cancellationToken); var compilation = await project.GetCompilationAsync(cancellationToken);
Guard.Against.Null(compilation, nameof(compilation)); Guard.Against.Null(compilation, nameof(compilation));
var diagnostics = compilation.GetDiagnostics(cancellationToken); var diagnostics = compilation.GetDiagnostics(cancellationToken)
diagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToImmutableArray(); .Where(d => d.Severity is not DiagnosticSeverity.Hidden)
.Select(d =>
{
var mappedFileLinePositionSpan = d.Location.SourceTree!.GetMappedLineSpan(d.Location.SourceSpan);
return new SharpIdeDiagnostic(mappedFileLinePositionSpan.Span, d, mappedFileLinePositionSpan.Path);
})
.ToImmutableArray();
return diagnostics; return diagnostics;
} }
@@ -327,7 +333,11 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
var syntaxTree = compilation.SyntaxTrees.Single(s => s.FilePath == document.FilePath); var syntaxTree = compilation.SyntaxTrees.Single(s => s.FilePath == document.FilePath);
var diagnostics = compilation.GetDiagnostics(cancellationToken) var diagnostics = compilation.GetDiagnostics(cancellationToken)
.Where(d => d.Severity is not DiagnosticSeverity.Hidden && d.Location.SourceTree == syntaxTree) .Where(d => d.Severity is not DiagnosticSeverity.Hidden && d.Location.SourceTree == syntaxTree)
.Select(d => new SharpIdeDiagnostic(syntaxTree.GetMappedLineSpan(d.Location.SourceSpan).Span, d)) .Select(d =>
{
var mappedFileLinePositionSpan = d.Location.SourceTree!.GetMappedLineSpan(d.Location.SourceSpan);
return new SharpIdeDiagnostic(mappedFileLinePositionSpan.Span, d, mappedFileLinePositionSpan.Path);
})
.ToImmutableArray(); .ToImmutableArray();
return diagnostics; return diagnostics;
} }
@@ -347,7 +357,13 @@ public class RoslynAnalysis(ILogger<RoslynAnalysis> logger, BuildService buildSe
var diagnostics = semanticModel.GetDiagnostics(cancellationToken: cancellationToken); var diagnostics = semanticModel.GetDiagnostics(cancellationToken: cancellationToken);
diagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToImmutableArray(); diagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToImmutableArray();
var result = diagnostics.Select(d => new SharpIdeDiagnostic(semanticModel.SyntaxTree.GetMappedLineSpan(d.Location.SourceSpan).Span, d)).ToImmutableArray(); var result = diagnostics
.Select(d =>
{
var mappedFileLinePositionSpan = semanticModel.SyntaxTree.GetMappedLineSpan(d.Location.SourceSpan);
return new SharpIdeDiagnostic(mappedFileLinePositionSpan.Span, d, mappedFileLinePositionSpan.Path);
})
.ToImmutableArray();
return result; return result;
} }

View File

@@ -3,4 +3,4 @@ using Microsoft.CodeAnalysis.Text;
namespace SharpIDE.Application.Features.Analysis; namespace SharpIDE.Application.Features.Analysis;
public readonly record struct SharpIdeDiagnostic(LinePositionSpan Span, Diagnostic Diagnostic); public readonly record struct SharpIdeDiagnostic(LinePositionSpan Span, Diagnostic Diagnostic, string FilePath);

View File

@@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Threading.Channels; using System.Threading.Channels;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using ObservableCollections; using ObservableCollections;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.Evaluation; using SharpIDE.Application.Features.Evaluation;
using Project = Microsoft.Build.Evaluation.Project; using Project = Microsoft.Build.Evaluation.Project;
@@ -138,5 +139,5 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi
public event Func<Task> ProjectStoppedRunning = () => Task.CompletedTask; public event Func<Task> ProjectStoppedRunning = () => Task.CompletedTask;
public void InvokeProjectStoppedRunning() => ProjectStoppedRunning.Invoke(); public void InvokeProjectStoppedRunning() => ProjectStoppedRunning.Invoke();
public ObservableHashSet<Diagnostic> Diagnostics { get; internal set; } = []; public ObservableHashSet<SharpIdeDiagnostic> Diagnostics { get; internal set; } = [];
} }

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Collections.Specialized;
using Godot; using Godot;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeActions;
@@ -7,6 +8,8 @@ using Microsoft.CodeAnalysis.Rename.ConflictEngine;
using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Tags; using Microsoft.CodeAnalysis.Tags;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using ObservableCollections;
using R3;
using Roslyn.Utilities; using Roslyn.Utilities;
using SharpIDE.Application; using SharpIDE.Application;
using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Analysis;
@@ -107,8 +110,6 @@ public partial class SharpIdeCodeEdit : CodeEdit
await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await documentSyntaxHighlighting, await razorSyntaxHighlighting)); await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await documentSyntaxHighlighting, await razorSyntaxHighlighting));
var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile, ct); var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile, ct);
await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics)); await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics));
var projectDiagnostics = await _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile, ct);
await this.InvokeAsync(() => SetProjectDiagnostics(projectDiagnostics));
} }
public enum LineEditOrigin public enum LineEditOrigin
@@ -255,11 +256,20 @@ public partial class SharpIdeCodeEdit : CodeEdit
var readFileTask = _openTabsFileManager.GetFileTextAsync(file); var readFileTask = _openTabsFileManager.GetFileTextAsync(file);
_currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternally); _currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternally);
_currentFile.FileDeleted.Subscribe(OnFileDeleted); _currentFile.FileDeleted.Subscribe(OnFileDeleted);
var project = ((IChildSharpIdeNode)_currentFile).GetNearestProjectNode();
if (project is not null)
{
project.Diagnostics.ObserveChanged()
.SubscribeAwait(async (innerEvent, ct) =>
{
var projectDiagnosticsForFile = project.Diagnostics.Where(s => s.FilePath == _currentFile.Path).ToImmutableArray();
await this.InvokeAsync(() => SetProjectDiagnostics(projectDiagnosticsForFile));
});
}
var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile); var syntaxHighlighting = _roslynAnalysis.GetDocumentSyntaxHighlighting(_currentFile);
var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); var razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile);
var diagnostics = _roslynAnalysis.GetDocumentDiagnostics(_currentFile); var diagnostics = _roslynAnalysis.GetDocumentDiagnostics(_currentFile);
var projectDiagnosticsForFile = _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile);
await readFileTask; await readFileTask;
var setTextTask = this.InvokeAsync(async () => var setTextTask = this.InvokeAsync(async () =>
{ {
@@ -275,8 +285,6 @@ public partial class SharpIdeCodeEdit : CodeEdit
await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting)); await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting));
await diagnostics; await diagnostics;
await this.InvokeAsync(async () => SetDiagnostics(await diagnostics)); await this.InvokeAsync(async () => SetDiagnostics(await diagnostics));
await projectDiagnosticsForFile;
await this.InvokeAsync(async () => SetProjectDiagnostics(await projectDiagnosticsForFile));
}); });
} }

View File

@@ -79,14 +79,14 @@ public partial class ProblemsPanel : Control
}); });
} }
private async Task CreateDiagnosticTreeItem(Tree tree, TreeItem parent, ViewChangedEvent<Diagnostic, TreeItemContainer> e) private async Task CreateDiagnosticTreeItem(Tree tree, TreeItem parent, ViewChangedEvent<SharpIdeDiagnostic, TreeItemContainer> e)
{ {
await this.InvokeAsync(() => await this.InvokeAsync(() =>
{ {
var diagItem = tree.CreateItem(parent); var diagItem = tree.CreateItem(parent);
diagItem.SetText(0, e.NewItem.Value.GetMessage()); diagItem.SetText(0, e.NewItem.Value.Diagnostic.GetMessage());
diagItem.SetMetadata(0, new RefCountedContainer<Diagnostic>(e.NewItem.Value)); diagItem.SetMetadata(0, new RefCountedContainer<SharpIdeDiagnostic>(e.NewItem.Value));
diagItem.SetIcon(0, e.NewItem.Value.Severity switch diagItem.SetIcon(0, e.NewItem.Value.Diagnostic.Severity switch
{ {
DiagnosticSeverity.Error => ErrorIcon, DiagnosticSeverity.Error => ErrorIcon,
DiagnosticSeverity.Warning => WarningIcon, DiagnosticSeverity.Warning => WarningIcon,
@@ -107,13 +107,13 @@ public partial class ProblemsPanel : Control
private void TreeOnItemActivated() private void TreeOnItemActivated()
{ {
var selected = _tree.GetSelected(); var selected = _tree.GetSelected();
var diagnosticContainer = selected.GetMetadata(0).As<RefCountedContainer<Diagnostic>?>(); var diagnosticContainer = selected.GetMetadata(0).As<RefCountedContainer<SharpIdeDiagnostic>?>();
if (diagnosticContainer is null) return; if (diagnosticContainer is null) return;
var diagnostic = diagnosticContainer.Item; var diagnostic = diagnosticContainer.Item;
var parentTreeItem = selected.GetParent(); var parentTreeItem = selected.GetParent();
var projectContainer = parentTreeItem.GetMetadata(0).As<RefCountedContainer<SharpIdeProjectModel>?>(); var projectContainer = parentTreeItem.GetMetadata(0).As<RefCountedContainer<SharpIdeProjectModel>?>();
if (projectContainer is null) return; if (projectContainer is null) return;
OpenDocumentContainingDiagnostic(diagnostic); OpenDocumentContainingDiagnostic(diagnostic.Diagnostic);
} }
private void OpenDocumentContainingDiagnostic(Diagnostic diagnostic) private void OpenDocumentContainingDiagnostic(Diagnostic diagnostic)

View File

@@ -11,11 +11,11 @@
<MudTreeViewItem T="string" TextTypo="Typo.body2" EndTextTypo="Typo.caption" Expanded="false" Icon="@Icons.Material.Filled.Code" IconColor="Color.Success" Value="@ProjectModel.Name" Text="@ProjectModel.Name" EndText="@($"{_diagnostics.Length} diagnostics")"> <MudTreeViewItem T="string" TextTypo="Typo.body2" EndTextTypo="Typo.caption" Expanded="false" Icon="@Icons.Material.Filled.Code" IconColor="Color.Success" Value="@ProjectModel.Name" Text="@ProjectModel.Name" EndText="@($"{_diagnostics.Length} diagnostics")">
@foreach (var diagnostic in _diagnostics) @foreach (var diagnostic in _diagnostics)
{ {
<MudTreeViewItem T="string" TextTypo="Typo.body2" OnClick="@(async () => await OpenDocumentContainingDiagnostic(diagnostic))" Icon="@Icons.Material.Filled.Warning" IconColor="@GetDiagnosticIconColour(diagnostic)" Value="@diagnostic.ToString()"> <MudTreeViewItem T="string" TextTypo="Typo.body2" OnClick="@(async () => await OpenDocumentContainingDiagnostic(diagnostic.Diagnostic))" Icon="@Icons.Material.Filled.Warning" IconColor="@GetDiagnosticIconColour(diagnostic.Diagnostic)" Value="@diagnostic.ToString()">
<BodyContent> <BodyContent>
<MudText Typo="Typo.body2"> <MudText Typo="Typo.body2">
@diagnostic.GetMessage() @diagnostic.Diagnostic.GetMessage()
<MudText Typo="Typo.caption" Style="color: var(--mud-palette-gray-dark)">@diagnostic.Id</MudText> <MudText Typo="Typo.caption" Style="color: var(--mud-palette-gray-dark)">@diagnostic.Diagnostic.Id</MudText>
</MudText> </MudText>
</BodyContent> </BodyContent>
</MudTreeViewItem> </MudTreeViewItem>
@@ -30,7 +30,7 @@
[Parameter] [Parameter]
public EventCallback<SharpIdeFile> OnFileSelected { get; set; } public EventCallback<SharpIdeFile> OnFileSelected { get; set; }
private ImmutableArray<Diagnostic> _diagnostics = []; private ImmutableArray<SharpIdeDiagnostic> _diagnostics = [];
private static Color GetDiagnosticIconColour(Diagnostic diagnostic) => diagnostic.Severity switch private static Color GetDiagnosticIconColour(Diagnostic diagnostic) => diagnostic.Severity switch
{ {