diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index 0c4f229..4b2e3c5 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -300,7 +300,7 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe _logger.LogInformation("RoslynAnalysis: Solution diagnostics updated in {ElapsedMilliseconds}ms", timer.ElapsedMilliseconds); } - public async Task> GetProjectDiagnostics(SharpIdeProjectModel projectModel, CancellationToken cancellationToken = default) + public async Task> GetProjectDiagnostics(SharpIdeProjectModel projectModel, CancellationToken cancellationToken = default) { using var _ = SharpIdeOtel.Source.StartActivity($"{nameof(RoslynAnalysis)}.{nameof(GetProjectDiagnostics)}"); await _solutionLoadedTcs.Task; @@ -308,8 +308,14 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe var compilation = await project.GetCompilationAsync(cancellationToken); Guard.Against.Null(compilation, nameof(compilation)); - var diagnostics = compilation.GetDiagnostics(cancellationToken); - diagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToImmutableArray(); + var diagnostics = compilation.GetDiagnostics(cancellationToken) + .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; } @@ -327,7 +333,11 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe var syntaxTree = compilation.SyntaxTrees.Single(s => s.FilePath == document.FilePath); var diagnostics = compilation.GetDiagnostics(cancellationToken) .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(); return diagnostics; } @@ -347,7 +357,13 @@ public class RoslynAnalysis(ILogger logger, BuildService buildSe var diagnostics = semanticModel.GetDiagnostics(cancellationToken: cancellationToken); 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; } diff --git a/src/SharpIDE.Application/Features/Analysis/SharpIdeDiagnostic.cs b/src/SharpIDE.Application/Features/Analysis/SharpIdeDiagnostic.cs index 3a2ef0b..a6d502c 100644 --- a/src/SharpIDE.Application/Features/Analysis/SharpIdeDiagnostic.cs +++ b/src/SharpIDE.Application/Features/Analysis/SharpIdeDiagnostic.cs @@ -3,4 +3,4 @@ using Microsoft.CodeAnalysis.Text; 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); diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs index 8bd7c02..f0d630f 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs @@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis; using System.Threading.Channels; using Microsoft.CodeAnalysis; using ObservableCollections; +using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Evaluation; using Project = Microsoft.Build.Evaluation.Project; @@ -138,5 +139,5 @@ public class SharpIdeProjectModel : ISharpIdeNode, IExpandableSharpIdeNode, IChi public event Func ProjectStoppedRunning = () => Task.CompletedTask; public void InvokeProjectStoppedRunning() => ProjectStoppedRunning.Invoke(); - public ObservableHashSet Diagnostics { get; internal set; } = []; + public ObservableHashSet Diagnostics { get; internal set; } = []; } diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index e337500..1ef31ba 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -1,4 +1,5 @@ using System.Collections.Immutable; +using System.Collections.Specialized; using Godot; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; @@ -7,6 +8,8 @@ using Microsoft.CodeAnalysis.Rename.ConflictEngine; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Tags; using Microsoft.CodeAnalysis.Text; +using ObservableCollections; +using R3; using Roslyn.Utilities; using SharpIDE.Application; using SharpIDE.Application.Features.Analysis; @@ -107,8 +110,6 @@ public partial class SharpIdeCodeEdit : CodeEdit await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await documentSyntaxHighlighting, await razorSyntaxHighlighting)); var documentDiagnostics = await _roslynAnalysis.GetDocumentDiagnostics(_currentFile, ct); await this.InvokeAsync(() => SetDiagnostics(documentDiagnostics)); - var projectDiagnostics = await _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile, ct); - await this.InvokeAsync(() => SetProjectDiagnostics(projectDiagnostics)); } public enum LineEditOrigin @@ -255,11 +256,20 @@ public partial class SharpIdeCodeEdit : CodeEdit var readFileTask = _openTabsFileManager.GetFileTextAsync(file); _currentFile.FileContentsChangedExternally.Subscribe(OnFileChangedExternally); _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 razorSyntaxHighlighting = _roslynAnalysis.GetRazorDocumentSyntaxHighlighting(_currentFile); var diagnostics = _roslynAnalysis.GetDocumentDiagnostics(_currentFile); - var projectDiagnosticsForFile = _roslynAnalysis.GetProjectDiagnosticsForFile(_currentFile); await readFileTask; var setTextTask = this.InvokeAsync(async () => { @@ -275,8 +285,6 @@ public partial class SharpIdeCodeEdit : CodeEdit await this.InvokeAsync(async () => SetSyntaxHighlightingModel(await syntaxHighlighting, await razorSyntaxHighlighting)); await diagnostics; await this.InvokeAsync(async () => SetDiagnostics(await diagnostics)); - await projectDiagnosticsForFile; - await this.InvokeAsync(async () => SetProjectDiagnostics(await projectDiagnosticsForFile)); }); } diff --git a/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs b/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs index baf3e8d..81cc649 100644 --- a/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs +++ b/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs @@ -79,14 +79,14 @@ public partial class ProblemsPanel : Control }); } - private async Task CreateDiagnosticTreeItem(Tree tree, TreeItem parent, ViewChangedEvent e) + private async Task CreateDiagnosticTreeItem(Tree tree, TreeItem parent, ViewChangedEvent e) { await this.InvokeAsync(() => { var diagItem = tree.CreateItem(parent); - diagItem.SetText(0, e.NewItem.Value.GetMessage()); - diagItem.SetMetadata(0, new RefCountedContainer(e.NewItem.Value)); - diagItem.SetIcon(0, e.NewItem.Value.Severity switch + diagItem.SetText(0, e.NewItem.Value.Diagnostic.GetMessage()); + diagItem.SetMetadata(0, new RefCountedContainer(e.NewItem.Value)); + diagItem.SetIcon(0, e.NewItem.Value.Diagnostic.Severity switch { DiagnosticSeverity.Error => ErrorIcon, DiagnosticSeverity.Warning => WarningIcon, @@ -107,13 +107,13 @@ public partial class ProblemsPanel : Control private void TreeOnItemActivated() { var selected = _tree.GetSelected(); - var diagnosticContainer = selected.GetMetadata(0).As?>(); + var diagnosticContainer = selected.GetMetadata(0).As?>(); if (diagnosticContainer is null) return; var diagnostic = diagnosticContainer.Item; var parentTreeItem = selected.GetParent(); var projectContainer = parentTreeItem.GetMetadata(0).As?>(); if (projectContainer is null) return; - OpenDocumentContainingDiagnostic(diagnostic); + OpenDocumentContainingDiagnostic(diagnostic.Diagnostic); } private void OpenDocumentContainingDiagnostic(Diagnostic diagnostic) diff --git a/src/SharpIDE.Photino/Components/Problems/ProjectProblemComponent.razor b/src/SharpIDE.Photino/Components/Problems/ProjectProblemComponent.razor index 0d07ccb..c88d459 100644 --- a/src/SharpIDE.Photino/Components/Problems/ProjectProblemComponent.razor +++ b/src/SharpIDE.Photino/Components/Problems/ProjectProblemComponent.razor @@ -11,11 +11,11 @@ @foreach (var diagnostic in _diagnostics) { - + - @diagnostic.GetMessage() - @diagnostic.Id + @diagnostic.Diagnostic.GetMessage() + @diagnostic.Diagnostic.Id @@ -30,7 +30,7 @@ [Parameter] public EventCallback OnFileSelected { get; set; } - private ImmutableArray _diagnostics = []; + private ImmutableArray _diagnostics = []; private static Color GetDiagnosticIconColour(Diagnostic diagnostic) => diagnostic.Severity switch {