diff --git a/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs b/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs index b6ebe26..1f757e4 100644 --- a/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs +++ b/src/SharpIDE.Application/Features/FileWatching/IdeFileOperationsService.cs @@ -28,6 +28,36 @@ public class IdeFileOperationsService(SharpIdeSolutionModificationService sharpI await _sharpIdeSolutionModificationService.RemoveDirectory(folder); } + public async Task CopyDirectory(IFolderOrProject destinationParentNode, string sourceDirectoryPath, string newDirectoryName) + { + var newDirectoryPath = Path.Combine(destinationParentNode.ChildNodeBasePath, newDirectoryName); + CopyAll(new DirectoryInfo(sourceDirectoryPath), new DirectoryInfo(newDirectoryPath)); + var newFolder = await _sharpIdeSolutionModificationService.AddDirectory(destinationParentNode, newDirectoryName); + return; + + static void CopyAll(DirectoryInfo source, DirectoryInfo target) + { + Directory.CreateDirectory(target.FullName); + foreach (var fi in source.GetFiles()) + { + fi.CopyTo(Path.Combine(target.FullName, fi.Name)); + } + + foreach (var diSourceSubDir in source.GetDirectories()) + { + var nextTargetSubDir = target.CreateSubdirectory(diSourceSubDir.Name); + CopyAll(diSourceSubDir, nextTargetSubDir); + } + } + } + + public async Task MoveDirectory(IFolderOrProject destinationParentNode, SharpIdeFolder folderToMove) + { + var newDirectoryPath = Path.Combine(destinationParentNode.ChildNodeBasePath, folderToMove.Name); + Directory.Move(folderToMove.Path, newDirectoryPath); + await _sharpIdeSolutionModificationService.MoveDirectory(destinationParentNode, folderToMove); + } + public async Task DeleteFile(SharpIdeFile file) { File.Delete(file.Path); diff --git a/src/SharpIDE.Application/Features/FileWatching/SharpIdeSolutionModificationService.cs b/src/SharpIDE.Application/Features/FileWatching/SharpIdeSolutionModificationService.cs index a5f9306..9f0b34e 100644 --- a/src/SharpIDE.Application/Features/FileWatching/SharpIdeSolutionModificationService.cs +++ b/src/SharpIDE.Application/Features/FileWatching/SharpIdeSolutionModificationService.cs @@ -60,9 +60,37 @@ public class SharpIdeSolutionModificationService(FileChangedService fileChangedS } } - public async Task MoveDirectory(SharpIdeFolder folder, string newDirectoryPath) + public async Task MoveDirectory(IFolderOrProject destinationParentNode, SharpIdeFolder folderToMove) { + var oldFolderPath = folderToMove.Path; + var newFolderPath = Path.Combine(destinationParentNode.ChildNodeBasePath, folderToMove.Name); + var parentFolderOrProject = (IFolderOrProject)folderToMove.Parent; + parentFolderOrProject.Folders.Remove(folderToMove); + destinationParentNode.Folders.Add(folderToMove); + folderToMove.Parent = destinationParentNode; + folderToMove.Path = newFolderPath; + + var stack = new Stack(); + stack.Push(folderToMove); + + while (stack.Count > 0) + { + var current = stack.Pop(); + + foreach (var subfolder in current.Folders) + { + subfolder.Path = Path.Combine(current.Path, subfolder.Name); + stack.Push(subfolder); + } + + foreach (var file in current.Files) + { + var oldPath = file.Path; + file.Path = Path.Combine(current.Path, file.Name); + await _fileChangedService.SharpIdeFileMoved(file, oldPath); + } + } } public async Task RenameDirectory(SharpIdeFolder folder, string renamedFolderName) diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs index 4e7efec..c150bdb 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFile.cs @@ -6,7 +6,7 @@ using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; namespace SharpIDE.Application.Features.SolutionDiscovery; -public class SharpIdeFile : ISharpIdeNode, IChildSharpIdeNode +public class SharpIdeFile : ISharpIdeNode, IChildSharpIdeNode, IFileOrFolder { public required IExpandableSharpIdeNode Parent { get; set; } public required string Path { get; set; } diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs index 0373761..81e40f0 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/SharpIdeFolder.cs @@ -5,7 +5,7 @@ using SharpIDE.Application.Features.SolutionDiscovery.VsPersistence; namespace SharpIDE.Application.Features.SolutionDiscovery; -public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildSharpIdeNode, IFolderOrProject +public class SharpIdeFolder : ISharpIdeNode, IExpandableSharpIdeNode, IChildSharpIdeNode, IFolderOrProject, IFileOrFolder { public required IExpandableSharpIdeNode Parent { get; set; } public required string Path { get; set; } diff --git a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs index 08a144c..308bc1e 100644 --- a/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs +++ b/src/SharpIDE.Application/Features/SolutionDiscovery/VsPersistence/SharpIdeModels.cs @@ -22,6 +22,11 @@ public interface IFolderOrProject : IExpandableSharpIdeNode, IChildSharpIdeNode public string Name { get; set; } public string ChildNodeBasePath { get; } } +public interface IFileOrFolder : IChildSharpIdeNode +{ + public string Path { get; set; } + public string Name { get; set; } +} public interface IChildSharpIdeNode { public IExpandableSharpIdeNode Parent { get; set; } diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.Clipboard.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.Clipboard.cs index d8648e4..644078b 100644 --- a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.Clipboard.cs +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.Clipboard.cs @@ -14,11 +14,21 @@ public partial class SolutionExplorerPanel var selectedItems = GetSelectedTreeItems(); if (selectedItems.Count is 0) return; _itemsOnClipboard = (selectedItems - .Select(item => item.GetMetadata(0).As()) - .OfType>() - .Select(s => s.Item) + .Select(item => + { + var metadata = item.GetMetadata(0).As(); + IFileOrFolder? result = metadata switch + { + RefCountedContainer file => file.Item, + RefCountedContainer folder => folder.Item, + _ => null + }; + return result; + }) + .OfType() .ToList(), clipboardOperation); + GD.Print($"Solution Explorer - Added {_itemsOnClipboard.Value.Item1.Count} items to clipboard with operation {clipboardOperation}"); } private List GetSelectedTreeItems() @@ -51,18 +61,18 @@ public partial class SolutionExplorerPanel _itemsOnClipboard = null; } - private void CopyNodeFromClipboardToSelectedNode() + private void CopyNodesFromClipboardToSelectedNode() { var selected = _tree.GetSelected(); if (selected is null || _itemsOnClipboard is null) return; var genericMetadata = selected.GetMetadata(0).As(); - IFolderOrProject? folderOrProject = genericMetadata switch + IFolderOrProject? destinationFolderOrProject = genericMetadata switch { RefCountedContainer f => f.Item, RefCountedContainer p => p.Item, _ => null }; - if (folderOrProject is null) return; + if (destinationFolderOrProject is null) return; var (filesToPaste, operation) = _itemsOnClipboard.Value; _itemsOnClipboard = null; @@ -70,17 +80,31 @@ public partial class SolutionExplorerPanel { if (operation is ClipboardOperation.Copy) { - foreach (var fileToPaste in filesToPaste) + foreach (var fileOrFolderToPaste in filesToPaste) { - await _ideFileOperationsService.CopyFile(folderOrProject, fileToPaste.Path, fileToPaste.Name); + if (fileOrFolderToPaste is SharpIdeFolder folderToPaste) + { + await _ideFileOperationsService.CopyDirectory(destinationFolderOrProject, folderToPaste.Path, folderToPaste.Name); + } + else if (fileOrFolderToPaste is SharpIdeFile fileToPaste) + { + await _ideFileOperationsService.CopyFile(destinationFolderOrProject, fileToPaste.Path, fileToPaste.Name); + } } } // This will blow up if cutting a file into a directory that already has a file with the same name, but I don't really want to handle renaming cut-pasted files for MVP else if (operation is ClipboardOperation.Cut) { - foreach (var fileToPaste in filesToPaste) + foreach (var fileOrFolderToPaste in filesToPaste) { - await _ideFileOperationsService.MoveFile(folderOrProject, fileToPaste); + if (fileOrFolderToPaste is SharpIdeFolder folderToPaste) + { + await _ideFileOperationsService.MoveDirectory(destinationFolderOrProject, folderToPaste); + } + else if (fileOrFolderToPaste is SharpIdeFile fileToPaste) + { + await _ideFileOperationsService.MoveFile(destinationFolderOrProject, fileToPaste); + } } } }); diff --git a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs index f4d5f3d..83ee6e2 100644 --- a/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs +++ b/src/SharpIDE.Godot/Features/SolutionExplorer/SolutionExplorerPanel.cs @@ -29,7 +29,7 @@ public partial class SolutionExplorerPanel : MarginContainer private TreeItem _rootItem = null!; private enum ClipboardOperation { Cut, Copy } - private (List, ClipboardOperation)? _itemsOnClipboard; + private (List, ClipboardOperation)? _itemsOnClipboard; public override void _Ready() { _tree = GetNode("Tree"); @@ -52,7 +52,7 @@ public partial class SolutionExplorerPanel : MarginContainer // Paste else if (@event is InputEventKey { Pressed: true, Keycode: Key.V, CtrlPressed: true }) { - CopyNodeFromClipboardToSelectedNode(); + CopyNodesFromClipboardToSelectedNode(); } else if (@event is InputEventKey { Pressed: true, Keycode: Key.Delete }) {