From 2351d9e1b608e6f9d9a3699b71d7eb39ee41cfa4 Mon Sep 17 00:00:00 2001 From: Benjamin Carlsson Date: Fri, 9 Oct 2026 04:59:32 +0000 Subject: [PATCH] Avoid an extra blank quote line when pasting text with a trailing newline (#52418) ## Why Pasting text ending in a newline into a composer line beginning with `> ` adds an unnecessary empty quoted line before the next Markdown block. ## What changed Remove one trailing newline from nonempty blockquote paste content before adding quote prefixes. Preserve interior blank lines and the two unquoted newlines that move the cursor to the next block. ## Testing Add a trailing-newline regression case, update the CRLF/CR expectations, and exercise trailing-newline input in the existing rendering snapshot test. GitOrigin-RevId: 2367efa8253dd0512de8ba6333b39eba6381fba8 --- .../chat_composer/blockquote_paste_tests.rs | 5 +++-- .../tui/src/bottom_pane/chat_composer/paste_input.rs | 11 ++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) 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