mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Preserve status line accents on 16-color terminals (#53440)
## Why RGB and extended indexed theme colors cannot be displayed directly on 16-color terminals, causing status line items to lose their color accents. ## What changed Use each status line item's existing ANSI fallback accent when its theme foreground requires RGB or an indexed color beyond the first 16 colors. ## Testing Add regression coverage for uncolored output with `FORCE_COLOR=0` and ANSI status line accents with `FORCE_COLOR=1`. Give the RGB styling test explicit terminal colors so it consistently exercises true-color behavior. GitOrigin-RevId: 213ca5c6c366f0a3c696aab256a0b146558a2837
This commit is contained in:
@@ -10,6 +10,8 @@ use super::status_line_setup::StatusLineItem;
|
||||
use crate::render::highlight::foreground_style_for_scopes;
|
||||
use crate::style::readable_color_on;
|
||||
use crate::style::secondary_text_style;
|
||||
use crate::terminal_palette::StdoutColorLevel;
|
||||
use crate::terminal_palette::effective_stdout_color_level;
|
||||
use crate::thread_color::thread_color;
|
||||
use codex_protocol::ThreadId;
|
||||
|
||||
@@ -135,10 +137,14 @@ where
|
||||
secondary_text_style()
|
||||
};
|
||||
let style = if use_theme_colors {
|
||||
style.fg(readable_color_on(
|
||||
style.fg.unwrap_or(Color::Reset),
|
||||
/*background*/ None,
|
||||
))
|
||||
let foreground = style.fg.unwrap_or(Color::Reset);
|
||||
if effective_stdout_color_level() == StdoutColorLevel::Ansi16
|
||||
&& matches!(foreground, Color::Rgb(..) | Color::Indexed(16..))
|
||||
{
|
||||
style.patch(StatusLineAccent::for_item(item).fallback_style())
|
||||
} else {
|
||||
style.fg(readable_color_on(foreground, /*background*/ None))
|
||||
}
|
||||
} else {
|
||||
style
|
||||
};
|
||||
@@ -286,22 +292,28 @@ mod tests {
|
||||
#[test]
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
fn status_line_segments_soften_rgb_theme_styles_without_dimming_text() {
|
||||
let line = status_line_from_segments_with_resolver(
|
||||
[(StatusLineItem::ModelName, "gpt-5".to_string())],
|
||||
/*use_theme_colors*/ true,
|
||||
/*thread_id*/ None,
|
||||
|_| Some(Style::default().fg(Color::Rgb(255, 0, 0))),
|
||||
)
|
||||
.expect("status line");
|
||||
let colors = crate::terminal_probe::DefaultColors {
|
||||
fg: (255, 255, 255),
|
||||
bg: (0, 0, 0),
|
||||
};
|
||||
crate::terminal_palette::with_test_default_colors(colors, || {
|
||||
let line = status_line_from_segments_with_resolver(
|
||||
[(StatusLineItem::ModelName, "gpt-5".to_string())],
|
||||
/*use_theme_colors*/ true,
|
||||
/*thread_id*/ None,
|
||||
|_| Some(Style::default().fg(Color::Rgb(255, 0, 0))),
|
||||
)
|
||||
.expect("status line");
|
||||
|
||||
assert_eq!(
|
||||
line.spans[0].style.fg,
|
||||
Some(readable_color_on(
|
||||
Color::Rgb(228, 11, 11),
|
||||
/*background*/ None
|
||||
))
|
||||
);
|
||||
assert!(!line.spans[0].style.add_modifier.contains(Modifier::DIM));
|
||||
assert_eq!(
|
||||
line.spans[0].style.fg,
|
||||
Some(readable_color_on(
|
||||
Color::Rgb(228, 11, 11),
|
||||
/*background*/ None
|
||||
))
|
||||
);
|
||||
assert!(!line.spans[0].style.add_modifier.contains(Modifier::DIM));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -389,4 +401,80 @@ mod tests {
|
||||
insta::assert_snapshot!(format!("{buffer:?}"));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn status_line_theme_colors_at_limited_color_depths() {
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::Widget;
|
||||
|
||||
const CHILD: &str = "CODEX_STATUS_LINE_COLOR_TEST_CHILD";
|
||||
let Ok(level) = std::env::var(CHILD) else {
|
||||
for level in ["0", "1"] {
|
||||
let output = std::process::Command::new(std::env::current_exe().unwrap())
|
||||
.args([
|
||||
"--exact",
|
||||
"bottom_pane::status_line_style::tests::status_line_theme_colors_at_limited_color_depths",
|
||||
])
|
||||
.env(CHILD, level)
|
||||
.env("FORCE_COLOR", level)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"level {level}: {}\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
let theme = crate::render::highlight::resolve_theme_by_name(
|
||||
"coldark-dark",
|
||||
/*codex_home*/ None,
|
||||
)
|
||||
.unwrap();
|
||||
crate::render::highlight::set_syntax_theme(theme);
|
||||
let line = status_line_from_segments(
|
||||
[
|
||||
(StatusLineItem::ModelName, "gpt-5".to_string()),
|
||||
(StatusLineItem::CurrentDir, "~/code".to_string()),
|
||||
(StatusLineItem::UsedTokens, "12k tokens".to_string()),
|
||||
],
|
||||
/*use_theme_colors*/ true,
|
||||
/*thread_id*/ None,
|
||||
)
|
||||
.expect("status line");
|
||||
if level == "0" {
|
||||
assert_eq!(
|
||||
line.spans
|
||||
.iter()
|
||||
.map(|span| span.style.fg)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![Some(Color::Reset); line.spans.len()]
|
||||
);
|
||||
} else {
|
||||
let area = Rect::new(
|
||||
/*x*/ 0, /*y*/ 0, /*width*/ 30, /*height*/ 1,
|
||||
);
|
||||
let mut buffer = Buffer::empty(area);
|
||||
line.render(area, &mut buffer);
|
||||
insta::assert_snapshot!(format!("{buffer:?}"), @r###"
|
||||
Buffer {
|
||||
area: Rect { x: 0, y: 0, width: 30, height: 1 },
|
||||
content: [
|
||||
"gpt-5 · ~/code · 12k tokens ",
|
||||
],
|
||||
styles: [
|
||||
x: 0, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 5, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 8, y: 0, fg: Green, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 14, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 17, y: 0, fg: Green, bg: Reset, underline: Reset, modifier: NONE,
|
||||
x: 27, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
|
||||
]
|
||||
}
|
||||
"###);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user