From 90af046c5c08da705cf7a99adc7b7b4ffd7e8daf Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Fri, 10 Oct 2025 09:56:18 -0700 Subject: [PATCH] tui: include the image name in the textarea placeholder (#5056) Fixes #5013 --- codex-rs/tui/src/bottom_pane/chat_composer.rs | 46 ++++++++++++++----- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 7aa6e669..3cd38e8d 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -319,8 +319,12 @@ impl ChatComposer { } /// Attempt to start a burst by retro-capturing recent chars before the cursor. - pub fn attach_image(&mut self, path: PathBuf, width: u32, height: u32, format_label: &str) { - let placeholder = format!("[image {width}x{height} {format_label}]"); + pub fn attach_image(&mut self, path: PathBuf, width: u32, height: u32, _format_label: &str) { + let file_label = path + .file_name() + .map(|name| name.to_string_lossy().into_owned()) + .unwrap_or_else(|| "image".to_string()); + let placeholder = format!("[{file_label} {width}x{height}]"); // Insert as an element to match large paste placeholder behavior: // styled distinctly and treated atomically for cursor/mutations. self.textarea.insert_element(&placeholder); @@ -2581,7 +2585,7 @@ mod tests { let (result, _) = composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); match result { - InputResult::Submitted(text) => assert_eq!(text, "[image 32x16 PNG] hi"), + InputResult::Submitted(text) => assert_eq!(text, "[image1.png 32x16] hi"), _ => panic!("expected Submitted"), } let imgs = composer.take_recent_submission_images(); @@ -2604,7 +2608,7 @@ mod tests { let (result, _) = composer.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE)); match result { - InputResult::Submitted(text) => assert_eq!(text, "[image 10x5 PNG]"), + InputResult::Submitted(text) => assert_eq!(text, "[image2.png 10x5]"), _ => panic!("expected Submitted"), } let imgs = composer.take_recent_submission_images(); @@ -2677,7 +2681,12 @@ mod tests { composer.handle_key_event(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE)); assert_eq!(composer.attached_images.len(), 1); - assert!(composer.textarea.text().starts_with("[image 10x5 PNG]")); + assert!( + composer + .textarea + .text() + .starts_with("[image_multibyte.png 10x5]") + ); } #[test] @@ -2700,21 +2709,31 @@ mod tests { composer.handle_paste(" ".into()); composer.attach_image(path2.clone(), 10, 5, "PNG"); - let ph = composer.attached_images[0].placeholder.clone(); + let placeholder1 = composer.attached_images[0].placeholder.clone(); + let placeholder2 = composer.attached_images[1].placeholder.clone(); let text = composer.textarea.text().to_string(); - let start1 = text.find(&ph).expect("first placeholder present"); - let end1 = start1 + ph.len(); + let start1 = text.find(&placeholder1).expect("first placeholder present"); + let end1 = start1 + placeholder1.len(); composer.textarea.set_cursor(end1); // Backspace should delete the first placeholder and its mapping. composer.handle_key_event(KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE)); let new_text = composer.textarea.text().to_string(); - assert_eq!(1, new_text.matches(&ph).count(), "one placeholder remains"); + assert_eq!( + 0, + new_text.matches(&placeholder1).count(), + "first placeholder removed" + ); + assert_eq!( + 1, + new_text.matches(&placeholder2).count(), + "second placeholder remains" + ); assert_eq!( vec![AttachedImage { path: path2, - placeholder: "[image 10x5 PNG]".to_string() + placeholder: "[image_dup2.png 10x5]".to_string() }], composer.attached_images, "one image mapping remains" @@ -2741,7 +2760,12 @@ mod tests { let needs_redraw = composer.handle_paste(tmp_path.to_string_lossy().to_string()); assert!(needs_redraw); - assert!(composer.textarea.text().starts_with("[image 3x2 PNG] ")); + assert!( + composer + .textarea + .text() + .starts_with("[codex_tui_test_paste_image.png 3x2] ") + ); let imgs = composer.take_recent_submission_images(); assert_eq!(imgs, vec![tmp_path]);