Skip to content

Commit 277503e

Browse files
authored
Creating a new file with clipboard contents (#1995)
### Description - A new file can be created with the contents on the clipboard using menu action. - PTAL and LMK if I can proceed to implement this with shortcut key. ### Related Issue - Addresses #1970 ### Screenshot https://github.com/user-attachments/assets/79d42cdf-d88d-46ff-b8bd-11c4ae640333 ### Potential Bugs to fix - New file not being opened in new tab.
1 parent 12f3199 commit 277503e

File tree

7 files changed

+92
-21
lines changed

7 files changed

+92
-21
lines changed

CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFileManager+FileManagement.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ extension CEWorkspaceFileManager {
6262
func addFile(
6363
fileName: String,
6464
toFile file: CEWorkspaceFile,
65-
useExtension: String? = nil
65+
useExtension: String? = nil,
66+
contents: Data? = nil
6667
) throws -> CEWorkspaceFile {
6768
// check the folder for other files, and see what the most common file extension is
6869
do {
@@ -95,7 +96,7 @@ extension CEWorkspaceFileManager {
9596
// Create the file
9697
guard fileManager.createFile(
9798
atPath: fileUrl.path,
98-
contents: nil,
99+
contents: contents,
99100
attributes: [FileAttributeKey.creationDate: Date()]
100101
) else {
101102
throw CocoaError.error(.fileWriteUnknown, url: fileUrl)

CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ final class ProjectNavigatorMenu: NSMenu {
6161
let showFileInspector = menuItem("Show File Inspector", action: nil)
6262

6363
let newFile = menuItem("New File...", action: #selector(newFile))
64+
let newFileFromClipboard = menuItem(
65+
"New File from Clipboard",
66+
action: #selector(newFileFromClipboard),
67+
key: "v"
68+
)
69+
newFileFromClipboard.keyEquivalentModifierMask = [.command]
6470
let newFolder = menuItem("New Folder", action: #selector(newFolder))
6571

6672
let rename = menuItem("Rename", action: #selector(renameFile))
@@ -100,6 +106,7 @@ final class ProjectNavigatorMenu: NSMenu {
100106
showFileInspector,
101107
NSMenuItem.separator(),
102108
newFile,
109+
newFileFromClipboard,
103110
newFolder
104111
]
105112

CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenuActions.swift

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,47 @@ extension ProjectNavigatorMenu {
9898
}
9999
}
100100

101+
/// Opens the rename file dialogue on the cell this was presented from.
102+
@objc
103+
func renameFile() {
104+
guard let newFile = workspace?.listenerModel.highlightedFileItem else { return }
105+
let row = sender.outlineView.row(forItem: newFile)
106+
guard row > 0,
107+
let cell = sender.outlineView.view(
108+
atColumn: 0,
109+
row: row,
110+
makeIfNecessary: false
111+
) as? ProjectNavigatorTableViewCell else {
112+
return
113+
}
114+
sender.outlineView.window?.makeFirstResponder(cell.textField)
115+
}
116+
117+
// TODO: Automatically identified the file type
118+
/// Action that creates a new file with clipboard content
119+
@objc
120+
func newFileFromClipboard() {
121+
guard let item else { return }
122+
do {
123+
let clipBoardContent = NSPasteboard.general.string(forType: .string)?.data(using: .utf8)
124+
if let clipBoardContent, !clipBoardContent.isEmpty, let newFile = try workspace?
125+
.workspaceFileManager?
126+
.addFile(
127+
fileName: "untitled",
128+
toFile: item,
129+
contents: clipBoardContent
130+
) {
131+
workspace?.listenerModel.highlightedFileItem = newFile
132+
workspace?.editorManager?.openTab(item: newFile)
133+
renameFile()
134+
}
135+
} catch {
136+
let alert = NSAlert(error: error)
137+
alert.addButton(withTitle: "Dismiss")
138+
alert.runModal()
139+
}
140+
}
141+
101142
// TODO: allow custom folder names
102143
/// Action that creates a new untitled folder
103144
@objc
@@ -143,21 +184,6 @@ extension ProjectNavigatorMenu {
143184
reloadData()
144185
}
145186

146-
/// Opens the rename file dialogue on the cell this was presented from.
147-
@objc
148-
func renameFile() {
149-
let row = sender.outlineView.row(forItem: item)
150-
guard row > 0,
151-
let cell = sender.outlineView.view(
152-
atColumn: 0,
153-
row: row,
154-
makeIfNecessary: false
155-
) as? ProjectNavigatorTableViewCell else {
156-
return
157-
}
158-
sender.outlineView.window?.makeFirstResponder(cell.textField)
159-
}
160-
161187
/// Action that moves the item to trash.
162188
@objc
163189
func trash() {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
//
2+
// ProjectNavigatorNSOutlineView.swift
3+
// CodeEdit
4+
//
5+
// Created by Khan Winter on 6/10/25.
6+
//
7+
8+
import AppKit
9+
10+
final class ProjectNavigatorNSOutlineView: NSOutlineView, NSMenuItemValidation {
11+
override func performKeyEquivalent(with event: NSEvent) -> Bool {
12+
guard event.window === window && window?.firstResponder === self else {
13+
return super.performKeyEquivalent(with: event)
14+
}
15+
16+
if event.charactersIgnoringModifiers == "v"
17+
&& event.modifierFlags.intersection(.deviceIndependentFlagsMask) == .command {
18+
guard let menu = menu as? ProjectNavigatorMenu else {
19+
return super.performKeyEquivalent(with: event)
20+
}
21+
menu.delegate?.menuNeedsUpdate?(menu)
22+
for fileItem in selectedRowIndexes.compactMap({ item(atRow: $0) as? CEWorkspaceFile }) {
23+
menu.item = fileItem
24+
menu.newFileFromClipboard()
25+
}
26+
return true
27+
}
28+
return super.performKeyEquivalent(with: event)
29+
}
30+
31+
func validateMenuItem(_ menuItem: NSMenuItem) -> Bool {
32+
if menuItem.action == #selector(ProjectNavigatorMenu.newFileFromClipboard) {
33+
return !selectedRowIndexes.isEmpty
34+
}
35+
return false
36+
}
37+
}

CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+NSMenuDelegate.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ extension ProjectNavigatorViewController: NSMenuDelegate {
1818
let row = outlineView.clickedRow
1919
guard let menu = menu as? ProjectNavigatorMenu else { return }
2020

21+
menu.workspace = workspace
2122
if row == -1 {
2223
menu.item = nil
2324
} else {
2425
if let item = outlineView.item(atRow: row) as? CEWorkspaceFile {
2526
menu.item = item
26-
menu.workspace = workspace
2727
} else {
2828
menu.item = nil
2929
}

CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ final class ProjectNavigatorViewController: NSViewController {
7474
self.scrollView.hasVerticalScroller = true
7575
self.view = scrollView
7676

77-
self.outlineView = NSOutlineView()
77+
self.outlineView = ProjectNavigatorNSOutlineView()
7878
self.outlineView.dataSource = self
7979
self.outlineView.delegate = self
8080
self.outlineView.autosaveExpandedItems = true

0 commit comments

Comments
 (0)