From 23ded1e6dd673019a45690b61efb047272184144 Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:22:03 +1000 Subject: [PATCH] rename file from IDE --- .../FileWatching/IdeFileOperationsService.cs | 10 +++ .../ContextMenus/Dialogs/RenameFileDialog.cs | 66 +++++++++++++++++++ .../Dialogs/RenameFileDialog.cs.uid | 1 + .../Dialogs/RenameFileDialog.tscn | 26 ++++++++ .../ContextMenus/FileContextMenu.cs | 13 +++- 5 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs create mode 100644 src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs.uid create mode 100644 src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.tscn diff --git a/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs b/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs index 1f757e4..cef349f 100644 --- a/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs +++ b/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs @@ -87,6 +87,16 @@ public class IdeFileOperationsService(SharpIdeSolutionModificationService sharpI return sharpIdeFile; } + public async Task RenameFile(SharpIdeFile file, string newFileName) + { + var parentPath = Path.GetDirectoryName(file.Path)!; + var newFilePath = Path.Combine(parentPath, newFileName); + if (File.Exists(newFilePath)) throw new InvalidOperationException($"File {newFilePath} already exists."); + File.Move(file.Path, newFilePath); + var sharpIdeFile = await _sharpIdeSolutionModificationService.RenameFile(file, newFileName); + return sharpIdeFile; + } + public async Task MoveFile(IFolderOrProject destinationParentNode, SharpIdeFile fileToMove) { var newFilePath = Path.Combine(destinationParentNode.ChildNodeBasePath, fileToMove.Name); diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs new file mode 100644 index 0000000..ddc2615 --- /dev/null +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs @@ -0,0 +1,66 @@ +using Godot; +using SharpIDE.Application.Features.FileWatching; +using SharpIDE.Application.Features.SolutionDiscovery; + +namespace SharpIDE.Godot.Features.SolutionExplorer.ContextMenus.Dialogs; + +public partial class RenameFileDialog : ConfirmationDialog +{ + private LineEdit _nameLineEdit = null!; + + public SharpIdeFile File { get; set; } = null!; + + [Inject] private readonly IdeFileOperationsService _ideFileOperationsService = null!; + + private bool _isNameValid = true; + private string _fileParentPath = null!; + + public override void _Ready() + { + _fileParentPath = Path.GetDirectoryName(File.Path)!; + _nameLineEdit = GetNode("%FileNameLineEdit"); + _nameLineEdit.Text = File.Name; + _nameLineEdit.GrabFocus(); + // select the name without the extension + _nameLineEdit.Select(0, File.Name.LastIndexOf('.')); + _nameLineEdit.TextChanged += ValidateNewFileName; + Confirmed += OnConfirmed; + } + + private void ValidateNewFileName(string newFileNameText) + { + _isNameValid = true; + var newFileName = newFileNameText.Trim(); + if (string.IsNullOrEmpty(newFileName) || System.IO.File.Exists(Path.Combine(_fileParentPath, newFileName))) + { + _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 fileName = _nameLineEdit.Text.Trim(); + if (string.IsNullOrEmpty(fileName)) + { + GD.PrintErr("File name cannot be empty."); + return; + } + + _ = Task.GodotRun(async () => + { + await _ideFileOperationsService.RenameFile(File, fileName); + }); + QueueFree(); + } +} \ No newline at end of file diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs.uid b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs.uid new file mode 100644 index 0000000..90657e0 --- /dev/null +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs.uid @@ -0,0 +1 @@ +uid://dq72hpd4r54w4 diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.tscn b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.tscn new file mode 100644 index 0000000..76cac63 --- /dev/null +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.tscn @@ -0,0 +1,26 @@ +[gd_scene load_steps=2 format=3 uid="uid://b775b5j4rkxxw"] + +[ext_resource type="Script" uid="uid://dq72hpd4r54w4" path="res://Features/SolutionExplorer/ContextMenus/Dialogs/RenameFileDialog.cs" id="1_0pttq"] + +[node name="RenameFileDialog" type="ConfirmationDialog"] +oversampling_override = 1.0 +title = "Rename: Directory" +position = Vector2i(0, 36) +size = Vector2i(405, 115) +visible = true +script = ExtResource("1_0pttq") + +[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 name:" + +[node name="FileNameLineEdit" type="LineEdit" parent="VBoxContainer"] +unique_name_in_owner = true +layout_mode = 2 +text = "ExistingDirectoryName" diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/FileContextMenu.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/FileContextMenu.cs index 190a104..b0450ec 100644 --- a/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/FileContextMenu.cs +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/ContextMenus/FileContextMenu.cs @@ -1,6 +1,7 @@ using Godot; using SharpIDE.Application.Features.SolutionDiscovery; using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; +using SharpIDE.Godot.Features.SolutionExplorer.ContextMenus.Dialogs; namespace SharpIDE.Godot.Features.SolutionExplorer; @@ -9,11 +10,13 @@ file enum FileContextMenuOptions Open = 0, RevealInFileExplorer = 1, CopyFullPath = 2, - Delete = 3 + Rename = 3, + Delete = 4 } public partial class SolutionExplorerPanel { + private readonly PackedScene _renameFileDialogScene = GD.Load("uid://b775b5j4rkxxw"); private void OpenContextMenuFile(SharpIdeFile file) { var menu = new PopupMenu(); @@ -23,6 +26,7 @@ public partial class SolutionExplorerPanel menu.AddSeparator(); menu.AddItem("Copy Full Path", (int)FileContextMenuOptions.CopyFullPath); menu.AddSeparator(); + menu.AddItem("Rename", (int)FileContextMenuOptions.Rename); menu.AddItem("Delete", (int)FileContextMenuOptions.Delete); if (file.Parent is SharpIdeSolutionFolder) menu.SetItemDisabled((int)FileContextMenuOptions.Delete, true); menu.PopupHide += () => menu.QueueFree(); @@ -41,6 +45,13 @@ public partial class SolutionExplorerPanel { DisplayServer.ClipboardSet(file.Path); } + else if (actionId is FileContextMenuOptions.Rename) + { + var renameFileDialog = _renameFileDialogScene.Instantiate(); + renameFileDialog.File = file; + AddChild(renameFileDialog); + renameFileDialog.PopupCentered(); + } else if (actionId is FileContextMenuOptions.Delete) { var confirmedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);