diff --git a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs index 1c2dadf..3faef64 100644 --- a/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs +++ b/src/SharpIDE.Application/Features/Analysis/RoslynAnalysis.cs @@ -127,7 +127,7 @@ public static class RoslynAnalysis return diagnostics; } - public static async Task> GetDocumentDiagnostics(SharpIdeFile fileModel) + public static async Task> GetDocumentDiagnostics(SharpIdeFile fileModel) { await _solutionLoadedTcs.Task; var cancellationToken = CancellationToken.None; @@ -141,7 +141,8 @@ public static class RoslynAnalysis var diagnostics = semanticModel.GetDiagnostics(cancellationToken: cancellationToken); 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> GetDocumentSyntaxHighlighting(SharpIdeFile fileModel) @@ -161,12 +162,14 @@ public static class RoslynAnalysis return result; } - public static async Task> GetCodeFixesAsync(Diagnostic diagnostic) + public static async Task> GetCodeFixesAsync(Diagnostic diagnostic) { var cancellationToken = CancellationToken.None; var document = _workspace!.CurrentSolution.GetDocument(diagnostic.Location.SourceTree); 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; } private static async Task> GetCodeFixesAsync(Document document, Diagnostic diagnostic) diff --git a/src/SharpIDE.Godot/IdeRoot.tscn b/src/SharpIDE.Godot/IdeRoot.tscn index b729058..b765e05 100644 --- a/src/SharpIDE.Godot/IdeRoot.tscn +++ b/src/SharpIDE.Godot/IdeRoot.tscn @@ -61,6 +61,11 @@ code_completion_enabled = true auto_brace_completion_enabled = true 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="."] unique_name_in_owner = true title = "Open a File" diff --git a/src/SharpIDE.Godot/InputStringNames.cs b/src/SharpIDE.Godot/InputStringNames.cs new file mode 100644 index 0000000..a9d5ea5 --- /dev/null +++ b/src/SharpIDE.Godot/InputStringNames.cs @@ -0,0 +1,8 @@ +using Godot; + +namespace SharpIDE.Godot; + +public static class InputStringNames +{ + public static readonly StringName CodeFixes = "CodeFixes"; +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/InputStringNames.cs.uid b/src/SharpIDE.Godot/InputStringNames.cs.uid new file mode 100644 index 0000000..eb467f9 --- /dev/null +++ b/src/SharpIDE.Godot/InputStringNames.cs.uid @@ -0,0 +1 @@ +uid://c8dler1dxpwda diff --git a/src/SharpIDE.Godot/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/SharpIdeCodeEdit.cs index 42323c0..32b4dd4 100644 --- a/src/SharpIDE.Godot/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/SharpIdeCodeEdit.cs @@ -1,8 +1,14 @@ +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Godot; +using Microsoft.Build.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Classification; +using Microsoft.CodeAnalysis.Text; +using SharpIDE.Application.Features.Analysis; +using Task = System.Threading.Tasks.Task; namespace SharpIDE.Godot; @@ -16,12 +22,17 @@ public partial class SharpIdeCodeEdit : CodeEdit private int _selectionEndCol; private CustomHighlighter _syntaxHighlighter = new(); + private PopupMenu _popupMenu = null!; - private ImmutableArray _diagnostics = []; + private ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> _diagnostics = []; public override void _Ready() { - //AddThemeFontOverride("Cascadia Code", ResourceLoader.Load("res://CascadiaCode.ttf")); + _popupMenu = GetNode("CodeFixesMenu"); + _popupMenu.IdPressed += (id) => + { + GD.Print($"Code fix selected: {id}"); + }; this.CodeCompletionRequested += OnCodeCompletionRequested; this.CodeFixesRequested += OnCodeFixesRequested; this.CaretChanged += () => @@ -67,14 +78,13 @@ public partial class SharpIdeCodeEdit : CodeEdit public override void _Draw() { UnderlineRange(_currentLine, _selectionStartCol, _selectionEndCol, new Color(1, 0, 0)); - foreach (var diagnostic in _diagnostics) + foreach (var (fileSpan, 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 line = fileSpan.StartLinePosition.Line; + var startCol = fileSpan.StartLinePosition.Character; + var endCol = fileSpan.EndLinePosition.Character; var color = diagnostic.Severity switch { DiagnosticSeverity.Error => new Color(1, 0, 0), @@ -86,7 +96,15 @@ public partial class SharpIdeCodeEdit : CodeEdit } } - public void ProvideDiagnostics(ImmutableArray diagnostics) + public override void _UnhandledKeyInput(InputEvent @event) + { + if (@event.IsActionPressed(InputStringNames.CodeFixes)) + { + EmitSignalCodeFixesRequested(); + } + } + + public void ProvideDiagnostics(ImmutableArray<(FileLinePositionSpan fileSpan, Diagnostic diagnostic)> diagnostics) { _diagnostics = diagnostics; } @@ -105,13 +123,50 @@ public partial class SharpIdeCodeEdit : CodeEdit 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() { - var caretColumn = GetCaretColumn(); - var caretLine = GetCaretLine(); + var (caretLine, caretColumn) = GetCaretPosition(); GD.Print($"Code completion requested at line {caretLine}, column {caretColumn}"); } + + private (int, int) GetCaretPosition() + { + var caretColumn = GetCaretColumn(); + var caretLine = GetCaretLine(); + return (caretLine, caretColumn); + } } \ No newline at end of file diff --git a/src/SharpIDE.Godot/project.godot b/src/SharpIDE.Godot/project.godot index 494dd18..8b0455f 100644 --- a/src/SharpIDE.Godot/project.godot +++ b/src/SharpIDE.Godot/project.godot @@ -20,3 +20,11 @@ config/icon="res://icon.svg" [dotnet] 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) +] +}