diff --git a/codex-rs/tui/src/bottom_pane/footer/footer_content.rs b/codex-rs/tui/src/bottom_pane/footer/footer_content.rs index 05ce7ddd51..5fff31b0bd 100644 --- a/codex-rs/tui/src/bottom_pane/footer/footer_content.rs +++ b/codex-rs/tui/src/bottom_pane/footer/footer_content.rs @@ -7,6 +7,7 @@ use ratatui::text::Line; use ratatui::widgets::Widget; use crate::line_truncation::truncate_line_with_ellipsis_if_overflow; +use crate::render::highlight::with_syntax_theme; use crate::terminal_hyperlinks::HyperlinkLine; use crate::ui_tree::UiTree; @@ -24,7 +25,9 @@ impl FooterContent { let width = match self { Self::Line(line) => line.width(), Self::HyperlinkLine(line) => line.width(), - Self::Extension { content } => content.render_line(u16::MAX).width(), + Self::Extension { content } => { + with_syntax_theme(|theme| content.render_line(u16::MAX, theme)).width() + } }; u16::try_from(width).unwrap_or(u16::MAX) } @@ -33,7 +36,10 @@ impl FooterContent { pub(crate) fn render(self, area: Rect, buf: &mut Buffer) { match self { Self::Line(line) => Widget::render(line, area, buf), - Self::Extension { content } => content.render_line(area.width).render_fitted(area, buf), + Self::Extension { content } => { + with_syntax_theme(|theme| content.render_line(area.width, theme)) + .render_fitted(area, buf) + } Self::HyperlinkLine(mut line) => { let area = Rect { height: area.height.min(1), @@ -55,7 +61,9 @@ impl FooterContent { match self { Self::Line(line) => line.into(), Self::HyperlinkLine(line) => line, - Self::Extension { content } => content.render_line(u16::MAX), + Self::Extension { content } => { + with_syntax_theme(|theme| content.render_line(u16::MAX, theme)) + } } } } diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index 006c777a9d..a6f2e71312 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -125,6 +125,7 @@ pub(crate) use mcp_server_elicitation::McpServerElicitationFormRequest; pub(crate) use mcp_server_elicitation::McpServerElicitationOverlay; pub(crate) use request_user_input::RequestUserInputOverlay; pub(crate) use status_line_style::STATUS_LINE_SEPARATOR; +pub(crate) use status_line_style::StatusLineAccent; pub(crate) use status_line_style::status_line_from_segments; pub(crate) use voice_strip::VoiceStripPhase; pub(crate) use voice_strip::VoiceStripState; diff --git a/codex-rs/tui/src/bottom_pane/status_line_style.rs b/codex-rs/tui/src/bottom_pane/status_line_style.rs index 571530d1c4..8fa1b120c2 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_style.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_style.rs @@ -19,17 +19,27 @@ pub(crate) const STATUS_LINE_SEPARATOR: &str = " · "; const STATUS_LINE_COLOR_SATURATION_PERCENT: u16 = 85; const STATUS_LINE_COLOR_BRIGHTNESS_PERCENT: u16 = 100; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum StatusLineAccent { +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub(crate) enum StatusLineAccent { + #[serde(rename = "status-model")] Model, + #[serde(rename = "status-path")] Path, + #[serde(rename = "status-branch")] Branch, + #[serde(rename = "status-state")] State, + #[serde(rename = "status-usage")] Usage, + #[serde(rename = "status-limit")] Limit, + #[serde(rename = "status-metadata")] Metadata, + #[serde(rename = "status-mode")] Mode, + #[serde(rename = "status-thread")] Thread, + #[serde(rename = "status-progress")] Progress, } @@ -70,7 +80,7 @@ impl StatusLineAccent { } } - fn scopes(self) -> &'static [&'static str] { + pub(crate) fn scopes(self) -> &'static [&'static str] { match self { Self::Model => &["entity.name.type", "support.type", "variable"], Self::Path => &["string", "markup.underline.link"], @@ -85,11 +95,11 @@ impl StatusLineAccent { } } - fn fallback_style(self) -> Style { + pub(crate) fn fallback_color(self) -> Color { match self { - Self::Model | Self::State | Self::Metadata | Self::Mode => Style::default().cyan(), - Self::Path | Self::Usage | Self::Progress => Style::default().green(), - Self::Branch | Self::Limit | Self::Thread => Style::default().magenta(), + Self::Model | Self::State | Self::Metadata | Self::Mode => Color::Cyan, + Self::Path | Self::Usage | Self::Progress => Color::Green, + Self::Branch | Self::Limit | Self::Thread => Color::Magenta, } } } @@ -142,7 +152,8 @@ where } else if use_theme_colors { let accent = StatusLineAccent::for_item(item); soften_status_line_style( - theme_style_for_accent(accent).unwrap_or_else(|| accent.fallback_style()), + theme_style_for_accent(accent) + .unwrap_or_else(|| Style::default().fg(accent.fallback_color())), ) } else { secondary_text_style() @@ -152,7 +163,7 @@ where if effective_stdout_color_level() == StdoutColorLevel::Ansi16 && matches!(foreground, Color::Rgb(..) | Color::Indexed(16..)) { - style.patch(StatusLineAccent::for_item(item).fallback_style()) + style.fg(StatusLineAccent::for_item(item).fallback_color()) } else { style.fg(readable_color_on(foreground, /*background*/ None)) } diff --git a/codex-rs/tui/src/render/highlight.rs b/codex-rs/tui/src/render/highlight.rs index 41b1d239c1..26c21acabe 100644 --- a/codex-rs/tui/src/render/highlight.rs +++ b/codex-rs/tui/src/render/highlight.rs @@ -290,11 +290,16 @@ pub(crate) fn syntax_theme_revision() -> u64 { /// Clone the current syntax theme (e.g. to save for cancel-restore). pub(crate) fn current_syntax_theme() -> Theme { + with_syntax_theme(Theme::clone) +} + +/// Borrow the active theme for one synchronous render operation. +pub(crate) fn with_syntax_theme(f: impl FnOnce(&Theme) -> T) -> T { let active_theme = theme_lock(); - match active_theme.read() { - Ok(theme) => theme.clone(), - Err(poisoned) => poisoned.into_inner().clone(), - } + let theme = active_theme + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); + f(&theme) } /// An explicit diff background, including the ANSI terminal-default marker. @@ -366,11 +371,18 @@ pub(crate) fn foreground_style_for_scopes_with_theme( theme: &Theme, scope_names: &[&str], ) -> Option