use std::io::IsTerminal;
use std::sync::OnceLock;
const fn fg(r: u8, g: u8, b: u8) -> Rgb {
Rgb(r, g, b)
}
#[derive(Clone, Copy)]
pub struct Rgb(u8, u8, u8);
include!("brand_palette.rs");
const fn role(c: (u8, u8, u8)) -> Rgb {
fg(c.0, c.1, c.2)
}
pub const ACCENT: Rgb = role(BRAND_ACCENT);
pub const TEAL: Rgb = role(BRAND_TEAL);
pub const OK: Rgb = role(BRAND_OK);
pub const WARN: Rgb = role(BRAND_WARN);
pub const ERR: Rgb = role(BRAND_ERR);
pub const SKY: Rgb = role(BRAND_SKY);
pub const MUTED: Rgb = role(BRAND_MUTED);
pub const DIM: Rgb = role(BRAND_DIM);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum ColorLevel {
None,
Ansi256,
TrueColor,
}
fn detect_color_level(
no_color: bool,
clicolor_force: bool,
term: Option<&str>,
colorterm: Option<&str>,
is_tty: bool,
) -> ColorLevel {
if no_color {
return ColorLevel::None;
}
if term == Some("dumb") {
return ColorLevel::None;
}
if !clicolor_force && !is_tty {
return ColorLevel::None;
}
match colorterm {
Some("truecolor") | Some("24bit") => ColorLevel::TrueColor,
_ => ColorLevel::Ansi256,
}
}
fn color_level() -> ColorLevel {
static LEVEL: OnceLock<ColorLevel> = OnceLock::new();
*LEVEL.get_or_init(|| {
detect_color_level(
std::env::var_os("NO_COLOR").is_some(),
std::env::var_os("CLICOLOR_FORCE").is_some(),
std::env::var("TERM").ok().as_deref(),
std::env::var("COLORTERM").ok().as_deref(),
std::io::stderr().is_terminal() || std::io::stdout().is_terminal(),
)
})
}
fn color_enabled() -> bool {
color_level() != ColorLevel::None
}
fn rgb_to_xterm256(r: u8, g: u8, b: u8) -> u8 {
const CUBE_STEPS: [u8; 6] = [0, 95, 135, 175, 215, 255];
fn nearest_cube_level(v: u8) -> (i32, u8) {
CUBE_STEPS
.iter()
.enumerate()
.map(|(i, &s)| (i as i32, s))
.min_by_key(|&(_, s)| (s as i32 - v as i32).abs())
.expect("CUBE_STEPS is non-empty")
}
fn dist2(r1: u8, g1: u8, b1: u8, r2: u8, g2: u8, b2: u8) -> i32 {
let dr = r1 as i32 - r2 as i32;
let dg = g1 as i32 - g2 as i32;
let db = b1 as i32 - b2 as i32;
dr * dr + dg * dg + db * db
}
let (rl, rv) = nearest_cube_level(r);
let (gl, gv) = nearest_cube_level(g);
let (bl, bv) = nearest_cube_level(b);
let cube_idx = 16 + 36 * rl + 6 * gl + bl;
let cube_dist = dist2(r, g, b, rv, gv, bv);
let avg = (r as i32 + g as i32 + b as i32) as f64 / 3.0;
let gray_step = (((avg - 8.0) / 10.0).round()).clamp(0.0, 23.0) as i32;
let gray_val = (8 + gray_step * 10) as u8;
let gray_idx = 232 + gray_step;
let gray_dist = dist2(r, g, b, gray_val, gray_val, gray_val);
(if gray_dist < cube_dist {
gray_idx
} else {
cube_idx
}) as u8
}
fn fg_sgr(c: Rgb, level: ColorLevel) -> String {
match level {
ColorLevel::TrueColor => format!("38;2;{};{};{}", c.0, c.1, c.2),
ColorLevel::Ansi256 | ColorLevel::None => {
format!("38;5;{}", rgb_to_xterm256(c.0, c.1, c.2))
}
}
}
fn bg_sgr(c: Rgb, level: ColorLevel) -> String {
match level {
ColorLevel::TrueColor => format!("48;2;{};{};{}", c.0, c.1, c.2),
ColorLevel::Ansi256 | ColorLevel::None => {
format!("48;5;{}", rgb_to_xterm256(c.0, c.1, c.2))
}
}
}
pub fn paint(c: Rgb, s: impl AsRef<str>) -> String {
let s = s.as_ref();
let level = color_level();
if level != ColorLevel::None {
format!("\x1b[{}m{s}\x1b[0m", fg_sgr(c, level))
} else {
s.to_string()
}
}
pub fn bold(c: Rgb, s: impl AsRef<str>) -> String {
let s = s.as_ref();
let level = color_level();
if level != ColorLevel::None {
format!("\x1b[1;{}m{s}\x1b[0m", fg_sgr(c, level))
} else {
s.to_string()
}
}
pub fn bold_plain(s: impl AsRef<str>) -> String {
let s = s.as_ref();
if color_enabled() {
format!("\x1b[1m{s}\x1b[0m")
} else {
s.to_string()
}
}
pub fn pill(bg: Rgb, s: impl AsRef<str>) -> String {
let s = s.as_ref();
let level = color_level();
if level != ColorLevel::None {
format!(
"\x1b[1;{};{}m {s} \x1b[0m",
bg_sgr(bg, level),
fg_sgr(role(BRAND_ON_STRONG), level)
)
} else {
format!("[{s}]")
}
}
pub fn mark(ok: bool) -> String {
if ok {
paint(OK, "✓")
} else {
paint(ERR, "✗")
}
}
pub fn logo_mark() -> String {
bold(ACCENT, "◆")
}
const RULE_WIDTH: usize = 60;
pub fn panel_top(title: &str) -> String {
format!(
"{} {}\n{}",
paint(ACCENT, "▌"),
bold_plain(title),
paint(DIM, "─".repeat(RULE_WIDTH)),
)
}
pub fn panel_bottom() -> String {
paint(DIM, "─".repeat(RULE_WIDTH))
}
pub fn section(title: &str) -> String {
format!("{} {}", paint(ACCENT, "▌"), bold_plain(title))
}
pub fn kv(key: &str, val: impl AsRef<str>) -> String {
format!(" {} {}", paint(MUTED, format!("{key:<12}")), val.as_ref())
}
pub fn kv_info(key: &str, val: impl AsRef<str>) -> String {
format!(
" {} {} {}",
paint(DIM, "◦"),
paint(MUTED, format!("{key:<12}")),
val.as_ref()
)
}
pub fn kv_blank(key: &str, val: impl AsRef<str>) -> String {
format!(
" {} {}",
paint(MUTED, format!("{key:<12}")),
val.as_ref()
)
}
pub fn kv_status(ok: bool, key: &str, val: impl AsRef<str>) -> String {
format!(
" {} {} {}",
mark(ok),
paint(MUTED, format!("{key:<12}")),
val.as_ref()
)
}