diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift index 29182c645..2618f1e19 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift @@ -116,6 +116,57 @@ extension CEWorkspaceFileManager { } } + /// This function is used to create a file with contents + func addFileWithContents( + fileName: String, + toFile file: CEWorkspaceFile, + useExtension: String? = nil, + contents: Data? + ) throws -> CEWorkspaceFile { + do { + var fileExtension: String + if fileName.contains(".") { + fileExtension = "" + } else { + fileExtension = useExtension ?? findCommonFileExtension(for: file) + if !fileExtension.isEmpty && !fileExtension.starts(with: ".") { + fileExtension = "." + fileExtension + } + } + + var fileUrl = file.nearestFolder.appendingPathComponent("\(fileName)\(fileExtension)") + var fileNumber = 0 + while fileManager.fileExists(atPath: fileUrl.path) { + fileNumber += 1 + fileUrl = fileUrl.deletingLastPathComponent() + .appendingPathComponent("\(fileName)\(fileNumber)\(fileExtension)") + } + + guard fileUrl.fileName.isValidFilename else { + throw FileManagerError.invalidFileName + } + + guard fileManager.createFile( + atPath: fileUrl.path, + contents: contents, + attributes: [FileAttributeKey.creationDate: Date()] + ) else { + throw CocoaError.error(.fileWriteUnknown, url: fileUrl) + } + + try rebuildFiles(fromItem: file.isFolder ? file : file.parent ?? file) + notifyObservers(updatedItems: [file.isFolder ? file : file.parent ?? file]) + + guard let newFile = getFile(fileUrl.path, createIfNotFound: true) else { + throw FileManagerError.fileNotIndexed + } + return newFile + } catch { + logger.error("Failed to add file with contents: \(error, privacy: .auto)") + throw error + } + } + /// Finds a common file extension in the same directory as a file. Defaults to `txt` if no better alternatives /// are found. /// - Parameter file: The file to use to determine a common extension. diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift index eb7c7764c..7698a2b0e 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift @@ -58,6 +58,7 @@ final class ProjectNavigatorMenu: NSMenu { let showFileInspector = menuItem("Show File Inspector", action: nil) let newFile = menuItem("New File...", action: #selector(newFile)) + let newFileFromClipboard = menuItem("New File from Clipboard", action: #selector(newFileFromClipboard)) let newFolder = menuItem("New Folder", action: #selector(newFolder)) let rename = menuItem("Rename", action: #selector(renameFile)) @@ -94,6 +95,7 @@ final class ProjectNavigatorMenu: NSMenu { showFileInspector, NSMenuItem.separator(), newFile, + newFileFromClipboard, newFolder ] diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift index 8cdd59349..ebe1776d2 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift @@ -98,6 +98,51 @@ extension ProjectNavigatorMenu { } } + /// Opens the rename file dialogue on the cell this was presented from. + @objc + func renameFile() { + guard let newFile = workspace?.listenerModel.highlightedFileItem else { return } + let row = sender.outlineView.row(forItem: newFile) + guard row > 0, + let cell = sender.outlineView.view( + atColumn: 0, + row: row, + makeIfNecessary: false + ) as? ProjectNavigatorTableViewCell else { + return + } + sender.outlineView.window?.makeFirstResponder(cell.textField) + } + + // TODO: Automatically identified the file type + /// Action that creates a new file with clipboard content + @objc + func newFileFromClipboard() { + guard let item else { return } + do { + let clipBoardContent = NSPasteboard.general.string(forType: .string)?.data(using: .utf8) + if let newFile = try workspace? + .workspaceFileManager? + .addFileWithContents( + fileName: "untitled", + toFile: item, + useExtension: "", + contents: clipBoardContent + ) { + workspace?.listenerModel.highlightedFileItem = newFile + workspace?.editorManager?.openTab(item: newFile) + } + /// To resolve racing condition + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.renameFile() + } + } catch { + let alert = NSAlert(error: error) + alert.addButton(withTitle: "Dismiss") + alert.runModal() + } + } + // TODO: allow custom folder names /// Action that creates a new untitled folder @objc @@ -143,21 +188,6 @@ extension ProjectNavigatorMenu { reloadData() } - /// Opens the rename file dialogue on the cell this was presented from. - @objc - func renameFile() { - let row = sender.outlineView.row(forItem: item) - guard row > 0, - let cell = sender.outlineView.view( - atColumn: 0, - row: row, - makeIfNecessary: false - ) as? ProjectNavigatorTableViewCell else { - return - } - sender.outlineView.window?.makeFirstResponder(cell.textField) - } - /// Action that moves the item to trash. @objc func trash() {