From 010262491fd476bb27e05bf795387be092a987f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Larra=C3=B1aga?= Date: Mon, 14 Oct 2024 21:13:18 -0700 Subject: [PATCH 01/10] Selecting multiple files. Deleting, duplications, new folder from selection where supported, open in finder, open in external editor. --- .../ProjectNavigatorMenuActions.swift | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift new file mode 100644 index 000000000..a564f1c75 --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -0,0 +1,171 @@ +// +// ProjectNavigatorMenuActions.swift +// CodeEdit +// +// Created by Leonardo LarraƱaga on 10/11/24. +// + +import AppKit + +extension ProjectNavigatorMenu { + /// - Returns: the currently selected `CEWorkspaceFile` items in the outline view. + func selectedItems() -> Set { + /// Selected items... + let selectedItems = Set(outlineView.selectedRowIndexes.compactMap { + outlineView.item(atRow: $0) as? CEWorkspaceFile + }) + + /// Item that the user brought up the menu with... + if let menuItem = outlineView.item(atRow: outlineView.clickedRow) as? CEWorkspaceFile { + /// If the item is not in the set, just like in Xcode, only modify that item. + if !selectedItems.contains(menuItem) { + return Set([menuItem]) + } + } + + return selectedItems + } + + /// Verify if a folder can be mode from selection by getting the amount of parents found in the selected items. + /// Used to know if we can create a new folder from selection. + func canCreateFolderFromSelection() -> Bool { + var uniqueParents: Set = [] + for file in selectedItems() { + if let parent = file.parent { + uniqueParents.insert(parent) + } + } + + return uniqueParents.count == 1 + } + + /// Action that opens **Finder** at the items location. + @objc + func showInFinder() { + NSWorkspace.shared.activateFileViewerSelecting(selectedItems().map { $0.url }) + } + + /// Action that opens the item, identical to clicking it. + @objc + func openInTab() { + /// Sort the selected items first by their parent and then by name. + let sortedItems = selectedItems().sorted { (item1, item2) -> Bool in + /// Get the parents of both items. + let parent1 = outlineView.parent(forItem: item1) as? CEWorkspaceFile + let parent2 = outlineView.parent(forItem: item2) as? CEWorkspaceFile + + /// Compare by parent. + if parent1 != parent2 { + /// If the parents are different, use their row position in the outline view. + return outlineView.row(forItem: parent1) < outlineView.row(forItem: parent2) + } else { + /// If both items have the same parent, sort them by name. + return item1.name < item2.name + } + } + + /// Open the items in order. + sortedItems.forEach { item in + workspace?.editorManager?.openTab(item: item) + } + } + + /// Action that opens in an external editor + @objc + func openWithExternalEditor() { + /// Using `Process` to open all of the selected files at the same time. + let process = Process() + process.launchPath = "/usr/bin/open" + process.arguments = selectedItems().map { $0.url.absoluteString } + try? process.run() + } + + // TODO: allow custom file names + /// Action that creates a new untitled file + @objc + func newFile() { + guard let item else { return } + do { + try workspace?.workspaceFileManager?.addFile(fileName: "untitled", toFile: item) + } catch { + let alert = NSAlert(error: error) + alert.addButton(withTitle: "Dismiss") + alert.runModal() + } + outlineView.reloadData() + outlineView.expandItem(item.isFolder ? item : item.parent) + } + + // TODO: allow custom folder names + /// Action that creates a new untitled folder + @objc + func newFolder() { + guard let item else { return } + workspace?.workspaceFileManager?.addFolder(folderName: "untitled", toFile: item) + outlineView.expandItem(item) + outlineView.expandItem(item.isFolder ? item : item.parent) + } + + /// Creates a new folder with the items selected. + @objc + func newFolderFromSelection() { + guard let workspace, let workspaceFileManager = workspace.workspaceFileManager else { return } + + let selectedItems = selectedItems() + guard let parent = selectedItems.first?.parent else { return } + + /// Get 'New Folder' name. + var newFolderURL = parent.url.appendingPathComponent("New Folder", conformingTo: .folder) + var folderNumber = 0 + while workspaceFileManager.fileManager.fileExists(atPath: newFolderURL.path) { + folderNumber += 1 + newFolderURL = parent.url.appendingPathComponent("New Folder \(folderNumber)") + } + + for selectedItem in selectedItems where selectedItem.url != newFolderURL { + workspaceFileManager.move(file: selectedItem, to: newFolderURL.appending(path: selectedItem.name)) + } + + outlineView.reloadData() + outlineView.expandItem(parent.isFolder ? parent : parent.parent) + } + + /// Opens the rename file dialogue on the cell this was presented from. + @objc + func renameFile() { + let row = outlineView.row(forItem: item) + guard row > 0, + let cell = outlineView.view( + atColumn: 0, + row: row, + makeIfNecessary: false + ) as? ProjectNavigatorTableViewCell else { + return + } + outlineView.window?.makeFirstResponder(cell.textField) + } + + /// Action that moves the item to trash. + @objc + func trash() { + selectedItems().forEach { item in + workspace?.workspaceFileManager?.trash(file: item) + } + } + + /// Action that deletes the item immediately. + @objc + func delete() { + selectedItems().forEach { item in + workspace?.workspaceFileManager?.delete(file: item) + } + } + + /// Action that duplicates the item + @objc + func duplicate() { + selectedItems().forEach { item in + workspace?.workspaceFileManager?.duplicate(file: item) + } + } +} From ed9ccd2795e7353acea1447575a5a1d1e7bd2d58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Larra=C3=B1aga?= Date: Mon, 14 Oct 2024 21:13:59 -0700 Subject: [PATCH 02/10] Select multiple files. Delete, duplicate, new folder from selection, open in Finder, open in external editor. --- CodeEdit.xcodeproj/project.pbxproj | 4 + .../OutlineView/ProjectNavigatorMenu.swift | 128 ++++-------------- .../ProjectNavigatorMenuActions.swift | 38 ++++-- ...ViewController+NSOutlineViewDelegate.swift | 4 + .../ProjectNavigatorViewController.swift | 6 +- 5 files changed, 66 insertions(+), 114 deletions(-) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 0d697ab9f..9e3f824bc 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -97,6 +97,7 @@ 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; }; 3E0196732A3921AC002648D8 /* codeedit_shell_integration_rc.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration_rc.zsh */; }; 3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; }; + 4A6F0DB52CBA462B00499627 /* ProjectNavigatorMenuActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A6F0DB42CBA462B00499627 /* ProjectNavigatorMenuActions.swift */; }; 4E7F066629602E7B00BB3C12 /* CodeEditSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */; }; 4EE96ECB2960565E00FFBEA8 /* DocumentsUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */; }; 4EE96ECE296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */; }; @@ -773,6 +774,7 @@ 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = ""; }; 3E0196722A3921AC002648D8 /* codeedit_shell_integration_rc.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration_rc.zsh; sourceTree = ""; }; 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = ""; }; + 4A6F0DB42CBA462B00499627 /* ProjectNavigatorMenuActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectNavigatorMenuActions.swift; sourceTree = ""; }; 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditSplitViewController.swift; sourceTree = ""; }; 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentsUnitTests.swift; sourceTree = ""; }; 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSHapticFeedbackPerformerMock.swift; sourceTree = ""; }; @@ -1461,6 +1463,7 @@ 6CC17B502C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift */, 285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */, 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */, + 4A6F0DB42CBA462B00499627 /* ProjectNavigatorMenuActions.swift */, D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */, EC0870F62A455F6400EB8692 /* ProjectNavigatorViewController+NSMenuDelegate.swift */, ); @@ -4286,6 +4289,7 @@ 587B9DA629300ABD00AC7927 /* ToolbarBranchPicker.swift in Sources */, 6C6BD6F629CD145F00235D17 /* ExtensionInfo.swift in Sources */, 04BA7C202AE2D92B00584E1C /* GitClient+Status.swift in Sources */, + 4A6F0DB52CBA462B00499627 /* ProjectNavigatorMenuActions.swift in Sources */, 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */, 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */, 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */, diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift index 01206e590..eb7c7764c 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift @@ -17,10 +17,12 @@ final class ProjectNavigatorMenu: NSMenu { /// The workspace, for opening the item var workspace: WorkspaceDocument? - var outlineView: NSOutlineView + /// The `ProjectNavigatorViewController` is being called from. + /// By sending it, we can access it's variables and functions. + var sender: ProjectNavigatorViewController - init(sender: NSOutlineView) { - outlineView = sender + init(_ sender: ProjectNavigatorViewController) { + self.sender = sender super.init(title: "Options") } @@ -42,9 +44,9 @@ final class ProjectNavigatorMenu: NSMenu { return mItem } - /// Setup the menu and disables certain items when `isFile` is false - /// - Parameter isFile: A flag indicating that the item is a file instead of a directory - private func setupMenu() { + /// Configures the menu based on the current selection in the outline view. + /// - Menu items get added depending on the amount of selected items. + private func setupMenu() { // swiftlint:disable:this function_body_length guard let item else { return } let showInFinder = menuItem("Show in Finder", action: #selector(showInFinder)) @@ -92,19 +94,30 @@ final class ProjectNavigatorMenu: NSMenu { showFileInspector, NSMenuItem.separator(), newFile, - newFolder, - NSMenuItem.separator(), - rename, - trash, - delete, - duplicate, - NSMenuItem.separator(), - sortByName, - sortByType, - NSMenuItem.separator(), - sourceControl, + newFolder ] + if canCreateFolderFromSelection() { + items.append(menuItem("New Folder from Selection", action: #selector(newFolderFromSelection))) + } + items.append(NSMenuItem.separator()) + if selectedItems().count == 1 { + items.append(rename) + } + + items.append( + contentsOf: [ + trash, + delete, + duplicate, + NSMenuItem.separator(), + sortByName, + sortByType, + NSMenuItem.separator(), + sourceControl, + ] + ) + setSubmenu(openAsMenu(item: item), for: openAs) setSubmenu(sourceControlMenu(item: item), for: sourceControl) } @@ -183,87 +196,6 @@ final class ProjectNavigatorMenu: NSMenu { removeAllItems() setupMenu() } - - /// Action that opens **Finder** at the items location. - @objc - private func showInFinder() { - item?.showInFinder() - } - - /// Action that opens the item, identical to clicking it. - @objc - private func openInTab() { - if let item { - workspace?.editorManager?.openTab(item: item) - } - } - - /// Action that opens in an external editor - @objc - private func openWithExternalEditor() { - item?.openWithExternalEditor() - } - - // TODO: allow custom file names - /// Action that creates a new untitled file - @objc - private func newFile() { - guard let item else { return } - do { - try workspace?.workspaceFileManager?.addFile(fileName: "untitled", toFile: item) - } catch { - let alert = NSAlert(error: error) - alert.addButton(withTitle: "Dismiss") - alert.runModal() - } - outlineView.expandItem(item.isFolder ? item : item.parent) - } - - // TODO: allow custom folder names - /// Action that creates a new untitled folder - @objc - private func newFolder() { - guard let item else { return } - workspace?.workspaceFileManager?.addFolder(folderName: "untitled", toFile: item) - outlineView.expandItem(item) - outlineView.expandItem(item.isFolder ? item : item.parent) - } - - /// Opens the rename file dialogue on the cell this was presented from. - @objc - private func renameFile() { - let row = outlineView.row(forItem: item) - guard row > 0, - let cell = outlineView.view( - atColumn: 0, - row: row, - makeIfNecessary: false - ) as? ProjectNavigatorTableViewCell else { - return - } - outlineView.window?.makeFirstResponder(cell.textField) - } - - /// Action that moves the item to trash. - @objc - private func trash() { - guard let item else { return } - workspace?.workspaceFileManager?.trash(file: item) - } - - /// Action that deletes the item immediately. - @objc - private func delete() { - guard let item else { return } - workspace?.workspaceFileManager?.delete(file: item) - } - - /// Action that duplicates the item - @objc - private func duplicate() { - guard let item else { return } - workspace?.workspaceFileManager?.duplicate(file: item) - } } extension NSMenuItem { diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index a564f1c75..c561a824c 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -11,12 +11,12 @@ extension ProjectNavigatorMenu { /// - Returns: the currently selected `CEWorkspaceFile` items in the outline view. func selectedItems() -> Set { /// Selected items... - let selectedItems = Set(outlineView.selectedRowIndexes.compactMap { - outlineView.item(atRow: $0) as? CEWorkspaceFile + let selectedItems = Set(sender.outlineView.selectedRowIndexes.compactMap { + sender.outlineView.item(atRow: $0) as? CEWorkspaceFile }) /// Item that the user brought up the menu with... - if let menuItem = outlineView.item(atRow: outlineView.clickedRow) as? CEWorkspaceFile { + if let menuItem = sender.outlineView.item(atRow: sender.outlineView.clickedRow) as? CEWorkspaceFile { /// If the item is not in the set, just like in Xcode, only modify that item. if !selectedItems.contains(menuItem) { return Set([menuItem]) @@ -51,13 +51,13 @@ extension ProjectNavigatorMenu { /// Sort the selected items first by their parent and then by name. let sortedItems = selectedItems().sorted { (item1, item2) -> Bool in /// Get the parents of both items. - let parent1 = outlineView.parent(forItem: item1) as? CEWorkspaceFile - let parent2 = outlineView.parent(forItem: item2) as? CEWorkspaceFile + let parent1 = sender.outlineView.parent(forItem: item1) as? CEWorkspaceFile + let parent2 = sender.outlineView.parent(forItem: item2) as? CEWorkspaceFile /// Compare by parent. if parent1 != parent2 { /// If the parents are different, use their row position in the outline view. - return outlineView.row(forItem: parent1) < outlineView.row(forItem: parent2) + return sender.outlineView.row(forItem: parent1) < sender.outlineView.row(forItem: parent2) } else { /// If both items have the same parent, sort them by name. return item1.name < item2.name @@ -92,8 +92,8 @@ extension ProjectNavigatorMenu { alert.addButton(withTitle: "Dismiss") alert.runModal() } - outlineView.reloadData() - outlineView.expandItem(item.isFolder ? item : item.parent) + reloadData() + sender.outlineView.expandItem(item.isFolder ? item : item.parent) } // TODO: allow custom folder names @@ -102,8 +102,9 @@ extension ProjectNavigatorMenu { func newFolder() { guard let item else { return } workspace?.workspaceFileManager?.addFolder(folderName: "untitled", toFile: item) - outlineView.expandItem(item) - outlineView.expandItem(item.isFolder ? item : item.parent) + reloadData() + sender.outlineView.expandItem(item) + sender.outlineView.expandItem(item.isFolder ? item : item.parent) } /// Creates a new folder with the items selected. @@ -126,23 +127,22 @@ extension ProjectNavigatorMenu { workspaceFileManager.move(file: selectedItem, to: newFolderURL.appending(path: selectedItem.name)) } - outlineView.reloadData() - outlineView.expandItem(parent.isFolder ? parent : parent.parent) + reloadData() } /// Opens the rename file dialogue on the cell this was presented from. @objc func renameFile() { - let row = outlineView.row(forItem: item) + let row = sender.outlineView.row(forItem: item) guard row > 0, - let cell = outlineView.view( + let cell = sender.outlineView.view( atColumn: 0, row: row, makeIfNecessary: false ) as? ProjectNavigatorTableViewCell else { return } - outlineView.window?.makeFirstResponder(cell.textField) + sender.outlineView.window?.makeFirstResponder(cell.textField) } /// Action that moves the item to trash. @@ -151,6 +151,7 @@ extension ProjectNavigatorMenu { selectedItems().forEach { item in workspace?.workspaceFileManager?.trash(file: item) } + reloadData() } /// Action that deletes the item immediately. @@ -159,6 +160,7 @@ extension ProjectNavigatorMenu { selectedItems().forEach { item in workspace?.workspaceFileManager?.delete(file: item) } + reloadData() } /// Action that duplicates the item @@ -167,5 +169,11 @@ extension ProjectNavigatorMenu { selectedItems().forEach { item in workspace?.workspaceFileManager?.duplicate(file: item) } + reloadData() + } + + private func reloadData() { + sender.outlineView.reloadData() + sender.filteredContentChildren.removeAll() } } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift index 87c93a2c4..6f8a6f30a 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSOutlineViewDelegate.swift @@ -36,6 +36,10 @@ extension ProjectNavigatorViewController: NSOutlineViewDelegate { func outlineViewSelectionDidChange(_ notification: Notification) { guard let outlineView = notification.object as? NSOutlineView else { return } + /// If multiple rows are selected, do not open any file. + guard outlineView.selectedRowIndexes.count == 1 else { return } + + /// If only one row is selected, proceed as before let selectedIndex = outlineView.selectedRow guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return } diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index 973bbdde4..8851fd1ec 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -75,9 +75,10 @@ final class ProjectNavigatorViewController: NSViewController { self.outlineView.autosaveExpandedItems = true self.outlineView.autosaveName = workspace?.workspaceFileManager?.folderUrl.path ?? "" self.outlineView.headerView = nil - self.outlineView.menu = ProjectNavigatorMenu(sender: self.outlineView) + self.outlineView.menu = ProjectNavigatorMenu(self) self.outlineView.menu?.delegate = self self.outlineView.doubleAction = #selector(onItemDoubleClicked) + self.outlineView.allowsMultipleSelection = true self.outlineView.setAccessibilityIdentifier("ProjectNavigator") self.outlineView.setAccessibilityLabel("Project Navigator") @@ -157,6 +158,9 @@ final class ProjectNavigatorViewController: NSViewController { /// Expand or collapse the folder on double click @objc private func onItemDoubleClicked() { + /// If there are multiples items selected, don't do anything, just like in Xcode. + guard outlineView.selectedRowIndexes.count == 1 else { return } + guard let item = outlineView.item(atRow: outlineView.clickedRow) as? CEWorkspaceFile else { return } if item.isFolder { From 48c5d1a28691c63e168ed158ac519e5cc5852ef8 Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Mon, 14 Oct 2024 22:37:58 -0700 Subject: [PATCH 03/10] Update comments in ProjectNavigatorMenuActions.swift --- .../OutlineView/ProjectNavigatorMenuActions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index c561a824c..322b7818b 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -26,8 +26,8 @@ extension ProjectNavigatorMenu { return selectedItems } - /// Verify if a folder can be mode from selection by getting the amount of parents found in the selected items. - /// Used to know if we can create a new folder from selection. + /// Verify if a folder can be made from selection by getting the amount of parents found in the selected items. + /// If the amount of parents is equal to one, a folder can be made. func canCreateFolderFromSelection() -> Bool { var uniqueParents: Set = [] for file in selectedItems() { From ea01604452cdf2187e96c30b55be006d924510c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Larra=C3=B1aga?= Date: Mon, 14 Oct 2024 22:47:02 -0700 Subject: [PATCH 04/10] Changed batch delete so that the user only gets asked for a single confirmation instead of one for every file. --- ...EWorkspaceFileManager+FileManagement.swift | 23 +++++++++++++++++++ .../ProjectNavigatorMenuActions.swift | 9 ++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift index e3469687f..90a99d3cc 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift @@ -141,6 +141,29 @@ extension CEWorkspaceFileManager { } } + /// This function deletes multiple files or folders from the current project by erasing immediately. + /// - Parameters: + /// - file: The file to delete + /// - confirmDelete: True to present an alert to confirm the delete. + public func batchDelete(files: Set, confirmDelete: Bool = true) { + let deleteConfirmation = NSAlert() + deleteConfirmation.messageText = "Do you want to delete \(files.count) items?" + deleteConfirmation.informativeText = "These items will be deleted immediately. You can't undo this action." + deleteConfirmation.alertStyle = .critical + deleteConfirmation.addButton(withTitle: "Delete") + deleteConfirmation.buttons.last?.hasDestructiveAction = true + deleteConfirmation.addButton(withTitle: "Cancel") + if !confirmDelete || deleteConfirmation.runModal() == .alertFirstButtonReturn { + for file in files where fileManager.fileExists(atPath: file.url.path) { + do { + try fileManager.removeItem(at: file.url) + } catch { + fatalError(error.localizedDescription) + } + } + } + } + /// This function duplicates the item or folder /// - Parameter file: The file to duplicate /// - Authors: Mattijs Eikelenboom, KaiTheRedNinja. *Moved from 7c27b1e* diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index c561a824c..b2dde6268 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -157,8 +157,13 @@ extension ProjectNavigatorMenu { /// Action that deletes the item immediately. @objc func delete() { - selectedItems().forEach { item in - workspace?.workspaceFileManager?.delete(file: item) + let selectedItems = selectedItems() + if selectedItems.count == 1 { + selectedItems.forEach { item in + workspace?.workspaceFileManager?.delete(file: item) + } + } else { + workspace?.workspaceFileManager?.batchDelete(files: selectedItems) } reloadData() } From f20901570136a7c3f34f2f64c751bfbd10dcc69f Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Tue, 15 Oct 2024 00:36:17 -0700 Subject: [PATCH 05/10] Update comments CEWorkspaceFileManager+FileManagement.swift --- .../Models/CEWorkspaceFileManager+FileManagement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift index 90a99d3cc..8bc15f4a5 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift @@ -143,7 +143,7 @@ extension CEWorkspaceFileManager { /// This function deletes multiple files or folders from the current project by erasing immediately. /// - Parameters: - /// - file: The file to delete + /// - files: The files to delete /// - confirmDelete: True to present an alert to confirm the delete. public func batchDelete(files: Set, confirmDelete: Bool = true) { let deleteConfirmation = NSAlert() From cf6d5af71b92f20ad4824259ac4f75348feba5f7 Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:19:13 -0700 Subject: [PATCH 06/10] Update CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift Co-authored-by: Austin Condiff --- .../OutlineView/ProjectNavigatorMenuActions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index 08a40ea53..0cb095f82 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -120,7 +120,7 @@ extension ProjectNavigatorMenu { var folderNumber = 0 while workspaceFileManager.fileManager.fileExists(atPath: newFolderURL.path) { folderNumber += 1 - newFolderURL = parent.url.appendingPathComponent("New Folder \(folderNumber)") + newFolderURL = parent.url.appendingPathComponent("New Folder With Items \(folderNumber)") } for selectedItem in selectedItems where selectedItem.url != newFolderURL { From 2b9feaf1de74f97a8ead244227776ba160d6c6ff Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:19:27 -0700 Subject: [PATCH 07/10] Update CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift Co-authored-by: Austin Condiff --- .../Models/CEWorkspaceFileManager+FileManagement.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift index 8bc15f4a5..b9e8b1975 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift @@ -147,8 +147,8 @@ extension CEWorkspaceFileManager { /// - confirmDelete: True to present an alert to confirm the delete. public func batchDelete(files: Set, confirmDelete: Bool = true) { let deleteConfirmation = NSAlert() - deleteConfirmation.messageText = "Do you want to delete \(files.count) items?" - deleteConfirmation.informativeText = "These items will be deleted immediately. You can't undo this action." + deleteConfirmation.messageText = "Are you sure you want to delete the \(files.count) selected items?" + deleteConfirmation.informativeText = "\(files.count) items will be deleted immediately. You cannot undo this action." deleteConfirmation.alertStyle = .critical deleteConfirmation.addButton(withTitle: "Delete") deleteConfirmation.buttons.last?.hasDestructiveAction = true From 75d1adfbf45739e157daef9d9fa2c0740ca76f3a Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Fri, 18 Oct 2024 09:19:39 -0700 Subject: [PATCH 08/10] Update CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift Co-authored-by: Austin Condiff --- .../OutlineView/ProjectNavigatorMenuActions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index 0cb095f82..9ab9d3ed2 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -116,7 +116,7 @@ extension ProjectNavigatorMenu { guard let parent = selectedItems.first?.parent else { return } /// Get 'New Folder' name. - var newFolderURL = parent.url.appendingPathComponent("New Folder", conformingTo: .folder) + var newFolderURL = parent.url.appendingPathComponent("New Folder With Items", conformingTo: .folder) var folderNumber = 0 while workspaceFileManager.fileManager.fileExists(atPath: newFolderURL.path) { folderNumber += 1 From acb51a9e21cc07c98461e534ca79ea2205a64f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Larra=C3=B1aga?= Date: Sat, 19 Oct 2024 13:01:40 -0700 Subject: [PATCH 09/10] Fixed SwiftLint errors --- .../Models/CEWorkspaceFileManager+FileManagement.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift index b9e8b1975..ae50e3409 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift @@ -148,6 +148,7 @@ extension CEWorkspaceFileManager { public func batchDelete(files: Set, confirmDelete: Bool = true) { let deleteConfirmation = NSAlert() deleteConfirmation.messageText = "Are you sure you want to delete the \(files.count) selected items?" + // swiftlint:disable:next line_length deleteConfirmation.informativeText = "\(files.count) items will be deleted immediately. You cannot undo this action." deleteConfirmation.alertStyle = .critical deleteConfirmation.addButton(withTitle: "Delete") From 84d48e3db27f8bbfb87a21ca71b54da4c8246d3b Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Tue, 22 Oct 2024 21:57:02 -0700 Subject: [PATCH 10/10] Update CEWorkspaceFileManager+FileManagement.swift --- .../Models/CEWorkspaceFileManager+FileManagement.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift index ae50e3409..f26dfa5cc 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift @@ -159,7 +159,7 @@ extension CEWorkspaceFileManager { do { try fileManager.removeItem(at: file.url) } catch { - fatalError(error.localizedDescription) + print(error.localizedDescription) } } }