display underline for diagnostics

This commit is contained in:
Matt Parker
2025-08-18 18:20:15 +10:00
parent c0c240cf0e
commit ef5a4c4a64
5 changed files with 76 additions and 21 deletions

View File

@@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.MSBuild; using Microsoft.CodeAnalysis.MSBuild;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using NuGet.Packaging; using NuGet.Packaging;
using SharpIDE.Application.Features.SolutionDiscovery;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
namespace SharpIDE.Application.Features.Analysis; namespace SharpIDE.Application.Features.Analysis;
@@ -125,6 +126,23 @@ public static class RoslynAnalysis
return diagnostics; return diagnostics;
} }
public static async Task<ImmutableArray<Diagnostic>> GetDocumentDiagnostics(SharpIdeFile fileModel)
{
await _solutionLoadedTcs.Task;
var cancellationToken = CancellationToken.None;
var project = _workspace!.CurrentSolution.Projects.Single(s => s.FilePath == ((IChildSharpIdeNode)fileModel).GetNearestProjectNode()!.FilePath);
var document = project.Documents.Single(s => s.FilePath == fileModel.Path);
//var document = _workspace!.CurrentSolution.GetDocument(fileModel.Path);
Guard.Against.Null(document, nameof(document));
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
Guard.Against.Null(semanticModel, nameof(semanticModel));
var diagnostics = semanticModel.GetDiagnostics(cancellationToken: cancellationToken);
diagnostics = diagnostics.Where(d => d.Severity is not DiagnosticSeverity.Hidden).ToImmutableArray();
return diagnostics;
}
public static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Diagnostic diagnostic) public static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Diagnostic diagnostic)
{ {
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;

View File

@@ -14,6 +14,17 @@ public interface IExpandableSharpIdeNode
public interface IChildSharpIdeNode public interface IChildSharpIdeNode
{ {
public IExpandableSharpIdeNode Parent { get; set; } public IExpandableSharpIdeNode Parent { get; set; }
// TODO: Profile/redesign
public SharpIdeProjectModel? GetNearestProjectNode()
{
var current = this;
while (current is not SharpIdeProjectModel && current?.Parent is not null)
{
current = current.Parent as IChildSharpIdeNode;
}
return current as SharpIdeProjectModel;
}
} }
public class SharpIdeSolutionModel : ISharpIdeNode, IExpandableSharpIdeNode public class SharpIdeSolutionModel : ISharpIdeNode, IExpandableSharpIdeNode

View File

@@ -1,12 +1,8 @@
using System; using System;
using System.IO; using System.IO;
using System.Reflection; using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using Godot; using Godot;
using Microsoft.Build.Locator; using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Host.Mef;
using SharpIDE.Application.Features.Analysis; using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
@@ -16,14 +12,18 @@ public partial class IdeRoot : Control
{ {
private Button _openSlnButton = null!; private Button _openSlnButton = null!;
private FileDialog _fileDialog = null!; private FileDialog _fileDialog = null!;
private SharpIdeCodeEdit _sharpIdeCodeEdit = null!;
public override void _Ready() public override void _Ready()
{ {
MSBuildLocator.RegisterDefaults(); MSBuildLocator.RegisterDefaults();
_openSlnButton = GetNode<Button>("%OpenSlnButton"); _openSlnButton = GetNode<Button>("%OpenSlnButton");
_sharpIdeCodeEdit = GetNode<SharpIdeCodeEdit>("%SharpIdeCodeEdit");
_fileDialog = GetNode<FileDialog>("%OpenSolutionDialog"); _fileDialog = GetNode<FileDialog>("%OpenSolutionDialog");
_fileDialog.FileSelected += OnFileSelected; _fileDialog.FileSelected += OnFileSelected;
_openSlnButton.Pressed += () => _fileDialog.Visible = true;
//_fileDialog.Visible = true; //_fileDialog.Visible = true;
OnFileSelected(@"C:\Users\Matthew\Documents\Git\BlazorCodeBreaker\BlazorCodeBreaker.slnx");
} }
private async void OnFileSelected(string path) private async void OnFileSelected(string path)
@@ -33,6 +33,12 @@ public partial class IdeRoot : Control
GD.Print($"Selected: {path}"); GD.Print($"Selected: {path}");
var solutionModel = await VsPersistenceMapper.GetSolutionModel(path); var solutionModel = await VsPersistenceMapper.GetSolutionModel(path);
RoslynAnalysis.StartSolutionAnalysis(path); RoslynAnalysis.StartSolutionAnalysis(path);
var infraProject = solutionModel.AllProjects.Single(s => s.Name == "Infrastructure");
var diFile = infraProject.Files.Single(s => s.Name == "DependencyInjection.cs");
var fileContents = await File.ReadAllTextAsync(diFile.Path);
_sharpIdeCodeEdit.SetText(fileContents);
var diagnostics = await RoslynAnalysis.GetDocumentDiagnostics(diFile);
_sharpIdeCodeEdit.ProvideDiagnostics(diagnostics);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@@ -49,6 +49,7 @@ split_offset = 250
layout_mode = 2 layout_mode = 2
[node name="SharpIdeCodeEdit" type="CodeEdit" parent="VBoxContainer/HBoxContainer/HSplitContainer"] [node name="SharpIdeCodeEdit" type="CodeEdit" parent="VBoxContainer/HBoxContainer/HSplitContainer"]
unique_name_in_owner = true
layout_mode = 2 layout_mode = 2
theme_override_fonts/font = ExtResource("2_rk34b") theme_override_fonts/font = ExtResource("2_rk34b")
highlight_current_line = true highlight_current_line = true
@@ -56,7 +57,6 @@ gutters_draw_line_numbers = true
code_completion_enabled = true code_completion_enabled = true
auto_brace_completion_enabled = true auto_brace_completion_enabled = true
script = ExtResource("2_qjf5e") script = ExtResource("2_qjf5e")
HighlightStartOffset = 0
[node name="OpenSolutionDialog" type="FileDialog" parent="."] [node name="OpenSolutionDialog" type="FileDialog" parent="."]
unique_name_in_owner = true unique_name_in_owner = true

View File

@@ -1,4 +1,6 @@
using System.Collections.Immutable;
using Godot; using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot; namespace SharpIDE.Godot;
@@ -7,15 +9,11 @@ public partial class SharpIdeCodeEdit : CodeEdit
[Signal] [Signal]
public delegate void CodeFixesRequestedEventHandler(); public delegate void CodeFixesRequestedEventHandler();
[Export]
public int HighlightStartOffset = 0;
[Export]
public int HighlightEndOffset = 0;
private int _currentLine; private int _currentLine;
private int _selectionStartCol; private int _selectionStartCol;
private int _selectionEndCol; private int _selectionEndCol;
private ImmutableArray<Diagnostic> _diagnostics = [];
public override void _Ready() public override void _Ready()
{ {
@@ -44,18 +42,19 @@ public partial class SharpIdeCodeEdit : CodeEdit
int lineLength = GetLine(line).Length; int lineLength = GetLine(line).Length;
caretStartCol = Mathf.Clamp(caretStartCol, 0, lineLength); caretStartCol = Mathf.Clamp(caretStartCol, 0, lineLength);
caretEndCol = Mathf.Clamp(caretEndCol, 0, lineLength); caretEndCol = Mathf.Clamp(caretEndCol, 0, lineLength);
var charRect = GetRectAtLineColumn(line, caretEndCol);
var charWidth = charRect.Size.X;
var startPos = GetPosAtLineColumn(line, caretStartCol); // GetRectAtLineColumn returns the rectangle for the character before the column passed in, or the first character if the column is 0.
var startRect = GetRectAtLineColumn(line, caretStartCol);
var endRect = GetRectAtLineColumn(line, caretEndCol);
//DrawLine(startRect.Position, startRect.End, color);
//DrawLine(endRect.Position, endRect.End, color);
var startPos = startRect.End;
if (caretStartCol is 0) if (caretStartCol is 0)
{ {
startPos.X -= 9; // Seems to be a bug or intended "feature" of GetPosAtLineColumn startPos.X -= startRect.Size.X;
} }
var endPos = GetPosAtLineColumn(line, caretEndCol); var endPos = endRect.End;
startPos.X += charWidth;
endPos.X += charWidth;
startPos.Y -= 1; startPos.Y -= 1;
endPos.Y -= 1; endPos.Y -= 1;
DrawLine(startPos, endPos, color, thickness); DrawLine(startPos, endPos, color, thickness);
@@ -63,7 +62,28 @@ public partial class SharpIdeCodeEdit : CodeEdit
public override void _Draw() public override void _Draw()
{ {
UnderlineRange(_currentLine, _selectionStartCol, _selectionEndCol, new Color(1, 0, 0)); UnderlineRange(_currentLine, _selectionStartCol, _selectionEndCol, new Color(1, 0, 0));
//UnderlineRange(_currentLine, 0, 7, new Color(1, 0, 0)); foreach (var diagnostic in _diagnostics)
{
if (diagnostic.Location.IsInSource)
{
var mappedLineSpan = (diagnostic.Location.SourceTree?.GetMappedLineSpan(diagnostic.Location.SourceSpan))!.Value;
var line = mappedLineSpan.StartLinePosition.Line;
var startCol = mappedLineSpan.StartLinePosition.Character;
var endCol = mappedLineSpan.EndLinePosition.Character;
var color = diagnostic.Severity switch
{
DiagnosticSeverity.Error => new Color(1, 0, 0),
DiagnosticSeverity.Warning => new Color(1, 1, 0),
_ => new Color(0, 1, 0) // Info or other
};
UnderlineRange(line, startCol, endCol, color);
}
}
}
public void ProvideDiagnostics(ImmutableArray<Diagnostic> diagnostics)
{
_diagnostics = diagnostics;
} }
private void OnCodeFixesRequested() private void OnCodeFixesRequested()