gpui: Convert macOS clipboard file URLs to paths for paste (#36848)

Sean Timm created

- On macOS, pasting now inserts the actual file path when the clipboard
contains a file URL (public.file-url/public.url)
- Terminal paste remains text-only; no temp files or data URLs are
created. If only raw image bytes exist on the clipboard, paste is a
no-op.
- Scope: macOS only; no dependency changes.
- Added a test (test_file_url_converts_to_path) that verifies URL→path
conversion using a unique pasteboard.

Release Notes:

- Improved pasting on macOS: now inserts the actual file path when the
clipboard contains a file URL (enables image paste support for Claude
Code)

Change summary

crates/gpui/src/platform/mac/platform.rs | 63 +++++++++++++++++++++++++
1 file changed, 61 insertions(+), 2 deletions(-)

Detailed changes

crates/gpui/src/platform/mac/platform.rs 🔗

@@ -1124,7 +1124,32 @@ impl Platform for MacPlatform {
                 }
             }
 
-            // If it wasn't a string, try the various supported image types.
+            // Next, check for URL flavors (including file URLs). Some tools only provide a URL
+            // with no plain text entry.
+            {
+                // Try the modern UTType identifiers first.
+                let file_url_type: id = ns_string("public.file-url");
+                let url_type: id = ns_string("public.url");
+
+                let url_data = if msg_send![types, containsObject: file_url_type] {
+                    pasteboard.dataForType(file_url_type)
+                } else if msg_send![types, containsObject: url_type] {
+                    pasteboard.dataForType(url_type)
+                } else {
+                    nil
+                };
+
+                if url_data != nil && !url_data.bytes().is_null() {
+                    let bytes = slice::from_raw_parts(
+                        url_data.bytes() as *mut u8,
+                        url_data.length() as usize,
+                    );
+
+                    return Some(self.read_string_from_clipboard(&state, bytes));
+                }
+            }
+
+            // If it wasn't a string or URL, try the various supported image types.
             for format in ImageFormat::iter() {
                 if let Some(item) = try_clipboard_image(pasteboard, format) {
                     return Some(item);
@@ -1132,7 +1157,7 @@ impl Platform for MacPlatform {
             }
         }
 
-        // If it wasn't a string or a supported image type, give up.
+        // If it wasn't a string, URL, or a supported image type, give up.
         None
     }
 
@@ -1707,6 +1732,40 @@ mod tests {
         );
     }
 
+    #[test]
+    fn test_file_url_reads_as_url_string() {
+        let platform = build_platform();
+
+        // Create a file URL for an arbitrary test path and write it to the pasteboard.
+        // This path does not need to exist; we only validate URL→path conversion.
+        let mock_path = "/tmp/zed-clipboard-file-url-test";
+        unsafe {
+            // Build an NSURL from the file path
+            let url: id = msg_send![class!(NSURL), fileURLWithPath: ns_string(mock_path)];
+            let abs: id = msg_send![url, absoluteString];
+
+            // Encode the URL string as UTF-8 bytes
+            let len: usize = msg_send![abs, lengthOfBytesUsingEncoding: NSUTF8StringEncoding];
+            let bytes_ptr = abs.UTF8String() as *const u8;
+            let data = NSData::dataWithBytes_length_(nil, bytes_ptr as *const c_void, len as u64);
+
+            // Write as public.file-url to the unique pasteboard
+            let file_url_type: id = ns_string("public.file-url");
+            platform
+                .0
+                .lock()
+                .pasteboard
+                .setData_forType(data, file_url_type);
+        }
+
+        // Ensure the clipboard read returns the URL string, not a converted path
+        let expected_url = format!("file://{}", mock_path);
+        assert_eq!(
+            platform.read_from_clipboard(),
+            Some(ClipboardItem::new_string(expected_url))
+        );
+    }
+
     fn build_platform() -> MacPlatform {
         let platform = MacPlatform::new(false);
         platform.0.lock().pasteboard = unsafe { NSPasteboard::pasteboardWithUniqueName(nil) };