From ec4cf9f5d3094653c5c02b6d6432429d0c080315 Mon Sep 17 00:00:00 2001 From: Jeremy Rose Date: Mon, 4 Aug 2025 14:26:08 -0700 Subject: [PATCH] wip --- codex-rs/tui/src/chatwidget.rs | 1 + codex-rs/tui/src/history_cell.rs | 87 ++++++++++++++++++++++++++------ codex-rs/tui/src/text_block.rs | 23 +++++++++ 3 files changed, 95 insertions(+), 16 deletions(-) diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 23e73cf82d..b0e558c6db 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -42,6 +42,7 @@ use crate::bottom_pane::CancellationEvent; use crate::bottom_pane::InputResult; use crate::exec_command::strip_bash_lc_and_escape; use crate::history_cell::CommandOutput; +use crate::history_cell::DynamicHeightWidgetRef; use crate::history_cell::HistoryCell; use crate::history_cell::PatchEventType; use crate::user_approval_widget::ApprovalRequest; diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index 3ca836ec59..5f23fab46e 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -78,7 +78,7 @@ pub(crate) enum HistoryCell { AgentReasoning { view: TextBlock }, /// An exec tool call that has not finished yet. - ActiveExecCommand { view: TextBlock }, + ActiveExecCommand { command: String }, /// Completed exec tool call. CompletedExecCommand { view: TextBlock }, @@ -123,6 +123,10 @@ pub(crate) enum HistoryCell { const TOOL_CALL_MAX_LINES: usize = 5; +pub trait DynamicHeightWidgetRef: WidgetRef { + fn desired_height(&self, width: u16) -> u16; +} + impl HistoryCell { /// Return a cloned, plain representation of the cell's lines suitable for /// one‑shot insertion into the terminal scrollback. Image cells are @@ -141,10 +145,17 @@ impl HistoryCell { | HistoryCell::CompletedMcpToolCall { view } | HistoryCell::PendingPatch { view } | HistoryCell::PlanUpdate { view } - | HistoryCell::ActiveExecCommand { view, .. } | HistoryCell::ActiveMcpToolCall { view, .. } => { view.lines.iter().map(line_to_static).collect() } + HistoryCell::ActiveExecCommand { command, .. } => { + let lines: Vec> = vec![ + Line::from(vec!["command".magenta(), " running...".dim()]), + Line::from(format!("$ {command}")), + Line::from(""), + ]; + lines.iter().map(line_to_static).collect() + } HistoryCell::CompletedMcpToolCallWithImageOutput { .. } => vec![ Line::from("tool result (image output omitted)"), Line::from(""), @@ -152,12 +163,28 @@ 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) + fn view(&self) -> Box { + match self { + HistoryCell::WelcomeMessage { view } + | HistoryCell::UserPrompt { view } + | HistoryCell::AgentMessage { view } + | HistoryCell::AgentReasoning { view } + | HistoryCell::BackgroundEvent { view } + | HistoryCell::GitDiffOutput { view } + | HistoryCell::ErrorEvent { view } + | HistoryCell::SessionInfo { view } + | HistoryCell::CompletedExecCommand { view } + | HistoryCell::CompletedMcpToolCall { view } + | HistoryCell::PendingPatch { view } + | HistoryCell::PlanUpdate { view } + | HistoryCell::ActiveMcpToolCall { view, .. } => Box::new(view), + HistoryCell::ActiveExecCommand { command, .. } => Box::new(ActiveExecCommandView { + command: command.clone(), + }), + HistoryCell::CompletedMcpToolCallWithImageOutput { .. } => { + panic!("view() called on image output cell") + } + } } pub(crate) fn new_session_info( @@ -268,14 +295,8 @@ impl HistoryCell { pub(crate) fn new_active_exec_command(command: Vec) -> Self { let command_escaped = strip_bash_lc_and_escape(&command); - let lines: Vec> = vec![ - Line::from(vec!["command".magenta(), " running...".dim()]), - Line::from(format!("$ {command_escaped}")), - Line::from(""), - ]; - HistoryCell::ActiveExecCommand { - view: TextBlock::new(lines), + command: command_escaped, } } @@ -643,9 +664,43 @@ impl HistoryCell { } } +impl DynamicHeightWidgetRef for &HistoryCell { + fn desired_height(&self, width: u16) -> u16 { + self.view().desired_height(width) + } +} + impl WidgetRef for &HistoryCell { fn render_ref(&self, area: Rect, buf: &mut Buffer) { - Paragraph::new(Text::from(self.plain_lines())) + self.view().render_ref(area, buf); + } +} + +struct ActiveExecCommandView { + command: String, +} +impl DynamicHeightWidgetRef for ActiveExecCommandView { + fn desired_height(&self, width: u16) -> u16 { + let lines: Vec> = vec![ + Line::from(vec!["command".yellow(), " running...".dim()]), + Line::from(format!("$ {}", self.command)), + Line::from(""), + ]; + Paragraph::new(Text::from(lines)) + .wrap(Wrap { trim: false }) + .line_count(width) + .try_into() + .unwrap_or(0) + } +} +impl WidgetRef for ActiveExecCommandView { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + let lines: Vec> = vec![ + Line::from(vec!["command".yellow(), " running...".dim()]), + Line::from(format!("$ {}", self.command)), + Line::from(""), + ]; + Paragraph::new(Text::from(lines)) .wrap(Wrap { trim: false }) .render(area, buf); } diff --git a/codex-rs/tui/src/text_block.rs b/codex-rs/tui/src/text_block.rs index 33f326b83d..8054e46530 100644 --- a/codex-rs/tui/src/text_block.rs +++ b/codex-rs/tui/src/text_block.rs @@ -1,4 +1,9 @@ use ratatui::prelude::*; +use ratatui::widgets::Paragraph; +use ratatui::widgets::WidgetRef; +use ratatui::widgets::Wrap; + +use crate::history_cell::DynamicHeightWidgetRef; /// A simple widget that just displays a list of `Line`s via a `Paragraph`. /// This is the default rendering backend for most `HistoryCell` variants. @@ -12,3 +17,21 @@ impl TextBlock { Self { lines } } } + +impl DynamicHeightWidgetRef for &TextBlock { + fn desired_height(&self, width: u16) -> u16 { + Paragraph::new(Text::from(self.lines.clone())) + .wrap(Wrap { trim: false }) + .line_count(width) + .try_into() + .unwrap_or(0) + } +} + +impl WidgetRef for &TextBlock { + fn render_ref(&self, area: Rect, buf: &mut Buffer) { + Paragraph::new(Text::from(self.lines.clone())) + .wrap(Wrap { trim: false }) + .render(area, buf); + } +}