use colored::Color;
use std::env;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum Background {
Light,
#[default]
Dark,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum ColorLevel {
None,
#[default]
Ansi16,
Ansi256,
TrueColor,
}
impl ColorLevel {
pub fn supports_256(&self) -> bool {
*self >= ColorLevel::Ansi256
}
pub fn supports_true_color(&self) -> bool {
*self == ColorLevel::TrueColor
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdaptiveColor {
pub light: Color,
pub dark: Color,
}
impl AdaptiveColor {
pub const fn new(light: Color, dark: Color) -> Self {
Self { light, dark }
}
pub fn resolve(&self, background: Background) -> Color {
match background {
Background::Light => self.light,
Background::Dark => self.dark,
}
}
}
pub mod palette {
use super::*;
pub const SUBTLE: AdaptiveColor = AdaptiveColor::new(
Color::TrueColor {
r: 88,
g: 88,
b: 88,
}, Color::TrueColor {
r: 168,
g: 168,
b: 168,
}, );
pub const HIGHLIGHT: AdaptiveColor = AdaptiveColor::new(
Color::TrueColor {
r: 205,
g: 92,
b: 178,
}, Color::TrueColor {
r: 255,
g: 135,
b: 215,
}, );
pub const SUCCESS: AdaptiveColor = AdaptiveColor::new(
Color::TrueColor { r: 0, g: 135, b: 0 }, Color::TrueColor {
r: 95,
g: 255,
b: 95,
}, );
pub const ERROR: AdaptiveColor = AdaptiveColor::new(
Color::TrueColor { r: 175, g: 0, b: 0 }, Color::TrueColor {
r: 255,
g: 95,
b: 95,
}, );
pub const WARNING: AdaptiveColor = AdaptiveColor::new(
Color::TrueColor {
r: 175,
g: 95,
b: 0,
}, Color::TrueColor {
r: 255,
g: 175,
b: 95,
}, );
pub const INFO: AdaptiveColor = AdaptiveColor::new(
Color::TrueColor {
r: 0,
g: 95,
b: 175,
}, Color::TrueColor {
r: 95,
g: 175,
b: 255,
}, );
}
pub fn detect_background() -> Background {
if let Ok(colorfgbg) = env::var("COLORFGBG")
&& let Some(bg) = colorfgbg.split(';').nth(1)
&& let Ok(bg_num) = bg.parse::<u8>()
{
return if bg_num <= 8 {
Background::Dark
} else {
Background::Light
};
}
if let Ok(theme) = env::var("TERMINAL_THEME")
&& theme.to_lowercase().contains("light")
{
return Background::Light;
}
if let Ok(bg) = env::var("TERM_BACKGROUND")
&& bg.to_lowercase() == "light"
{
return Background::Light;
}
Background::Dark
}
pub fn detect_color_level() -> ColorLevel {
if let Ok(colorterm) = env::var("COLORTERM") {
let ct_lower = colorterm.to_lowercase();
if ct_lower == "truecolor" || ct_lower == "24bit" {
return ColorLevel::TrueColor;
}
}
if let Ok(term) = env::var("TERM") {
if term == "dumb" {
return ColorLevel::None;
}
if term.contains("256color") {
return ColorLevel::Ansi256;
}
}
if env::var("WT_SESSION").is_ok() {
return ColorLevel::TrueColor;
}
if env::var("ConEmuANSI").is_ok() {
return ColorLevel::TrueColor;
}
ColorLevel::Ansi16
}
pub fn detect_hyperlink_support() -> bool {
if env::var("WT_SESSION").is_ok() {
return true;
}
if let Ok(term_program) = env::var("TERM_PROGRAM") {
let program_lower = term_program.to_lowercase();
if program_lower.contains("iterm")
|| program_lower.contains("hyper")
|| program_lower.contains("kitty")
{
return true;
}
}
if let Ok(vte_version) = env::var("VTE_VERSION")
&& let Ok(version) = vte_version.parse::<u32>()
&& version >= 5000
{
return true;
}
if let Ok(features) = env::var("TERM_FEATURES")
&& features.contains("hyperlink")
{
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
use tracing::info;
fn log_test_start(name: &str) {
info!("TEST START: {}", name);
}
#[test]
fn test_color_level_ordering() {
log_test_start("test_color_level_ordering");
assert!(ColorLevel::None < ColorLevel::Ansi16);
assert!(ColorLevel::Ansi16 < ColorLevel::Ansi256);
assert!(ColorLevel::Ansi256 < ColorLevel::TrueColor);
}
#[test]
fn test_color_level_supports_256() {
log_test_start("test_color_level_supports_256");
assert!(!ColorLevel::None.supports_256());
assert!(!ColorLevel::Ansi16.supports_256());
assert!(ColorLevel::Ansi256.supports_256());
assert!(ColorLevel::TrueColor.supports_256());
}
#[test]
fn test_color_level_supports_true_color() {
log_test_start("test_color_level_supports_true_color");
assert!(!ColorLevel::None.supports_true_color());
assert!(!ColorLevel::Ansi16.supports_true_color());
assert!(!ColorLevel::Ansi256.supports_true_color());
assert!(ColorLevel::TrueColor.supports_true_color());
}
#[test]
fn test_background_default_is_dark() {
log_test_start("test_background_default_is_dark");
assert_eq!(Background::default(), Background::Dark);
}
#[test]
fn test_adaptive_color_resolve_dark() {
log_test_start("test_adaptive_color_resolve_dark");
let adaptive = AdaptiveColor::new(Color::Black, Color::White);
assert_eq!(adaptive.resolve(Background::Dark), Color::White);
}
#[test]
fn test_adaptive_color_resolve_light() {
log_test_start("test_adaptive_color_resolve_light");
let adaptive = AdaptiveColor::new(Color::Black, Color::White);
assert_eq!(adaptive.resolve(Background::Light), Color::Black);
}
#[test]
fn test_palette_colors_exist() {
log_test_start("test_palette_colors_exist");
let bg = Background::Dark;
let _ = palette::SUBTLE.resolve(bg);
let _ = palette::HIGHLIGHT.resolve(bg);
let _ = palette::SUCCESS.resolve(bg);
let _ = palette::ERROR.resolve(bg);
let _ = palette::WARNING.resolve(bg);
let _ = palette::INFO.resolve(bg);
}
#[test]
fn test_detect_background_default() {
log_test_start("test_detect_background_default");
let result = detect_background();
assert!(result == Background::Light || result == Background::Dark);
}
#[test]
fn test_detect_color_level_default() {
log_test_start("test_detect_color_level_default");
let result = detect_color_level();
assert!(
result == ColorLevel::None
|| result == ColorLevel::Ansi16
|| result == ColorLevel::Ansi256
|| result == ColorLevel::TrueColor
);
}
}