diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index c6169144ab..a947f240cf 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -780,11 +780,12 @@ impl App { self.handle_key_event(tui, app_server, key_event).await; } TuiEvent::Paste(pasted) => { - // Many terminals convert newlines to \r when pasting (e.g., iTerm2), - // but tui-textarea expects \n. Normalize CR to LF. + // Pasted text may contain CRLF pairs or bare CRs (e.g., from iTerm2), + // but tui-textarea expects LF. Normalize CRLF pairs before bare CRs so + // each pasted line break becomes one LF and existing LFs stay unchanged. // [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"); + let pasted = pasted.replace("\r\n", "\n").replace('\r', "\n"); self.chat_widget.handle_paste(pasted); } TuiEvent::Draw | TuiEvent::Resume | TuiEvent::Resize(_) => { diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index 6186ca8267..8069a02172 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -153,6 +153,29 @@ fn test_absolute_path(path: &str) -> AbsolutePathBuf { AbsolutePathBuf::try_from(PathBuf::from(path)).expect("absolute test path") } +#[tokio::test] +async fn pasted_text_normalizes_mixed_line_endings_at_app_boundary() -> Result<()> { + let mut app = make_test_app().await; + let mut app_server = start_config_write_test_app_server(&app).await?; + let mut tui = crate::tui::test_support::make_test_tui()?; + + app.handle_tui_event( + &mut tui, + &mut app_server, + TuiEvent::Paste("line1\r\nline2\rline3\nline4".to_string()), + ) + .await?; + + assert_snapshot!(app.chat_widget.composer_text_with_pending(), @r" + line1 + line2 + line3 + line4 + "); + + Ok(()) +} + #[tokio::test] async fn chat_widget_frame_reuses_active_cell_height_across_frame_passes() { #[derive(Debug)]