mirror of
https://github.com/openai/codex.git
synced 2026-09-15 12:08:01 +00:00
support Windows themes, which override the color table.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
use crate::color::blend;
|
||||
use crate::color::is_light;
|
||||
use crate::color::perceptual_distance;
|
||||
use crate::terminal_palette::basic_palette;
|
||||
use crate::terminal_palette::terminal_palette;
|
||||
use ratatui::style::Color;
|
||||
use ratatui::style::Style;
|
||||
@@ -52,8 +53,15 @@ pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color {
|
||||
// perceptually closest to the blended target.
|
||||
Color::Indexed(i as u8)
|
||||
} else if color_level.has_basic {
|
||||
// Finally, degrade to the basic 16 ANSI colors using a perceptual distance match.
|
||||
closest_basic_color(target)
|
||||
if let Some(palette) = basic_palette() {
|
||||
// On Windows terminals the palette is configurable, so evaluate the actual
|
||||
// runtime color table to keep the blended shading aligned with custom themes.
|
||||
closest_runtime_basic_color(target, &palette)
|
||||
} else {
|
||||
// Finally, degrade to the well-known ANSI 16-color defaults using a perceptual
|
||||
// distance match.
|
||||
closest_basic_color(target)
|
||||
}
|
||||
} else {
|
||||
// If the runtime reports no color support at all, keep the default background to
|
||||
// avoid rendering garbage escape sequences.
|
||||
@@ -61,6 +69,19 @@ pub fn user_message_bg(terminal_bg: (u8, u8, u8)) -> Color {
|
||||
}
|
||||
}
|
||||
|
||||
fn closest_runtime_basic_color(target: (u8, u8, u8), palette: &[(u8, u8, u8); 16]) -> Color {
|
||||
palette
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| {
|
||||
perceptual_distance(**a, target)
|
||||
.partial_cmp(&perceptual_distance(**b, target))
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.and_then(|(idx, _)| BASIC_TERMINAL_COLORS.get(idx).map(|(color, _)| *color))
|
||||
.unwrap_or(Color::default())
|
||||
}
|
||||
|
||||
fn closest_basic_color(target: (u8, u8, u8)) -> Color {
|
||||
// Iterate through the baked-in ANSI colors and return whichever one is closest to the
|
||||
// desired RGB shade. This mirrors the logic used for the 256-color lookup but avoids
|
||||
|
||||
@@ -6,6 +6,15 @@ pub fn terminal_palette() -> Option<[(u8, u8, u8); 256]> {
|
||||
imp::terminal_palette()
|
||||
}
|
||||
|
||||
/// Returns the runtime palette for basic (0-15) color slots when the terminal reports one.
|
||||
///
|
||||
/// Windows exposes a mutable color table via the console API so callers can render subtle
|
||||
/// shading that matches the configured theme. Unix terminals rarely offer similar hooks, so
|
||||
/// this returns `None` there and consumers should fall back to well-known ANSI defaults.
|
||||
pub fn basic_palette() -> Option<[(u8, u8, u8); 16]> {
|
||||
imp::basic_palette()
|
||||
}
|
||||
|
||||
/// Forces the palette cache to re-run the default color probe for the current terminal.
|
||||
pub fn requery_default_colors() {
|
||||
imp::requery_default_colors();
|
||||
@@ -91,6 +100,10 @@ mod imp {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn basic_palette() -> Option<[(u8, u8, u8); 16]> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn default_colors() -> Option<DefaultColors> {
|
||||
let cache = default_colors_cache();
|
||||
let mut cache = cache.lock().ok()?;
|
||||
@@ -473,9 +486,13 @@ mod imp {
|
||||
mod imp {
|
||||
use super::DefaultColors;
|
||||
use std::mem::MaybeUninit;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::OnceLock;
|
||||
use windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE;
|
||||
use windows_sys::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFO;
|
||||
use windows_sys::Win32::System::Console::CONSOLE_SCREEN_BUFFER_INFOEX;
|
||||
use windows_sys::Win32::System::Console::GetConsoleScreenBufferInfo;
|
||||
use windows_sys::Win32::System::Console::GetConsoleScreenBufferInfoEx;
|
||||
use windows_sys::Win32::System::Console::GetStdHandle;
|
||||
use windows_sys::Win32::System::Console::STD_OUTPUT_HANDLE;
|
||||
|
||||
@@ -499,43 +516,152 @@ mod imp {
|
||||
(255, 255, 255),
|
||||
];
|
||||
|
||||
/// Captures the console defaults along with the resolved 16-color palette so lookups can
|
||||
/// serve both `default_colors()` and `basic_palette()` without re-querying Win32 APIs.
|
||||
#[derive(Clone, Copy)]
|
||||
struct ConsoleSnapshot {
|
||||
defaults: DefaultColors,
|
||||
palette: [(u8, u8, u8); 16],
|
||||
}
|
||||
|
||||
/// Lightweight memoization helper mirroring the Unix implementation so the Windows probe
|
||||
/// only runs when necessary.
|
||||
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 console_snapshot_cache() -> &'static Mutex<Cache<ConsoleSnapshot>> {
|
||||
static CACHE: OnceLock<Mutex<Cache<ConsoleSnapshot>>> = OnceLock::new();
|
||||
CACHE.get_or_init(|| Mutex::new(Cache::default()))
|
||||
}
|
||||
|
||||
pub(super) fn terminal_palette() -> Option<[(u8, u8, u8); 256]> {
|
||||
// Legacy Windows terminals only support the 16 basic colors so there is no
|
||||
// meaningful OSC palette to retrieve.
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn basic_palette() -> Option<[(u8, u8, u8); 16]> {
|
||||
let cache = console_snapshot_cache();
|
||||
let mut cache = cache.lock().ok()?;
|
||||
cache
|
||||
.get_or_init_with(query_console_snapshot)
|
||||
.map(|snapshot| snapshot.palette)
|
||||
}
|
||||
|
||||
/// Uses the Win32 console APIs to look up the active color attribute indices and map
|
||||
/// them into RGB triplets using the standard Windows palette table above.
|
||||
pub(super) fn default_colors() -> Option<DefaultColors> {
|
||||
let cache = console_snapshot_cache();
|
||||
let mut cache = cache.lock().ok()?;
|
||||
cache
|
||||
.get_or_init_with(query_console_snapshot)
|
||||
.map(|snapshot| snapshot.defaults)
|
||||
}
|
||||
|
||||
pub(super) fn requery_default_colors() {
|
||||
if let Ok(mut cache) = console_snapshot_cache().lock() {
|
||||
cache.refresh_with(query_console_snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries the Win32 console for the color attribute indices and, when possible, the
|
||||
/// active 16-color palette that modern Windows Terminal exposes for custom themes.
|
||||
fn query_console_snapshot() -> Option<ConsoleSnapshot> {
|
||||
unsafe {
|
||||
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
// Windows exposes null (0) and INVALID_HANDLE_VALUE (-1) as the two
|
||||
// sentinel return values from GetStdHandle. HANDLE is a raw pointer
|
||||
// so rely on is_null() for the zero check to keep pointer typing
|
||||
// consistent across the windows-sys bindings.
|
||||
if handle.is_null() || handle == INVALID_HANDLE_VALUE {
|
||||
return None;
|
||||
}
|
||||
let mut info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
|
||||
if GetConsoleScreenBufferInfo(handle, info.as_mut_ptr()) == 0 {
|
||||
return None;
|
||||
|
||||
// Prefer the extended console info call so that we can honor custom palettes.
|
||||
if let Some(snapshot) = query_with_ex(handle) {
|
||||
return Some(snapshot);
|
||||
}
|
||||
let info = info.assume_init();
|
||||
let attrs = info.wAttributes as usize;
|
||||
let fg_idx = attrs & 0x0f;
|
||||
let bg_idx = (attrs >> 4) & 0x0f;
|
||||
Some(DefaultColors {
|
||||
|
||||
// Fall back to the legacy structure when the extended query is unavailable.
|
||||
query_with_basic_info(handle)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn query_with_ex(handle: isize) -> Option<ConsoleSnapshot> {
|
||||
let mut info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFOEX>::zeroed();
|
||||
let info_ptr = info.as_mut_ptr();
|
||||
(*info_ptr).cbSize = std::mem::size_of::<CONSOLE_SCREEN_BUFFER_INFOEX>() as u32;
|
||||
if GetConsoleScreenBufferInfoEx(handle, info_ptr) == 0 {
|
||||
return None;
|
||||
}
|
||||
let info = info.assume_init();
|
||||
let attrs = info.wAttributes as usize;
|
||||
let fg_idx = attrs & 0x0f;
|
||||
let bg_idx = (attrs >> 4) & 0x0f;
|
||||
let mut palette = [(0u8, 0u8, 0u8); 16];
|
||||
for (slot, colorref) in palette.iter_mut().zip(info.ColorTable.iter().copied()) {
|
||||
*slot = unpack_colorref(colorref);
|
||||
}
|
||||
Some(ConsoleSnapshot {
|
||||
defaults: DefaultColors {
|
||||
fg: palette.get(fg_idx).copied().unwrap_or((255, 255, 255)),
|
||||
bg: palette.get(bg_idx).copied().unwrap_or((0, 0, 0)),
|
||||
},
|
||||
palette,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn query_with_basic_info(handle: isize) -> Option<ConsoleSnapshot> {
|
||||
let mut info = MaybeUninit::<CONSOLE_SCREEN_BUFFER_INFO>::uninit();
|
||||
if GetConsoleScreenBufferInfo(handle, info.as_mut_ptr()) == 0 {
|
||||
return None;
|
||||
}
|
||||
let info = info.assume_init();
|
||||
let attrs = info.wAttributes as usize;
|
||||
let fg_idx = attrs & 0x0f;
|
||||
let bg_idx = (attrs >> 4) & 0x0f;
|
||||
Some(ConsoleSnapshot {
|
||||
defaults: DefaultColors {
|
||||
fg: WINDOWS_COLORS
|
||||
.get(fg_idx)
|
||||
.copied()
|
||||
.unwrap_or((255, 255, 255)),
|
||||
bg: WINDOWS_COLORS.get(bg_idx).copied().unwrap_or((0, 0, 0)),
|
||||
})
|
||||
}
|
||||
},
|
||||
palette: WINDOWS_COLORS,
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn requery_default_colors() {}
|
||||
/// Unpacks a Windows COLORREF (0x00BBGGRR) triple into conventional RGB ordering.
|
||||
fn unpack_colorref(colorref: u32) -> (u8, u8, u8) {
|
||||
let r = (colorref & 0xff) as u8;
|
||||
let g = ((colorref >> 8) & 0xff) as u8;
|
||||
let b = ((colorref >> 16) & 0xff) as u8;
|
||||
(r, g, b)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(any(all(unix, not(test)), all(windows, not(test)))))]
|
||||
@@ -546,6 +672,10 @@ mod imp {
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn basic_palette() -> Option<[(u8, u8, u8); 16]> {
|
||||
None
|
||||
}
|
||||
|
||||
pub(super) fn default_colors() -> Option<DefaultColors> {
|
||||
None
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user