From d1ac0aff39f4512047489039d7e3c8f917e34fbe Mon Sep 17 00:00:00 2001 From: Qwerasd Date: Thu, 7 Mar 2024 20:53:48 -0500 Subject: [PATCH] feat(macOS): Paste copied files as absolute paths. Previously files would be pasted as only the filename. This commit introduces an extension to NSPasteboard which provides a method to consistently get the string contents of a pasteboard so that the behavior can stay the same anywhere where we need to do that. --- macos/Ghostty.xcodeproj/project.pbxproj | 4 ++++ macos/Sources/Ghostty/Ghostty.App.swift | 2 +- macos/Sources/Ghostty/SurfaceView_AppKit.swift | 3 ++- .../Helpers/NSPasteboard+Extension.swift | 18 ++++++++++++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 macos/Sources/Helpers/NSPasteboard+Extension.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index cf8f7bbbf..fa0b5e263 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -66,6 +66,7 @@ A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; }; A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; }; A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; }; + AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */; }; AEF9CE242B6AD07A0017E195 /* TerminalToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */; }; C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; }; C159E89D2B69A2EF00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; }; @@ -130,6 +131,7 @@ A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationController.swift; sourceTree = ""; }; A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationView.swift; sourceTree = ""; }; A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; + AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPasteboard+Extension.swift"; sourceTree = ""; }; AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalToolbar.swift; sourceTree = ""; }; C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = ""; }; C1F26EA62B738B9900404083 /* NSView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSView+Extension.swift"; sourceTree = ""; }; @@ -202,6 +204,7 @@ A59FB5D02AE0DEA7009128F3 /* MetalView.swift */, C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */, C1F26EA62B738B9900404083 /* NSView+Extension.swift */, + AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */, C1F26EE72B76CBFC00404083 /* VibrantLayer.h */, C1F26EE82B76CBFC00404083 /* VibrantLayer.m */, A5CEAFDA29B8005900646FDA /* SplitView */, @@ -492,6 +495,7 @@ A59630972AEE163600D64628 /* HostingWindow.swift in Sources */, A59630A02AEF6AEB00D64628 /* TerminalManager.swift in Sources */, A51BFC2B2B30F6BE00E92F16 /* UpdateDelegate.swift in Sources */, + AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */, A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */, A5333E222B5A2128008AEFF7 /* SurfaceView_AppKit.swift in Sources */, A5CDF1952AAFA19600513312 /* ConfigurationErrorsView.swift in Sources */, diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index 1be89ea4e..2e991ecba 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -382,7 +382,7 @@ extension Ghostty { } // Get our string - let str = NSPasteboard.general.string(forType: .string) ?? "" + let str = NSPasteboard.general.getOpinionatedStringContents() ?? "" completeClipboardRequest(surface, data: str, state: state) } diff --git a/macos/Sources/Ghostty/SurfaceView_AppKit.swift b/macos/Sources/Ghostty/SurfaceView_AppKit.swift index 591a6820d..1a526971f 100644 --- a/macos/Sources/Ghostty/SurfaceView_AppKit.swift +++ b/macos/Sources/Ghostty/SurfaceView_AppKit.swift @@ -1041,7 +1041,8 @@ extension Ghostty.SurfaceView: NSServicesMenuRequestor { } func readSelection(from pboard: NSPasteboard) -> Bool { - guard let str = pboard.string(forType: .string) else { return false } + guard let str = pboard.getOpinionatedStringContents() + else { return false } let len = str.utf8CString.count if (len == 0) { return true } diff --git a/macos/Sources/Helpers/NSPasteboard+Extension.swift b/macos/Sources/Helpers/NSPasteboard+Extension.swift new file mode 100644 index 000000000..2624f89f6 --- /dev/null +++ b/macos/Sources/Helpers/NSPasteboard+Extension.swift @@ -0,0 +1,18 @@ +import AppKit + +extension NSPasteboard { + /// Gets the contents of the pasteboard as a string following a specific set of semantics. + /// Does these things in order: + /// - Tries to get the absolute filesystem path of the file in the pasteboard if there is one. + /// - Tries to get any string from the pasteboard. + /// If all of the above fail, returns None. + func getOpinionatedStringContents() -> String? { + let file = self.string(forType: .fileURL) + if let file = file { + if let path = NSURL(string: file)?.path { + return path + } + } + return self.string(forType: .string) + } +}