From da2de0ed9bd459d010f0e28d594fae1d27d3750a Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:42:13 +1000 Subject: [PATCH] rename symbol --- .../Features/Analysis/IdeRenameService.cs | 19 +++++++ src/SharpIDE.Godot/DiAutoload.cs | 1 + .../Features/CodeEditor/RenameSymbolDialog.cs | 55 +++++++++++++++++++ .../CodeEditor/RenameSymbolDialog.cs.uid | 1 + .../CodeEditor/RenameSymbolDialog.tscn | 26 +++++++++ .../Features/CodeEditor/SharpIdeCodeEdit.cs | 8 ++- .../SharpIdeCodeEdit_RenameSymbol.cs | 37 +++++++++++++ .../SharpIdeCodeEdit_RenameSymbol.cs.uid | 1 + src/SharpIDE.Godot/InputStringNames.cs | 1 + 9 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 src/SharpIDE.Application/Features/Analysis/IdeRenameService.cs create mode 100644 src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs create mode 100644 src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs.uid create mode 100644 src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.tscn create mode 100644 src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs create mode 100644 src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs.uid diff --git a/src/SharpIDE.Application/Features/Analysis/IdeRenameService.cs b/src/SharpIDE.Application/Features/Analysis/IdeRenameService.cs new file mode 100644 index 0000000..d1d7440 --- /dev/null +++ b/src/SharpIDE.Application/Features/Analysis/IdeRenameService.cs @@ -0,0 +1,19 @@ +using Microsoft.CodeAnalysis; +using SharpIDE.Application.Features.FileWatching; + +namespace SharpIDE.Application.Features.Analysis; + +public class IdeRenameService(RoslynAnalysis roslynAnalysis, FileChangedService fileChangedService) +{ + private readonly RoslynAnalysis _roslynAnalysis = roslynAnalysis; + private readonly FileChangedService _fileChangedService = fileChangedService; + + public async Task ApplyRename(ISymbol symbol, string newName) + { + var affectedFiles = await _roslynAnalysis.GetRenameApplyChanges(symbol, newName); + foreach (var (affectedFile, updatedText) in affectedFiles) + { + await _fileChangedService.SharpIdeFileChanged(affectedFile, updatedText, FileChangeType.CodeActionChange); + } + } +} diff --git a/src/SharpIDE.Godot/DiAutoload.cs b/src/SharpIDE.Godot/DiAutoload.cs index 864fc61..1b12286 100644 --- a/src/SharpIDE.Godot/DiAutoload.cs +++ b/src/SharpIDE.Godot/DiAutoload.cs @@ -31,6 +31,7 @@ public partial class DiAutoload : Node services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs b/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs new file mode 100644 index 0000000..00cce4f --- /dev/null +++ b/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs @@ -0,0 +1,55 @@ +using Godot; + +namespace SharpIDE.Godot.Features.CodeEditor; + +public partial class RenameSymbolDialog : ConfirmationDialog +{ + private LineEdit _nameLineEdit = null!; + + public TaskCompletionSource RenameTaskCompletionSource { get; } = new(TaskCreationOptions.RunContinuationsAsynchronously); + public string SymbolName { get; set; } = string.Empty; + + private bool _isNameValid = true; + + public override void _Ready() + { + _nameLineEdit = GetNode("%SymbolNameLineEdit"); + _nameLineEdit.Text = SymbolName; + _nameLineEdit.GrabFocus(); + _nameLineEdit.SelectAll(); + _nameLineEdit.TextChanged += ValidateNewSymbolName; + Confirmed += OnConfirmed; + } + + public override void _ExitTree() + { + RenameTaskCompletionSource.TrySetResult(null); + } + + private void ValidateNewSymbolName(string newSymbolNameText) + { + _isNameValid = true; + var newSymbolName = newSymbolNameText.Trim(); + if (string.IsNullOrEmpty(newSymbolName)) + { + _isNameValid = false; + } + var textColour = _isNameValid ? new Color(1, 1, 1) : new Color(1, 0, 0); + _nameLineEdit.AddThemeColorOverride("font_color", textColour); + } + + public override void _Input(InputEvent @event) + { + if (@event is InputEventKey { Pressed: true, Keycode: Key.Enter }) + { + EmitSignalConfirmed(); + } + } + + private void OnConfirmed() + { + if (_isNameValid is false) return; + var newSymbolName = _nameLineEdit.Text.Trim(); + RenameTaskCompletionSource.SetResult(newSymbolName); + } +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs.uid b/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs.uid new file mode 100644 index 0000000..7322ece --- /dev/null +++ b/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.cs.uid @@ -0,0 +1 @@ +uid://c4ppo6t4kcegp diff --git a/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.tscn b/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.tscn new file mode 100644 index 0000000..750dd21 --- /dev/null +++ b/src/SharpIDE.Godot/Features/CodeEditor/RenameSymbolDialog.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=2 format=3 uid="uid://cfcgmyhahblw"] + +[ext_resource type="Script" uid="uid://c4ppo6t4kcegp" path="res://Features/CodeEditor/RenameSymbolDialog.cs" id="1_2s4mn"] + +[node name="RenameSymbolDialog" type="ConfirmationDialog"] +oversampling_override = 1.0 +title = "Rename: Symbol" +position = Vector2i(0, 36) +size = Vector2i(405, 115) +visible = true +script = ExtResource("1_2s4mn") + +[node name="VBoxContainer" type="VBoxContainer" parent="."] +offset_left = 8.0 +offset_top = 8.0 +offset_right = 397.0 +offset_bottom = 66.0 + +[node name="Label" type="Label" parent="VBoxContainer"] +layout_mode = 2 +text = "Enter new name:" + +[node name="SymbolNameLineEdit" type="LineEdit" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "ExistingSymbolName" diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs index 16fcb2f..baa77c3 100644 --- a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit.cs @@ -395,7 +395,11 @@ public partial class SharpIdeCodeEdit : CodeEdit } // Now we filter to only the focused tab if (HasFocus() is false) return; - if (@event.IsActionPressed(InputStringNames.CodeFixes)) + if (@event.IsActionPressed(InputStringNames.RenameSymbol)) + { + _ = Task.GodotRun(async () => await RenameSymbol()); + } + else if (@event.IsActionPressed(InputStringNames.CodeFixes)) { EmitSignalCodeFixesRequested(); } @@ -408,8 +412,6 @@ public partial class SharpIdeCodeEdit : CodeEdit } } - - private readonly Color _breakpointLineColor = new Color("3a2323"); private readonly Color _executingLineColor = new Color("665001"); public void SetLineColour(int line) diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs new file mode 100644 index 0000000..1a866c8 --- /dev/null +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs @@ -0,0 +1,37 @@ +using Godot; +using Microsoft.CodeAnalysis.Text; +using SharpIDE.Application.Features.Analysis; + +namespace SharpIDE.Godot.Features.CodeEditor; + +public partial class SharpIdeCodeEdit +{ + private readonly PackedScene _renameSymbolDialogScene = ResourceLoader.Load("uid://cfcgmyhahblw"); + [Inject] private readonly IdeRenameService _ideRenameService = null!; + public async Task RenameSymbol() + { + var cursorPosition = GetCaretPosition(); + var (roslynSymbol, linePositionSpan) = await _roslynAnalysis.LookupSymbol(_currentFile, new LinePosition(cursorPosition.line, cursorPosition.col)); + if (roslynSymbol is null || linePositionSpan is null) + { + GD.Print("No symbol found at cursor position for renaming."); + return; + } + + var renameSymbolDialog = _renameSymbolDialogScene.Instantiate(); + renameSymbolDialog.SymbolName = roslynSymbol.Name; + await this.InvokeAsync(() => + { + AddChild(renameSymbolDialog); + renameSymbolDialog.PopupCentered(); + }); + var newName = await renameSymbolDialog.RenameTaskCompletionSource.Task; + renameSymbolDialog.QueueFree(); + if (string.IsNullOrWhiteSpace(newName) || newName == roslynSymbol.Name) + { + GD.Print("Renaming cancelled or no change in name."); + return; + } + await _ideRenameService.ApplyRename(roslynSymbol, newName); + } +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs.uid b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs.uid new file mode 100644 index 0000000..74e0699 --- /dev/null +++ b/src/SharpIDE.Godot/Features/CodeEditor/SharpIdeCodeEdit_RenameSymbol.cs.uid @@ -0,0 +1 @@ +uid://bxtxw0soa1l5u diff --git a/src/SharpIDE.Godot/InputStringNames.cs b/src/SharpIDE.Godot/InputStringNames.cs index 18ef753..34d4701 100644 --- a/src/SharpIDE.Godot/InputStringNames.cs +++ b/src/SharpIDE.Godot/InputStringNames.cs @@ -4,6 +4,7 @@ namespace SharpIDE.Godot; public static class InputStringNames { + public static readonly StringName RenameSymbol = nameof(RenameSymbol); public static readonly StringName CodeFixes = "CodeFixes"; public static readonly StringName StepOver = "StepOver"; public static readonly StringName FindInFiles = nameof(FindInFiles);