diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/blockquote_paste_tests.rs b/codex-rs/tui/src/bottom_pane/chat_composer/blockquote_paste_tests.rs index fcd393739a..55d67c23b0 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer/blockquote_paste_tests.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer/blockquote_paste_tests.rs @@ -9,7 +9,8 @@ use pretty_assertions::assert_eq; fn blockquote_paste_continues_current_line_prefix() { for (draft, pasted, expected) in [ ("> ", "first\nsecond", "> first\n> second\n\n"), - ("intro\n> ", "α\r\n\r\nβ\r", "intro\n> α\n> \n> β\n> \n\n"), + ("> ", "first\n", "> first\n\n"), + ("intro\n> ", "α\r\n\r\nβ\r", "intro\n> α\n> \n> β\n\n"), ( "> existing ", "first\nsecond", @@ -82,7 +83,7 @@ fn blockquote_paste_rendered() { /*enhanced_keys_supported*/ false, |composer| { composer.insert_str("Please explain:\n> "); - composer.handle_paste("first line\n\nlast line".to_string()); + composer.handle_paste("first line\n\nlast line\n".to_string()); composer.insert_str("Next block"); }, ); diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/paste_input.rs b/codex-rs/tui/src/bottom_pane/chat_composer/paste_input.rs index b3c7739776..1574e5f0e4 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer/paste_input.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer/paste_input.rs @@ -1,8 +1,9 @@ //! Classify and integrate composer pastes and capture raw paste tabs before completion or submission. //! Multiline pastes on a line beginning with `> ` continue that prefix after every newline, -//! including blank and trailing lines. This happens before large pastes are collapsed, so their -//! expanded text retains the quoting. Two unquoted newlines after the inserted text or placeholder -//! leave the cursor in the next Markdown block, including in embedded answer fields. +//! including blank lines, but not after a single trailing newline. This happens before large pastes +//! are collapsed, so their expanded text retains the quoting. Two unquoted newlines after the +//! inserted text or placeholder leave the cursor in the next Markdown block, including in embedded +//! answer fields. //! Provisional startup input, search queries, and shell input remain literal. //! Paste classification and insertion share one textarea edit target, including selection replacement. @@ -149,6 +150,10 @@ impl ChatComposer { && current_line.starts_with("> ") && pasted.contains('\n'); let pasted = if is_blockquote { + let pasted = pasted + .strip_suffix('\n') + .filter(|pasted| !pasted.is_empty()) + .unwrap_or(pasted.as_ref()); std::borrow::Cow::Owned(pasted.replace('\n', "\n> ")) } else { pasted