diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index 848c63639db..f1d6ca5da4b 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -589,7 +589,7 @@ impl App { // [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783 // [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216 let pasted = pasted.replace("\r", "\n"); - self.chat_widget.handle_paste_event(pasted); + self.chat_widget.handle_paste(pasted); } TuiEvent::Draw => { self.chat_widget.maybe_post_pending_notification(tui); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index dbec3b47f60..65df3e6f96f 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1827,10 +1827,26 @@ impl ChatWidget { modifiers, kind: KeyEventKind::Press, .. - } if c.eq_ignore_ascii_case(&'v') - && modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) => + } if modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) + && c.eq_ignore_ascii_case(&'v') => { - self.paste_image_from_clipboard(); + match paste_image_to_temp_png() { + Ok((path, info)) => { + tracing::debug!( + "pasted image size={}x{} format={}", + info.width, + info.height, + info.encoded_format.label() + ); + self.attach_image(path); + } + Err(err) => { + tracing::warn!("failed to paste image: {err}"); + self.add_to_history(history_cell::new_error_event(format!( + "Failed to paste image: {err}", + ))); + } + } return; } other if other.kind == KeyEventKind::Press => { @@ -1899,32 +1915,6 @@ impl ChatWidget { self.request_redraw(); } - /// Attempt to attach an image from the system clipboard. - /// - /// This is a best-effort path used when we receive an empty paste event, - /// which some terminals emit when the clipboard contains non-text data - /// (like images). When the clipboard can't be read or no image exists, - /// surface a helpful follow-up so the user can retry with a file path. - fn paste_image_from_clipboard(&mut self) { - match paste_image_to_temp_png() { - Ok((path, info)) => { - tracing::debug!( - "pasted image size={}x{} format={}", - info.width, - info.height, - info.encoded_format.label() - ); - self.attach_image(path); - } - Err(err) => { - tracing::warn!("failed to paste image: {err}"); - self.add_to_history(history_cell::new_error_event(format!( - "Failed to paste image: {err}. Try saving the image to a file and pasting the file path instead.", - ))); - } - } - } - pub(crate) fn composer_text_with_pending(&self) -> String { self.bottom_pane.composer_text_with_pending() } @@ -2179,20 +2169,6 @@ impl ChatWidget { self.bottom_pane.handle_paste(text); } - /// Route paste events through image detection. - /// - /// Terminals vary in how they represent paste: some emit an empty paste - /// payload when the clipboard isn't text (common for image-only clipboard - /// contents). Treat the empty payload as a hint to attempt a clipboard - /// image read; otherwise, fall back to text handling. - pub(crate) fn handle_paste_event(&mut self, text: String) { - if text.is_empty() { - self.paste_image_from_clipboard(); - } else { - self.handle_paste(text); - } - } - // Returns true if caller should skip rendering this frame (a future frame is scheduled). pub(crate) fn handle_paste_burst_tick(&mut self, frame_requester: FrameRequester) -> bool { if self.bottom_pane.flush_paste_burst_if_due() {