Skip to content

Commit 8c2b804

Browse files
authored
Drag and drop file (#1031)
* drag and drop files * move file error alert
1 parent 18737df commit 8c2b804

2 files changed

Lines changed: 95 additions & 3 deletions

File tree

CodeEdit/Features/NavigatorSidebar/ProjectNavigator/OutlineView/OutlineViewController.swift

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ final class OutlineViewController: NSViewController {
6767
column.title = "Cell"
6868
outlineView.addTableColumn(column)
6969

70+
outlineView.setDraggingSourceOperationMask(.move, forLocal: false)
71+
outlineView.registerForDraggedTypes([.fileURL])
72+
7073
self.scrollView.documentView = outlineView
7174
self.scrollView.contentView.automaticallyAdjustsContentInsets = false
7275
self.scrollView.contentView.contentInsets = .init(top: 10, left: 0, bottom: 0, right: 0)
@@ -150,6 +153,84 @@ extension OutlineViewController: NSOutlineViewDataSource {
150153
}
151154
return false
152155
}
156+
157+
/// write dragged file(s) to pasteboard
158+
func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? {
159+
guard let fileItem = item as? Item else { return nil }
160+
return fileItem.url as NSURL
161+
}
162+
163+
/// declare valid drop target
164+
func outlineView(
165+
_ outlineView: NSOutlineView,
166+
validateDrop info: NSDraggingInfo,
167+
proposedItem item: Any?,
168+
proposedChildIndex index: Int
169+
) -> NSDragOperation {
170+
guard let fileItem = item as? Item else { return [] }
171+
// -1 index indicates that we are hovering over a row in outline view (folder or file)
172+
if index == -1 {
173+
if !fileItem.isFolder {
174+
outlineView.setDropItem(fileItem.parent, dropChildIndex: index)
175+
}
176+
return .move
177+
}
178+
return []
179+
}
180+
181+
/// handle successful or unsuccessful drop
182+
func outlineView(
183+
_ outlineView: NSOutlineView,
184+
acceptDrop info: NSDraggingInfo,
185+
item: Any?,
186+
childIndex index: Int
187+
) -> Bool {
188+
guard let pasteboardItems = info.draggingPasteboard.readObjects(forClasses: [NSURL.self]) else { return false }
189+
let fileItemURLS = pasteboardItems.compactMap { $0 as? URL }
190+
191+
guard let fileItemDestination = item as? Item else { return false }
192+
let destParentURL = fileItemDestination.url
193+
194+
for fileItemURL in fileItemURLS {
195+
let destURL = destParentURL.appendingPathComponent(fileItemURL.lastPathComponent)
196+
// cancel dropping file item on self or in parent directory
197+
if fileItemURL == destURL || fileItemURL == destParentURL {
198+
return false
199+
}
200+
201+
// Needs to come before call to .removeItem or else race condition occurs
202+
guard let srcFileItem = try? workspace?.workspaceClient?.getFileItem(fileItemURL.relativePath) else {
203+
return false
204+
}
205+
206+
if WorkspaceClient.FileItem.fileManger.fileExists(atPath: destURL.path) {
207+
let shouldReplace = replaceFileDialog(fileName: fileItemURL.lastPathComponent)
208+
guard shouldReplace else {
209+
return false
210+
}
211+
do {
212+
try WorkspaceClient.FileItem.fileManger.removeItem(at: destURL)
213+
} catch {
214+
fatalError(error.localizedDescription)
215+
}
216+
}
217+
218+
self.moveFile(file: srcFileItem, to: destURL)
219+
}
220+
return true
221+
}
222+
223+
func replaceFileDialog(fileName: String) -> Bool {
224+
let alert = NSAlert()
225+
alert.messageText = """
226+
A file or folder with the name \(fileName) already exists in the destination folder. Do you want to replace it?
227+
"""
228+
alert.informativeText = "This action is irreversible!"
229+
alert.alertStyle = .warning
230+
alert.addButton(withTitle: "Replace")
231+
alert.addButton(withTitle: "Cancel")
232+
return alert.runModal() == .alertFirstButtonReturn
233+
}
153234
}
154235

155236
// MARK: - NSOutlineViewDelegate
@@ -299,8 +380,12 @@ extension OutlineViewController: NSMenuDelegate {
299380

300381
extension OutlineViewController: OutlineTableViewCellDelegate {
301382
func moveFile(file: Item, to destination: URL) {
302-
workspace?.closeTab(item: .codeEditor(file.id))
383+
if !file.isFolder {
384+
workspace?.closeTab(item: .codeEditor(file.id))
385+
}
303386
file.move(to: destination)
304-
workspace?.openTab(item: file)
387+
if !file.isFolder {
388+
workspace?.openTab(item: file)
389+
}
305390
}
306391
}

CodeEdit/Utils/WorkspaceClient/Model/FileItem.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,14 @@ extension WorkspaceClient {
287287
try FileItem.fileManger.moveItem(at: self.url, to: newLocation)
288288
self.url = newLocation
289289
} catch {
290-
fatalError(error.localizedDescription)
290+
let errorCode = (error as NSError).code
291+
let errorAlert = NSAlert()
292+
errorAlert.messageText = """
293+
The operation can’t be completed because an unexpected error occurred (error code \(String(errorCode))).
294+
"""
295+
errorAlert.alertStyle = .critical
296+
errorAlert.addButton(withTitle: "OK")
297+
errorAlert.runModal()
291298
}
292299
}
293300
}

0 commit comments

Comments
 (0)