use ratatui::style::Color;
use crate::foundation::color::perceptual_distance;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ColorLevel {
TrueColor,
Ansi256,
Monochrome,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ColorCapabilities {
pub level: ColorLevel,
pub color_enabled: bool,
}
impl ColorCapabilities {
pub fn resolve(
no_color: bool,
term: Option<&str>,
colorterm: Option<&str>,
force_color: bool,
) -> Self {
if no_color && !force_color {
return Self::monochrome();
}
if term.is_some_and(|value| value.eq_ignore_ascii_case("dumb")) && !force_color {
return Self::monochrome();
}
let truecolor = colorterm.is_some_and(|value| {
value.eq_ignore_ascii_case("truecolor") || value.eq_ignore_ascii_case("24bit")
});
let ansi256 = term.is_some_and(|value| value.to_ascii_lowercase().contains("256color"));
let level = if truecolor {
ColorLevel::TrueColor
} else if ansi256 || force_color {
ColorLevel::Ansi256
} else {
ColorLevel::Monochrome
};
Self {
level,
color_enabled: level != ColorLevel::Monochrome,
}
}
pub const fn monochrome() -> Self {
Self {
level: ColorLevel::Monochrome,
color_enabled: false,
}
}
pub fn best_color(self, target: (u8, u8, u8)) -> Color {
match self.level {
ColorLevel::TrueColor => Color::Rgb(target.0, target.1, target.2),
ColorLevel::Ansi256 => Color::Indexed(nearest_xterm_index(target)),
ColorLevel::Monochrome => Color::Reset,
}
}
}
fn nearest_xterm_index(target: (u8, u8, u8)) -> u8 {
xterm_fixed_colors()
.min_by(|(_, left), (_, right)| {
perceptual_distance(*left, target)
.partial_cmp(&perceptual_distance(*right, target))
.unwrap_or(std::cmp::Ordering::Equal)
})
.map_or(16, |(index, _)| index)
}
fn xterm_fixed_colors() -> impl Iterator<Item = (u8, (u8, u8, u8))> {
let cube = (0u8..6).flat_map(|r| {
(0u8..6).flat_map(move |g| {
(0u8..6).map(move |b| {
let index = 16 + 36 * r + 6 * g + b;
(
index,
(xterm_component(r), xterm_component(g), xterm_component(b)),
)
})
})
});
let grayscale = (0u8..24).map(|offset| {
let value = 8 + offset * 10;
(232 + offset, (value, value, value))
});
cube.chain(grayscale)
}
const fn xterm_component(value: u8) -> u8 {
if value == 0 {
0
} else {
55 + value * 40
}
}