use std::io;
use std::sync::OnceLock;
use crossterm::tty::IsTty;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorMode {
Dark,
Light,
None,
}
struct DetectedEnv {
stdout_tty: bool,
stderr_tty: bool,
no_color: bool,
term_dumb: bool,
accessible: bool,
color_mode: ColorMode,
}
fn detect() -> &'static DetectedEnv {
static ENV: OnceLock<DetectedEnv> = OnceLock::new();
ENV.get_or_init(|| {
let no_color = std::env::var_os("NO_COLOR").is_some();
let term_dumb = std::env::var("TERM")
.map(|t| t == "dumb")
.unwrap_or(false);
let accessible = std::env::var_os("ACCESSIBLE").is_some();
let stdout_tty = io::stdout().is_tty();
let stderr_tty = io::stderr().is_tty();
let color_mode = if no_color || term_dumb || accessible {
ColorMode::None
} else if !stdout_tty && !stderr_tty {
ColorMode::None
} else {
if let Ok(colorfgbg) = std::env::var("COLORFGBG") {
if let Some(bg) = colorfgbg.rsplit(';').next().and_then(|s| s.parse::<u8>().ok())
{
if bg >= 8 {
return DetectedEnv {
stdout_tty,
stderr_tty,
no_color,
term_dumb,
accessible,
color_mode: ColorMode::Light,
};
}
}
}
ColorMode::Dark
};
DetectedEnv {
stdout_tty,
stderr_tty,
no_color,
term_dumb,
accessible,
color_mode,
}
})
}
pub fn is_tty_stdout() -> bool {
detect().stdout_tty
}
pub fn is_tty_stderr() -> bool {
detect().stderr_tty
}
pub fn is_accessible_mode() -> bool {
let env = detect();
env.no_color || env.term_dumb || env.accessible
}
pub fn should_style() -> bool {
let env = detect();
env.stderr_tty && !env.no_color && !env.term_dumb && !env.accessible
}
pub fn detect_color_mode() -> ColorMode {
detect().color_mode
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color_mode_variants_are_comparable() {
assert_ne!(ColorMode::Dark, ColorMode::Light);
assert_ne!(ColorMode::Dark, ColorMode::None);
}
}