use std::sync::atomic::{AtomicBool, Ordering};
static DARK_BACKGROUND: AtomicBool = AtomicBool::new(true);
pub fn set_dark_background(dark: bool) {
DARK_BACKGROUND.store(dark, Ordering::SeqCst);
}
pub fn is_dark_background() -> bool {
DARK_BACKGROUND.load(Ordering::SeqCst)
}
pub fn detect_background() -> Option<bool> {
if let Ok(colorfgbg) = std::env::var("COLORFGBG") {
if let Some(bg) = colorfgbg.split(';').next_back() {
if let Ok(bg_num) = bg.parse::<u8>() {
return Some(bg_num < 7);
}
}
}
if let Ok(term_program) = std::env::var("TERM_PROGRAM") {
if term_program.contains("iTerm")
|| term_program.contains("Alacritty")
|| term_program.contains("kitty")
{
return Some(true);
}
}
None
}
pub fn init_background_detection() {
if let Some(dark) = detect_background() {
set_dark_background(dark);
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Color {
#[default]
Reset,
Black,
Red,
Green,
Yellow,
Blue,
Magenta,
Cyan,
White,
BrightBlack,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightMagenta,
BrightCyan,
BrightWhite,
Ansi256(u8),
Rgb(u8, u8, u8),
}
impl Color {
pub fn hex(hex: &str) -> Self {
Self::try_hex(hex).unwrap_or(Color::Reset)
}
pub fn try_hex(hex: &str) -> Option<Self> {
let hex = hex.trim_start_matches('#');
if hex.len() != 6 {
return None;
}
let r = u8::from_str_radix(&hex[0..2], 16).ok()?;
let g = u8::from_str_radix(&hex[2..4], 16).ok()?;
let b = u8::from_str_radix(&hex[4..6], 16).ok()?;
Some(Color::Rgb(r, g, b))
}
pub fn rgb(r: u8, g: u8, b: u8) -> Self {
Color::Rgb(r, g, b)
}
pub fn ansi256(code: u8) -> Self {
Color::Ansi256(code)
}
pub fn to_ansi_fg(&self) -> String {
match self {
Color::Rgb(r, g, b) => format!("\x1b[38;2;{};{};{}m", r, g, b),
Color::Ansi256(code) => format!("\x1b[38;5;{}m", code),
Color::Reset => "\x1b[0m".to_string(),
Color::Black => "\x1b[30m".to_string(),
Color::Red => "\x1b[31m".to_string(),
Color::Green => "\x1b[32m".to_string(),
Color::Yellow => "\x1b[33m".to_string(),
Color::Blue => "\x1b[34m".to_string(),
Color::Magenta => "\x1b[35m".to_string(),
Color::Cyan => "\x1b[36m".to_string(),
Color::White => "\x1b[37m".to_string(),
Color::BrightBlack => "\x1b[90m".to_string(),
Color::BrightRed => "\x1b[91m".to_string(),
Color::BrightGreen => "\x1b[92m".to_string(),
Color::BrightYellow => "\x1b[93m".to_string(),
Color::BrightBlue => "\x1b[94m".to_string(),
Color::BrightMagenta => "\x1b[95m".to_string(),
Color::BrightCyan => "\x1b[96m".to_string(),
Color::BrightWhite => "\x1b[97m".to_string(),
}
}
pub fn to_ansi_bg(&self) -> String {
match self {
Color::Rgb(r, g, b) => format!("\x1b[48;2;{};{};{}m", r, g, b),
Color::Ansi256(code) => format!("\x1b[48;5;{}m", code),
Color::Reset => "\x1b[0m".to_string(),
Color::Black => "\x1b[40m".to_string(),
Color::Red => "\x1b[41m".to_string(),
Color::Green => "\x1b[42m".to_string(),
Color::Yellow => "\x1b[43m".to_string(),
Color::Blue => "\x1b[44m".to_string(),
Color::Magenta => "\x1b[45m".to_string(),
Color::Cyan => "\x1b[46m".to_string(),
Color::White => "\x1b[47m".to_string(),
Color::BrightBlack => "\x1b[100m".to_string(),
Color::BrightRed => "\x1b[101m".to_string(),
Color::BrightGreen => "\x1b[102m".to_string(),
Color::BrightYellow => "\x1b[103m".to_string(),
Color::BrightBlue => "\x1b[104m".to_string(),
Color::BrightMagenta => "\x1b[105m".to_string(),
Color::BrightCyan => "\x1b[106m".to_string(),
Color::BrightWhite => "\x1b[107m".to_string(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AdaptiveColor {
pub light: Color,
pub dark: Color,
}
impl AdaptiveColor {
pub fn new(light: Color, dark: Color) -> Self {
Self { light, dark }
}
pub fn from_hex(light: &str, dark: &str) -> Self {
Self {
light: Color::hex(light),
dark: Color::hex(dark),
}
}
pub fn resolve(&self) -> Color {
if is_dark_background() {
self.dark
} else {
self.light
}
}
pub fn universal(color: Color) -> Self {
Self {
light: color,
dark: color,
}
}
}
impl Default for AdaptiveColor {
fn default() -> Self {
Self {
light: Color::Black,
dark: Color::White,
}
}
}
impl From<AdaptiveColor> for Color {
fn from(adaptive: AdaptiveColor) -> Self {
adaptive.resolve()
}
}
impl From<Color> for AdaptiveColor {
fn from(color: Color) -> Self {
AdaptiveColor::universal(color)
}
}
pub mod adaptive_colors {
use super::{AdaptiveColor, Color};
pub fn text() -> AdaptiveColor {
AdaptiveColor::new(Color::Black, Color::White)
}
pub fn muted() -> AdaptiveColor {
AdaptiveColor::new(Color::BrightBlack, Color::BrightBlack)
}
pub fn success() -> AdaptiveColor {
AdaptiveColor::new(Color::Green, Color::BrightGreen)
}
pub fn error() -> AdaptiveColor {
AdaptiveColor::new(Color::Red, Color::BrightRed)
}
pub fn warning() -> AdaptiveColor {
AdaptiveColor::new(Color::Yellow, Color::BrightYellow)
}
pub fn info() -> AdaptiveColor {
AdaptiveColor::new(Color::Blue, Color::BrightBlue)
}
pub fn accent() -> AdaptiveColor {
AdaptiveColor::new(Color::Cyan, Color::BrightCyan)
}
pub fn highlight() -> AdaptiveColor {
AdaptiveColor::new(Color::Magenta, Color::BrightMagenta)
}
}
#[cfg(test)]
mod tests {
use super::{AdaptiveColor, Color};
#[test]
fn test_hex_color() {
assert_eq!(Color::hex("#ff0000"), Color::Rgb(255, 0, 0));
assert_eq!(Color::hex("00ff00"), Color::Rgb(0, 255, 0));
assert_eq!(Color::hex("#0000ff"), Color::Rgb(0, 0, 255));
}
#[test]
fn test_hex_invalid() {
assert_eq!(Color::hex("invalid"), Color::Reset);
assert_eq!(Color::hex("#fff"), Color::Reset); assert_eq!(Color::hex("#gg0000"), Color::Reset); }
#[test]
fn test_try_hex() {
assert_eq!(Color::try_hex("#ff0000"), Some(Color::Rgb(255, 0, 0)));
assert_eq!(Color::try_hex("00ff00"), Some(Color::Rgb(0, 255, 0)));
assert_eq!(Color::try_hex("#AABBCC"), Some(Color::Rgb(170, 187, 204)));
}
#[test]
fn test_try_hex_invalid() {
assert_eq!(Color::try_hex("invalid"), None);
assert_eq!(Color::try_hex("#fff"), None); assert_eq!(Color::try_hex("#gg0000"), None); assert_eq!(Color::try_hex(""), None);
}
#[test]
fn test_rgb() {
assert_eq!(Color::rgb(128, 64, 32), Color::Rgb(128, 64, 32));
}
#[test]
fn test_ansi256() {
assert_eq!(Color::ansi256(196), Color::Ansi256(196));
}
#[test]
fn test_adaptive() {
let adaptive = AdaptiveColor::new(Color::Black, Color::White);
assert_eq!(adaptive.light, Color::Black);
assert_eq!(adaptive.dark, Color::White);
}
#[test]
fn test_adaptive_from_hex() {
let adaptive = AdaptiveColor::from_hex("#000000", "#ffffff");
assert_eq!(adaptive.light, Color::Rgb(0, 0, 0));
assert_eq!(adaptive.dark, Color::Rgb(255, 255, 255));
}
#[test]
fn test_adaptive_universal() {
let adaptive = AdaptiveColor::universal(Color::Cyan);
assert_eq!(adaptive.light, Color::Cyan);
assert_eq!(adaptive.dark, Color::Cyan);
}
#[test]
fn test_to_ansi() {
assert_eq!(Color::Red.to_ansi_fg(), "\x1b[31m");
assert_eq!(Color::BrightRed.to_ansi_fg(), "\x1b[91m");
assert_eq!(Color::Ansi256(196).to_ansi_fg(), "\x1b[38;5;196m");
assert_eq!(Color::Reset.to_ansi_fg(), "\x1b[0m");
}
}