feat: ensure text, files and URLs can be drag and dropped to terminal (#4962)

This PR ensures one can drag and drop the following things to the
terminal window:

- File (Inserts the file path into the terminal)
- URL
- Text


This resolves #4932
This commit is contained in:
Mitchell Hashimoto
2025-01-11 19:40:08 -08:00
committed by GitHub
2 changed files with 63 additions and 16 deletions

View File

@ -92,22 +92,6 @@ extension Ghostty {
windowFocus = false windowFocus = false
} }
} }
.onDrop(of: [.fileURL], isTargeted: nil) { providers in
providers.forEach { provider in
_ = provider.loadObject(ofClass: URL.self) { url, _ in
guard let url = url else { return }
let path = Shell.escape(url.path)
DispatchQueue.main.async {
surfaceView.insertText(
path,
replacementRange: NSMakeRange(0, 0)
)
}
}
}
return true
}
#endif #endif
// If our geo size changed then we show the resize overlay as configured. // If our geo size changed then we show the resize overlay as configured.

View File

@ -1,3 +1,4 @@
import AppKit
import SwiftUI import SwiftUI
import CoreText import CoreText
import UserNotifications import UserNotifications
@ -230,6 +231,9 @@ extension Ghostty {
ghostty_surface_set_color_scheme(surface, scheme) ghostty_surface_set_color_scheme(surface, scheme)
} }
// The UTTypes that can be dragged onto this view.
registerForDraggedTypes(Array(Self.dropTypes))
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -1509,3 +1513,62 @@ extension Ghostty.SurfaceView: NSMenuItemValidation {
} }
} }
} }
// MARK: NSDraggingDestination
extension Ghostty.SurfaceView {
static let dropTypes: Set<NSPasteboard.PasteboardType> = [
.string,
.fileURL,
.URL
]
override func draggingEntered(_ sender: any NSDraggingInfo) -> NSDragOperation {
guard let types = sender.draggingPasteboard.types else { return [] }
// If the dragging object contains none of our types then we return none.
// This shouldn't happen because AppKit should guarantee that we only
// receive types we registered for but its good to check.
if Set(types).isDisjoint(with: Self.dropTypes) {
return []
}
// We use copy to get the proper icon
return .copy
}
override func performDragOperation(_ sender: any NSDraggingInfo) -> Bool {
let pb = sender.draggingPasteboard
let content: String?
if let url = pb.string(forType: .URL) {
// URLs first, they get escaped as-is.
content = Ghostty.Shell.escape(url)
} else if let urls = pb.readObjects(forClasses: [NSURL.self]) as? [URL],
urls.count > 0 {
// File URLs next. They get escaped individually and then joined by a
// space if there are multiple.
content = urls
.map { Ghostty.Shell.escape($0.path) }
.joined(separator: " ")
} else if let str = pb.string(forType: .string) {
// Strings are not escaped because they may be copy/pasting a
// command they want to execute.
content = str
} else {
content = nil
}
if let content {
DispatchQueue.main.async {
self.insertText(
content,
replacementRange: NSMakeRange(0, 0)
)
}
return true
}
return false
}
}