diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index d03a51bd70..d3f0e2d2d3 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -769,7 +769,10 @@ impl ChatWidget<'_> { return; } // Flush any partial line as a full row, then drain all remaining rows. - self.live_builder.end_line(); + // Only call end_line() if there's content to flush to avoid adding empty rows. + if !self.live_builder.is_current_line_empty() { + self.live_builder.end_line(); + } let remaining = self.live_builder.drain_rows(); // TODO: Re-add markdown rendering for assistant answers and reasoning. // When finalizing, pass the accumulated text through `markdown::append_markdown` diff --git a/codex-rs/tui/src/live_wrap.rs b/codex-rs/tui/src/live_wrap.rs index e78710dc6c..023231152a 100644 --- a/codex-rs/tui/src/live_wrap.rs +++ b/codex-rs/tui/src/live_wrap.rs @@ -91,6 +91,11 @@ impl RowBuilder { &self.rows } + /// Check if the current line buffer is empty. + pub fn is_current_line_empty(&self) -> bool { + self.current_line.is_empty() + } + /// Rows suitable for display, including the current partial line if any. pub fn display_rows(&self) -> Vec { let mut out = self.rows.clone(); @@ -287,4 +292,27 @@ mod tests { assert!(r.width() <= 5); } } + + #[test] + fn is_current_line_empty_behaves_correctly() { + let mut rb = RowBuilder::new(10); + // Initially empty + assert!(rb.is_current_line_empty()); + + // After adding text, not empty + rb.push_fragment("hello"); + assert!(!rb.is_current_line_empty()); + + // After a newline, empty again + rb.push_fragment("\n"); + assert!(rb.is_current_line_empty()); + + // After adding more text, not empty again + rb.push_fragment("world"); + assert!(!rb.is_current_line_empty()); + + // After ending the line, empty again + rb.end_line(); + assert!(rb.is_current_line_empty()); + } }