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.Text;
using NuGet.Packaging;
using SharpIDE.Application.Features.SolutionDiscovery;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
namespace SharpIDE.Application.Features.Analysis;
@@ -125,6 +126,23 @@ public static class RoslynAnalysis
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)
{
var cancellationToken = CancellationToken.None;

View File

@@ -14,6 +14,17 @@ public interface IExpandableSharpIdeNode
public interface IChildSharpIdeNode
{
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

View File

@@ -1,12 +1,8 @@
using System;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.Loader;
using System.Linq;
using Godot;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Host.Mef;
using SharpIDE.Application.Features.Analysis;
using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence;
@@ -16,14 +12,18 @@ public partial class IdeRoot : Control
{
private Button _openSlnButton = null!;
private FileDialog _fileDialog = null!;
private SharpIdeCodeEdit _sharpIdeCodeEdit = null!;
public override void _Ready()
{
MSBuildLocator.RegisterDefaults();
_openSlnButton = GetNode<Button>("%OpenSlnButton");
_sharpIdeCodeEdit = GetNode<SharpIdeCodeEdit>("%SharpIdeCodeEdit");
_fileDialog = GetNode<FileDialog>("%OpenSolutionDialog");
_fileDialog.FileSelected += OnFileSelected;
_openSlnButton.Pressed += () => _fileDialog.Visible = true;
//_fileDialog.Visible = true;
OnFileSelected(@"C:\Users\Matthew\Documents\Git\BlazorCodeBreaker\BlazorCodeBreaker.slnx");
}
private async void OnFileSelected(string path)
@@ -33,6 +33,12 @@ public partial class IdeRoot : Control
GD.Print($"Selected: {path}");
var solutionModel = await VsPersistenceMapper.GetSolutionModel(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)
{

View File

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

View File

@@ -1,4 +1,6 @@
using System.Collections.Immutable;
using Godot;
using Microsoft.CodeAnalysis;
namespace SharpIDE.Godot;
@@ -7,15 +9,11 @@ public partial class SharpIdeCodeEdit : CodeEdit
[Signal]
public delegate void CodeFixesRequestedEventHandler();
[Export]
public int HighlightStartOffset = 0;
[Export]
public int HighlightEndOffset = 0;
private int _currentLine;
private int _selectionStartCol;
private int _selectionEndCol;
private ImmutableArray<Diagnostic> _diagnostics = [];
public override void _Ready()
{
@@ -44,18 +42,19 @@ public partial class SharpIdeCodeEdit : CodeEdit
int lineLength = GetLine(line).Length;
caretStartCol = Mathf.Clamp(caretStartCol, 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)
{
startPos.X -= 9; // Seems to be a bug or intended "feature" of GetPosAtLineColumn
startPos.X -= startRect.Size.X;
}
var endPos = GetPosAtLineColumn(line, caretEndCol);
startPos.X += charWidth;
endPos.X += charWidth;
var endPos = endRect.End;
startPos.Y -= 1;
endPos.Y -= 1;
DrawLine(startPos, endPos, color, thickness);
@@ -63,7 +62,28 @@ public partial class SharpIdeCodeEdit : CodeEdit
public override void _Draw()
{
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()