From c6e8671b2a3a959750883a4345622db87f528b5e Mon Sep 17 00:00:00 2001 From: Ahmed Ibrahim Date: Tue, 23 Sep 2025 10:37:14 -0700 Subject: [PATCH] Refactor codex card layout (#4069) Refactor it to be used in status --- codex-rs/tui/src/history_cell.rs | 144 +++++++++++++++---------------- 1 file changed, 68 insertions(+), 76 deletions(-) diff --git a/codex-rs/tui/src/history_cell.rs b/codex-rs/tui/src/history_cell.rs index a4430ba6..1a55b2a8 100644 --- a/codex-rs/tui/src/history_cell.rs +++ b/codex-rs/tui/src/history_cell.rs @@ -633,6 +633,50 @@ impl HistoryCell for CompletedMcpToolCallWithImageOutput { const TOOL_CALL_MAX_LINES: usize = 5; const SESSION_HEADER_MAX_INNER_WIDTH: usize = 56; // Just an eyeballed value +fn card_inner_width(width: u16, max_inner_width: usize) -> Option { + if width < 4 { + return None; + } + let inner_width = std::cmp::min(width.saturating_sub(4) as usize, max_inner_width); + Some(inner_width) +} + +fn with_border(lines: Vec>) -> Vec> { + let content_width = lines + .iter() + .map(|line| { + line.iter() + .map(|span| UnicodeWidthStr::width(span.content.as_ref())) + .sum::() + }) + .max() + .unwrap_or(0); + + let mut out = Vec::with_capacity(lines.len() + 2); + let border_inner_width = content_width + 2; + out.push(vec![format!("╭{}╮", "─".repeat(border_inner_width)).dim()].into()); + + for line in lines.into_iter() { + let used_width: usize = line + .iter() + .map(|span| UnicodeWidthStr::width(span.content.as_ref())) + .sum(); + let span_count = line.spans.len(); + let mut spans: Vec> = Vec::with_capacity(span_count + 4); + spans.push(Span::from("│ ").dim()); + spans.extend(line.into_iter()); + if used_width < content_width { + spans.push(Span::from(" ".repeat(content_width - used_width)).dim()); + } + spans.push(Span::from(" │").dim()); + out.push(Line::from(spans)); + } + + out.push(vec![format!("╰{}╯", "─".repeat(border_inner_width)).dim()].into()); + + out +} + fn title_case(s: &str) -> String { if s.is_empty() { return String::new(); @@ -816,46 +860,20 @@ impl SessionHeaderHistoryCell { impl HistoryCell for SessionHeaderHistoryCell { fn display_lines(&self, width: u16) -> Vec> { - let mut out: Vec> = Vec::new(); - if width < 4 { - return out; - } + let Some(inner_width) = card_inner_width(width, SESSION_HEADER_MAX_INNER_WIDTH) else { + return Vec::new(); + }; - let inner_width = std::cmp::min( - width.saturating_sub(2) as usize, - SESSION_HEADER_MAX_INNER_WIDTH, - ); - // Top border without a title on the border - let mut top = String::with_capacity(inner_width + 2); - top.push('╭'); - top.push_str(&"─".repeat(inner_width)); - top.push('╮'); - out.push(Line::from(top.dim())); + let make_row = |spans: Vec>| Line::from(spans); - // Title line rendered inside the box: " >_ OpenAI Codex (vX)" - let title_text = format!(" >_ OpenAI Codex (v{})", self.version); - let title_w = UnicodeWidthStr::width(title_text.as_str()); - let pad_w = inner_width.saturating_sub(title_w); - let mut title_spans: Vec> = vec![ - Span::from("│").dim(), - Span::from(" ").dim(), + // Title line rendered inside the box: ">_ OpenAI Codex (vX)" + let title_spans: Vec> = vec![ Span::from(">_ ").dim(), Span::from("OpenAI Codex").bold(), Span::from(" ").dim(), Span::from(format!("(v{})", self.version)).dim(), ]; - if pad_w > 0 { - title_spans.push(Span::from(" ".repeat(pad_w)).dim()); - } - title_spans.push(Span::from("│").dim()); - out.push(Line::from(title_spans)); - // Spacer row between title and details - out.push(Line::from(vec![ - Span::from(format!("│{}│", " ".repeat(inner_width))).dim(), - ])); - - // Model line: " model: (change with /model)" const CHANGE_MODEL_HINT_COMMAND: &str = "/model"; const CHANGE_MODEL_HINT_EXPLANATION: &str = " to change"; const DIR_LABEL: &str = "directory:"; @@ -866,59 +884,33 @@ impl HistoryCell for SessionHeaderHistoryCell { label_width = label_width ); let reasoning_label = self.reasoning_label(); - let mut model_value_for_width = self.model.clone(); - if let Some(reasoning) = reasoning_label { - model_value_for_width.push(' '); - model_value_for_width.push_str(reasoning); - } - let model_text_for_width_calc = format!( - " {model_label} {model_value_for_width} {CHANGE_MODEL_HINT_COMMAND}{CHANGE_MODEL_HINT_EXPLANATION}", - ); - let model_w = UnicodeWidthStr::width(model_text_for_width_calc.as_str()); - let pad_w = inner_width.saturating_sub(model_w); - let mut spans: Vec> = vec![ - Span::from(format!("│ {model_label} ")).dim(), + let mut model_spans: Vec> = vec![ + Span::from(format!("{model_label} ")).dim(), Span::from(self.model.clone()), ]; if let Some(reasoning) = reasoning_label { - spans.push(Span::from(" ")); - spans.push(Span::from(reasoning)); + model_spans.push(Span::from(" ")); + model_spans.push(Span::from(reasoning)); } - spans.push(Span::from(" ").dim()); - spans.push(Span::from(CHANGE_MODEL_HINT_COMMAND).cyan()); - spans.push(Span::from(CHANGE_MODEL_HINT_EXPLANATION).dim()); - if pad_w > 0 { - spans.push(Span::from(" ".repeat(pad_w)).dim()); - } - spans.push(Span::from("│").dim()); - out.push(Line::from(spans)); + model_spans.push(" ".dim()); + model_spans.push(CHANGE_MODEL_HINT_COMMAND.cyan()); + model_spans.push(CHANGE_MODEL_HINT_EXPLANATION.dim()); - // Directory line: " Directory: " let dir_label = format!("{DIR_LABEL:> = vec![ - Span::from("│").dim(), - Span::from(" ").dim(), - Span::from(dir_label).dim(), - Span::from(" ").dim(), - Span::from(dir), + let dir_spans = vec![Span::from(dir_prefix).dim(), Span::from(dir)]; + + let lines = vec![ + make_row(title_spans), + make_row(Vec::new()), + make_row(model_spans), + make_row(dir_spans), ]; - if pad_w > 0 { - spans.push(Span::from(" ".repeat(pad_w)).dim()); - } - spans.push(Span::from("│").dim()); - out.push(Line::from(spans)); - // Bottom border - let bottom = format!("╰{}╯", "─".repeat(inner_width)); - out.push(Line::from(bottom.dim())); - - out + with_border(lines) } }