From 7ffd1d00be59629eb33444386900cd116fc0008a Mon Sep 17 00:00:00 2001 From: zhao-oai Date: Sun, 26 Oct 2025 15:44:36 -0700 Subject: [PATCH] Fix macOS image drag-and-drop detection --- codex-rs/tui/src/clipboard_paste.rs | 99 ++++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/codex-rs/tui/src/clipboard_paste.rs b/codex-rs/tui/src/clipboard_paste.rs index 4945eea350..d9ada96885 100644 --- a/codex-rs/tui/src/clipboard_paste.rs +++ b/codex-rs/tui/src/clipboard_paste.rs @@ -185,14 +185,79 @@ pub fn normalize_pasted_path(pasted: &str) -> Option { } // shell-escaped single path → unescaped - let parts: Vec = shlex::Shlex::new(pasted).collect(); - if parts.len() == 1 { - return parts.into_iter().next().map(PathBuf::from); + let mut shlex = shlex::Shlex::new(pasted); + let parts: Vec = shlex.by_ref().collect(); + match parts.len() { + 0 => {} + 1 => return parts.into_iter().next().map(PathBuf::from), + _ => {} + } + + if is_probably_posix_shell_escaped_path(pasted) { + let unescaped = unescape_posix_backslashes(pasted); + return Some(PathBuf::from(unescaped)); } None } +fn is_probably_posix_shell_escaped_path(pasted: &str) -> bool { + pasted.contains('/') + && pasted.contains('\\') + && !pasted.contains('"') + && !pasted.contains('\'') + && !has_unescaped_ascii_whitespace(pasted) +} + +fn has_unescaped_ascii_whitespace(pasted: &str) -> bool { + let mut escaped = false; + let mut in_single_quote = false; + let mut in_double_quote = false; + for ch in pasted.chars() { + if escaped { + escaped = false; + continue; + } + match ch { + '\\' if !in_single_quote => { + escaped = true; + } + '\'' if !in_double_quote => { + in_single_quote = !in_single_quote; + } + '"' if !in_single_quote => { + in_double_quote = !in_double_quote; + } + ' ' | '\t' if !in_single_quote && !in_double_quote => { + return true; + } + _ => {} + } + } + false +} + +fn unescape_posix_backslashes(pasted: &str) -> String { + let mut out = String::with_capacity(pasted.len()); + let mut escaped = false; + for ch in pasted.chars() { + if escaped { + out.push(ch); + escaped = false; + continue; + } + if ch == '\\' { + escaped = true; + continue; + } + out.push(ch); + } + if escaped { + out.push('\\'); + } + out +} + /// Infer an image format for the provided path based on its extension. pub fn pasted_image_format(path: &Path) -> EncodedImageFormat { match path @@ -255,6 +320,34 @@ mod pasted_paths_tests { assert!(result.is_none()); } + #[cfg(not(windows))] + #[test] + fn normalize_dragged_macos_screenshot_path() { + let input = "/Users/zhao/Downloads/Screenshot\\ 2025-09-25\\ at\\ 10.47.39\u{202F}AM.png "; + let result = normalize_pasted_path(input).expect("should parse Finder screenshot path"); + assert_eq!( + result, + PathBuf::from("/Users/zhao/Downloads/Screenshot 2025-09-25 at 10.47.39\u{202F}AM.png") + ); + } + + #[cfg(not(windows))] + #[test] + fn normalize_dragged_macos_temporary_screenshot_path() { + let input = concat!( + "/var/folders/mr/tt5wpmvs1w5b0td1bj93t0800000gp/T/TemporaryItems/", + "NSIRD_screencaptureui_SBcoDE/Screenshot\\ 2025-10-26\\ at\\ 2.10.40\u{202F}PM.png " + ); + let result = normalize_pasted_path(input).expect("should parse temporary screenshot path"); + assert_eq!( + result, + PathBuf::from(concat!( + "/var/folders/mr/tt5wpmvs1w5b0td1bj93t0800000gp/T/TemporaryItems/", + "NSIRD_screencaptureui_SBcoDE/Screenshot 2025-10-26 at 2.10.40\u{202F}PM.png" + )) + ); + } + #[test] fn pasted_image_format_png_jpeg_unknown() { assert_eq!(