mirror of
https://github.com/openai/codex.git
synced 2026-09-15 12:08:01 +00:00
support user_message bg color for modern Windows terminals
This commit is contained in:
committed by
David Wiesen
parent
fdb9fa301e
commit
e52f289bf0
44
codex-rs/Cargo.lock
generated
44
codex-rs/Cargo.lock
generated
@@ -1480,6 +1480,7 @@ dependencies = [
|
||||
"strum_macros 0.27.2",
|
||||
"supports-color",
|
||||
"tempfile",
|
||||
"terminal-colorsaurus",
|
||||
"textwrap 0.16.2",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -1493,6 +1494,7 @@ dependencies = [
|
||||
"unicode-width 0.2.1",
|
||||
"url",
|
||||
"vt100",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3748,14 +3750,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.4"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5750,9 +5752,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
@@ -6135,6 +6137,32 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal-colorsaurus"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8909f33134da34b43f69145e748790de650a6abd84faf1f82e773444dd293ec8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"terminal-trx",
|
||||
"windows-sys 0.61.1",
|
||||
"xterm-color",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal-trx"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662a3cd5ca570df622e848ef18b50c151e65c9835257465417242243b0bce783"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"windows-sys 0.61.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "terminal_size"
|
||||
version = "0.4.2"
|
||||
@@ -7657,6 +7685,12 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xterm-color"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4de5f056fb9dc8b7908754867544e26145767187aaac5a98495e88ad7cb8a80f"
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "1.0.1"
|
||||
|
||||
@@ -89,6 +89,13 @@ url = { workspace = true }
|
||||
|
||||
codex-windows-sandbox = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
windows-sys = { version = "0.52", features = [
|
||||
"Win32_Foundation",
|
||||
"Win32_System_Console",
|
||||
] }
|
||||
terminal-colorsaurus = "1"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = { workspace = true }
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ pub fn best_color(target: (u8, u8, u8)) -> Color {
|
||||
let Some(color_level) = supports_color::on_cached(supports_color::Stream::Stdout) else {
|
||||
return Color::default();
|
||||
};
|
||||
if color_level.has_16m {
|
||||
if stdout_has_truecolor(&color_level) {
|
||||
let (r, g, b) = target;
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
Color::Rgb(r, g, b)
|
||||
@@ -25,6 +25,58 @@ pub fn best_color(target: (u8, u8, u8)) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(not(windows))]
|
||||
fn stdout_has_truecolor(level: &supports_color::ColorLevel) -> bool {
|
||||
level.has_16m
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn stdout_has_truecolor(level: &supports_color::ColorLevel) -> bool {
|
||||
if level.has_16m {
|
||||
return true;
|
||||
}
|
||||
// Upgrade to truecolor on Windows Terminal when VT processing is available.
|
||||
std::env::var_os("WT_SESSION").is_some() && enable_vt_stdout().is_ok()
|
||||
}
|
||||
|
||||
/// Enables Virtual Terminal Processing (ANSI escape sequence handling) on Windows for stdout.
|
||||
///
|
||||
/// This ensures that ANSI SGR sequences (including 24‑bit truecolor) are interpreted by the
|
||||
/// Windows console pipeline. We use this as a lightweight capability gate before upgrading to
|
||||
/// truecolor on Windows Terminal:
|
||||
///
|
||||
/// - If enabling VT processing succeeds, we can safely emit 24‑bit color.
|
||||
/// - If it fails (e.g., legacy consoles or non‑TTY), we do not upgrade and fall back to the
|
||||
/// base `supports_color` decision to avoid printing raw escape sequences.
|
||||
///
|
||||
/// Calling this repeatedly is idempotent (it simply sets the corresponding console mode flag).
|
||||
#[cfg(windows)]
|
||||
fn enable_vt_stdout() -> std::io::Result<()> {
|
||||
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
||||
use windows_sys::Win32::System::Console::GetConsoleMode;
|
||||
use windows_sys::Win32::System::Console::GetStdHandle;
|
||||
use windows_sys::Win32::System::Console::SetConsoleMode;
|
||||
use windows_sys::Win32::System::Console::ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
|
||||
|
||||
unsafe {
|
||||
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if handle == INVALID_HANDLE_VALUE || handle == 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
let mut mode: u32 = 0;
|
||||
if GetConsoleMode(handle, &mut mode) == 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
// Idempotent if already enabled.
|
||||
if SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn requery_default_colors() {
|
||||
imp::requery_default_colors();
|
||||
}
|
||||
@@ -121,14 +173,80 @@ mod imp {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(all(unix, not(test))))]
|
||||
#[cfg(all(windows, not(test)))]
|
||||
mod imp {
|
||||
use super::DefaultColors;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
struct Cache<T> {
|
||||
attempted: bool,
|
||||
value: Option<T>,
|
||||
}
|
||||
|
||||
impl<T> Default for Cache<T> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
attempted: false,
|
||||
value: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Cache<T> {
|
||||
fn get_or_init_with(&mut self, mut init: impl FnMut() -> Option<T>) -> Option<T> {
|
||||
if !self.attempted {
|
||||
self.value = init();
|
||||
self.attempted = true;
|
||||
}
|
||||
self.value
|
||||
}
|
||||
|
||||
fn refresh_with(&mut self, mut init: impl FnMut() -> Option<T>) -> Option<T> {
|
||||
self.value = init();
|
||||
self.attempted = true;
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
fn default_colors_cache() -> &'static Mutex<Cache<DefaultColors>> {
|
||||
static CACHE: OnceLock<Mutex<Cache<DefaultColors>>> = OnceLock::new();
|
||||
CACHE.get_or_init(|| Mutex::new(Cache::default()))
|
||||
}
|
||||
|
||||
pub(super) fn default_colors() -> Option<DefaultColors> {
|
||||
None
|
||||
let cache = default_colors_cache();
|
||||
let mut cache = cache.lock().ok()?;
|
||||
cache.get_or_init_with(|| query_default_colors().unwrap_or_default())
|
||||
}
|
||||
|
||||
pub(super) fn requery_default_colors() {
|
||||
if let Ok(mut cache) = default_colors_cache().lock() {
|
||||
// Don't try to refresh if the cache is already attempted and failed.
|
||||
if cache.attempted && cache.value.is_none() {
|
||||
return;
|
||||
}
|
||||
cache.refresh_with(|| query_default_colors().unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
fn query_default_colors() -> std::io::Result<Option<DefaultColors>> {
|
||||
match terminal_colorsaurus::color_palette(terminal_colorsaurus::QueryOptions::default()) {
|
||||
Ok(p) => {
|
||||
let (fr, fg, fb) = p.foreground.scale_to_8bit();
|
||||
let (br, bg, bb) = p.background.scale_to_8bit();
|
||||
Ok(Some(DefaultColors { fg: (fr, fg, fb), bg: (br, bg, bb) }))
|
||||
}
|
||||
Err(_) => Ok(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(all(unix, not(test)), all(windows, not(test)))))]
|
||||
mod imp {
|
||||
use super::DefaultColors;
|
||||
pub(super) fn default_colors() -> Option<DefaultColors> { None }
|
||||
|
||||
pub(super) fn requery_default_colors() {}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user