display code fixes popup

This commit is contained in:
Matt Parker
2025-08-18 19:23:45 +10:00
parent 71df8bc1cd
commit e4e2d95bf8
6 changed files with 95 additions and 15 deletions

View File

@@ -127,7 +127,7 @@ public static class RoslynAnalysis
return diagnostics; return diagnostics;
} }
public static async Task<ImmutableArray<Diagnostic>> GetDocumentDiagnostics(SharpIdeFile fileModel) public static async Task<ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)>> GetDocumentDiagnostics(SharpIdeFile fileModel)
{ {
await _solutionLoadedTcs.Task; await _solutionLoadedTcs.Task;
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
@@ -141,7 +141,8 @@ public static class RoslynAnalysis
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();
return diagnostics; var result = diagnostics.Select(d => (semanticModel.SyntaxTree.GetMappedLineSpan(d.Location.SourceSpan), d)).ToImmutableArray();
return result;
} }
public static async Task<IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)>> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel) public static async Task<IEnumerable<(FileLinePositionSpan fileSpan, ClassifiedSpan classifiedSpan)>> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel)
@@ -161,12 +162,14 @@ public static class RoslynAnalysis
return result; return result;
} }
public static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Diagnostic diagnostic) public static async Task<ImmutableArray<(FileLinePositionSpan fileSpan, CodeAction codeAction)>> GetCodeFixesAsync(Diagnostic diagnostic)
{ {
var cancellationToken = CancellationToken.None; var cancellationToken = CancellationToken.None;
var document = _workspace!.CurrentSolution.GetDocument(diagnostic.Location.SourceTree); var document = _workspace!.CurrentSolution.GetDocument(diagnostic.Location.SourceTree);
Guard.Against.Null(document, nameof(document)); Guard.Against.Null(document, nameof(document));
var result = await GetCodeFixesAsync(document, diagnostic); var codeActions = await GetCodeFixesAsync(document, diagnostic);
var result = codeActions.Select(action => (diagnostic.Location.SourceTree!.GetMappedLineSpan(diagnostic.Location.SourceSpan), action))
.ToImmutableArray();
return result; return result;
} }
private static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Document document, Diagnostic diagnostic) private static async Task<ImmutableArray<CodeAction>> GetCodeFixesAsync(Document document, Diagnostic diagnostic)

View File

@@ -61,6 +61,11 @@ code_completion_enabled = true
auto_brace_completion_enabled = true auto_brace_completion_enabled = true
script = ExtResource("2_qjf5e") script = ExtResource("2_qjf5e")
[node name="CodeFixesMenu" type="PopupMenu" parent="VBoxContainer/HBoxContainer/HSplitContainer/SharpIdeCodeEdit"]
item_count = 1
item_0/text = "Getting Context Actions..."
item_0/id = 0
[node name="OpenSolutionDialog" type="FileDialog" parent="."] [node name="OpenSolutionDialog" type="FileDialog" parent="."]
unique_name_in_owner = true unique_name_in_owner = true
title = "Open a File" title = "Open a File"

View File

@@ -0,0 +1,8 @@
using Godot;
namespace SharpIDE.Godot;
public static class InputStringNames
{
public static readonly StringName CodeFixes = "CodeFixes";
}

View File

@@ -0,0 +1 @@
uid://c8dler1dxpwda

View File

@@ -1,8 +1,14 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Linq;
using Godot; using Godot;
using Microsoft.Build.Utilities;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Text;
using SharpIDE.Application.Features.Analysis;
using Task = System.Threading.Tasks.Task;
namespace SharpIDE.Godot; namespace SharpIDE.Godot;
@@ -16,12 +22,17 @@ public partial class SharpIdeCodeEdit : CodeEdit
private int _selectionEndCol; private int _selectionEndCol;
private CustomHighlighter _syntaxHighlighter = new(); private CustomHighlighter _syntaxHighlighter = new();
private PopupMenu _popupMenu = null!;
private ImmutableArray<Diagnostic> _diagnostics = []; private ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> _diagnostics = [];
public override void _Ready() public override void _Ready()
{ {
//AddThemeFontOverride("Cascadia Code", ResourceLoader.Load<Font>("res://CascadiaCode.ttf")); _popupMenu = GetNode<PopupMenu>("CodeFixesMenu");
_popupMenu.IdPressed += (id) =>
{
GD.Print($"Code fix selected: {id}");
};
this.CodeCompletionRequested += OnCodeCompletionRequested; this.CodeCompletionRequested += OnCodeCompletionRequested;
this.CodeFixesRequested += OnCodeFixesRequested; this.CodeFixesRequested += OnCodeFixesRequested;
this.CaretChanged += () => this.CaretChanged += () =>
@@ -67,14 +78,13 @@ 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));
foreach (var diagnostic in _diagnostics) foreach (var (fileSpan, diagnostic) in _diagnostics)
{ {
if (diagnostic.Location.IsInSource) if (diagnostic.Location.IsInSource)
{ {
var mappedLineSpan = (diagnostic.Location.SourceTree?.GetMappedLineSpan(diagnostic.Location.SourceSpan))!.Value; var line = fileSpan.StartLinePosition.Line;
var line = mappedLineSpan.StartLinePosition.Line; var startCol = fileSpan.StartLinePosition.Character;
var startCol = mappedLineSpan.StartLinePosition.Character; var endCol = fileSpan.EndLinePosition.Character;
var endCol = mappedLineSpan.EndLinePosition.Character;
var color = diagnostic.Severity switch var color = diagnostic.Severity switch
{ {
DiagnosticSeverity.Error => new Color(1, 0, 0), DiagnosticSeverity.Error => new Color(1, 0, 0),
@@ -86,7 +96,15 @@ public partial class SharpIdeCodeEdit : CodeEdit
} }
} }
public void ProvideDiagnostics(ImmutableArray<Diagnostic> diagnostics) public override void _UnhandledKeyInput(InputEvent @event)
{
if (@event.IsActionPressed(InputStringNames.CodeFixes))
{
EmitSignalCodeFixesRequested();
}
}
public void ProvideDiagnostics(ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> diagnostics)
{ {
_diagnostics = diagnostics; _diagnostics = diagnostics;
} }
@@ -105,13 +123,50 @@ public partial class SharpIdeCodeEdit : CodeEdit
private void OnCodeFixesRequested() private void OnCodeFixesRequested()
{ {
GD.Print("Code fixes requested"); var (caretLine, caretColumn) = GetCaretPosition();
var test = GetCaretDrawPos();
_popupMenu.Position = new Vector2I((int)test.X, (int)test.Y);
_popupMenu.Clear();
_popupMenu.AddItem("Getting Context Actions...", 0);
_popupMenu.Popup();
GD.Print($"Code fixes requested at line {caretLine}, column {caretColumn}");
_ = Task.Run(async () =>
{
try
{
var linePos = new LinePosition(caretLine, caretColumn);
var diagnostic = _diagnostics.FirstOrDefault(d =>
d.fileSpan.StartLinePosition <= linePos && d.fileSpan.EndLinePosition >= linePos);
if (diagnostic is (_, null)) return;
var codeActions = await RoslynAnalysis.GetCodeFixesAsync(diagnostic.diagnostic);
Callable.From(() =>
{
_popupMenu.Clear();
foreach (var (index, (fileSpan, codeAction)) in codeActions.Index())
{
_popupMenu.AddItem(codeAction.Title, index);
//_popupMenu.SetItemMetadata(menuItem, codeAction);
}
GD.Print($"Code fixes found: {codeActions.Length}, displaying menu");
}).CallDeferred();
}
catch (Exception ex)
{
GD.Print(ex);
}
});
} }
private void OnCodeCompletionRequested() private void OnCodeCompletionRequested()
{ {
var caretColumn = GetCaretColumn(); var (caretLine, caretColumn) = GetCaretPosition();
var caretLine = GetCaretLine();
GD.Print($"Code completion requested at line {caretLine}, column {caretColumn}"); GD.Print($"Code completion requested at line {caretLine}, column {caretColumn}");
} }
private (int, int) GetCaretPosition()
{
var caretColumn = GetCaretColumn();
var caretLine = GetCaretLine();
return (caretLine, caretColumn);
}
} }

View File

@@ -20,3 +20,11 @@ config/icon="res://icon.svg"
[dotnet] [dotnet]
project/assembly_name="SharpIDE.Godot" project/assembly_name="SharpIDE.Godot"
[input]
CodeFixes={
"deadzone": 0.2,
"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":true,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":4194309,"key_label":0,"unicode":0,"location":0,"echo":false,"script":null)
]
}