From 2195e6956e56cb51206fc35c23e354ab82971df5 Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Mon, 4 Aug 2025 11:26:47 -0700 Subject: [PATCH] show a transient history cell for commands --- codex-rs/tui/src/chatwidget.rs | 43 ++++++++++++++++++++++++++------ codex-rs/tui/src/history_cell.rs | 20 +++++++++++++++ 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index e5ebf58a07..acf1871787 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -26,6 +26,8 @@ use codex_core::protocol::TokenUsage; use crossterm::event::KeyEvent; use crossterm::event::KeyEventKind; use ratatui::buffer::Buffer; +use ratatui::layout::Constraint; +use ratatui::layout::Layout; use ratatui::layout::Rect; use ratatui::widgets::Widget; use ratatui::widgets::WidgetRef; @@ -55,6 +57,7 @@ pub(crate) struct ChatWidget<'a> { app_event_tx: AppEventSender, codex_op_tx: UnboundedSender, bottom_pane: BottomPane<'a>, + active_history_cell: Option, config: Config, initial_user_message: Option, token_usage: TokenUsage, @@ -142,6 +145,7 @@ impl ChatWidget<'_> { has_input_focus: true, enhanced_keys_supported, }), + active_history_cell: None, config, initial_user_message: create_initial_user_message( initial_prompt.unwrap_or_default(), @@ -156,6 +160,10 @@ impl ChatWidget<'_> { pub fn desired_height(&self, width: u16) -> u16 { self.bottom_pane.desired_height(width) + + self + .active_history_cell + .as_ref() + .map_or(0, |c| c.desired_height(width)) } pub(crate) fn handle_key_event(&mut self, key_event: KeyEvent) { @@ -371,9 +379,11 @@ impl ChatWidget<'_> { cwd: cwd.clone(), }, ); - self.add_to_history(HistoryCell::new_active_exec_command(command)); + self.active_history_cell = Some(HistoryCell::new_active_exec_command(command)); + } + EventMsg::ExecCommandOutputDelta(_) => { + // TODO } - EventMsg::ExecCommandOutputDelta(_) => {} EventMsg::PatchApplyBegin(PatchApplyBeginEvent { call_id: _, auto_approved, @@ -394,6 +404,7 @@ impl ChatWidget<'_> { stderr, }) => { let cmd = self.running_commands.remove(&call_id); + self.active_history_cell = None; self.add_to_history(HistoryCell::new_completed_exec_command( cmd.map(|cmd| cmd.command).unwrap_or_else(|| vec![call_id]), CommandOutput { @@ -511,16 +522,34 @@ impl ChatWidget<'_> { } pub fn cursor_pos(&self, area: Rect) -> Option<(u16, u16)> { - self.bottom_pane.cursor_pos(area) + let [_, bottom_pane_area] = Layout::vertical([ + Constraint::Max( + self.active_history_cell + .as_ref() + .map_or(0, |c| c.desired_height(area.width)), + ), + Constraint::Min(self.bottom_pane.desired_height(area.width)), + ]) + .areas(area); + self.bottom_pane.cursor_pos(bottom_pane_area) } } impl WidgetRef for &ChatWidget<'_> { fn render_ref(&self, area: Rect, buf: &mut Buffer) { - // In the hybrid inline viewport mode we only draw the interactive - // bottom pane; history entries are injected directly into scrollback - // via `Terminal::insert_before`. - (&self.bottom_pane).render(area, buf); + let [active_cell_area, bottom_pane_area] = Layout::vertical([ + Constraint::Max( + self.active_history_cell + .as_ref() + .map_or(0, |c| c.desired_height(area.width)), + ), + Constraint::Min(self.bottom_pane.desired_height(area.width)), + ]) + .areas(area); + (&self.bottom_pane).render(bottom_pane_area, buf); + if let Some(cell) = &self.active_history_cell { + cell.render_ref(active_cell_area, buf); + } } } diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index c2aafdd522..3ca836ec59 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -25,6 +25,9 @@ use ratatui::style::Modifier; use ratatui::style::Style; use ratatui::text::Line as RtLine; use ratatui::text::Span as RtSpan; +use ratatui::widgets::Paragraph; +use ratatui::widgets::WidgetRef; +use ratatui::widgets::Wrap; use std::collections::HashMap; use std::io::Cursor; use std::path::PathBuf; @@ -148,6 +151,15 @@ impl HistoryCell { ], } } + + pub(crate) fn desired_height(&self, width: u16) -> u16 { + Paragraph::new(Text::from(self.plain_lines())) + .wrap(Wrap { trim: false }) + .line_count(width) + .try_into() + .unwrap_or(0) + } + pub(crate) fn new_session_info( config: &Config, event: SessionConfiguredEvent, @@ -631,6 +643,14 @@ impl HistoryCell { } } +impl WidgetRef for &HistoryCell { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + Paragraph::new(Text::from(self.plain_lines())) + .wrap(Wrap { trim: false }) + .render(area, buf); + } +} + fn create_diff_summary(changes: HashMap) -> Vec { // Build a concise, human‑readable summary list similar to the // `git status` short format so the user can reason about the