#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[cfg_attr(not(feature = "color"), allow(dead_code))]
pub(crate) enum ColorLevel {
None,
Ansi16,
Ansi256,
TrueColor,
}
impl ColorLevel {
#[inline]
pub(crate) fn is_none(self) -> bool {
matches!(self, ColorLevel::None)
}
}
#[cfg(feature = "color")]
pub(crate) fn color_level() -> ColorLevel {
use std::sync::OnceLock;
static LEVEL: OnceLock<ColorLevel> = OnceLock::new();
*LEVEL.get_or_init(detect)
}
#[cfg(not(feature = "color"))]
#[inline]
pub(crate) fn color_level() -> ColorLevel {
ColorLevel::None
}
#[cfg(feature = "color")]
fn detect() -> ColorLevel {
use std::io::IsTerminal;
let is_tty = std::io::stdout().is_terminal();
let no_color = std::env::var_os("NO_COLOR").is_some_and(|v| !v.is_empty());
let force = std::env::var_os("CLICOLOR_FORCE").is_some_and(|v| v != "0");
let term = std::env::var("TERM").ok();
let colorterm = std::env::var("COLORTERM").ok();
let level = resolve(
is_tty,
no_color,
force,
term.as_deref(),
colorterm.as_deref(),
);
if level.is_none() {
return ColorLevel::None;
}
if enable_vt() { level } else { ColorLevel::None }
}
#[cfg(feature = "color")]
fn resolve(
is_tty: bool,
no_color: bool,
force: bool,
term: Option<&str>,
colorterm: Option<&str>,
) -> ColorLevel {
if !force && (no_color || !is_tty || matches!(term, Some("dumb"))) {
return ColorLevel::None;
}
if let Some(ct) = colorterm {
if ct.eq_ignore_ascii_case("truecolor") || ct.eq_ignore_ascii_case("24bit") {
return ColorLevel::TrueColor;
}
}
if term.is_some_and(|t| t.contains("256color")) {
return ColorLevel::Ansi256;
}
ColorLevel::Ansi16
}
#[cfg(all(feature = "color", windows))]
fn enable_vt() -> bool {
enable_ansi_support::enable_ansi_support().is_ok()
}
#[cfg(all(feature = "color", not(windows)))]
fn enable_vt() -> bool {
true
}
#[cfg(all(test, feature = "color"))]
mod tests {
use super::*;
#[test]
fn test_resolve_non_terminal_disables_color() {
assert_eq!(
resolve(
false,
false,
false,
Some("xterm-256color"),
Some("truecolor")
),
ColorLevel::None
);
}
#[test]
fn test_resolve_no_color_disables_even_on_tty() {
assert_eq!(
resolve(true, true, false, Some("xterm-256color"), None),
ColorLevel::None
);
}
#[test]
fn test_resolve_clicolor_force_overrides_no_color_and_pipe() {
assert_eq!(
resolve(false, true, true, Some("xterm-256color"), None),
ColorLevel::Ansi256
);
}
#[test]
fn test_resolve_dumb_terminal_disables_color() {
assert_eq!(
resolve(true, false, false, Some("dumb"), Some("truecolor")),
ColorLevel::None
);
}
#[test]
fn test_resolve_truecolor_from_colorterm() {
assert_eq!(
resolve(true, false, false, Some("xterm"), Some("24bit")),
ColorLevel::TrueColor
);
assert_eq!(
resolve(true, false, false, Some("xterm"), Some("TrueColor")),
ColorLevel::TrueColor
);
}
#[test]
fn test_resolve_256_from_term() {
assert_eq!(
resolve(true, false, false, Some("screen-256color"), None),
ColorLevel::Ansi256
);
}
#[test]
fn test_resolve_defaults_to_ansi16_on_plain_tty() {
assert_eq!(
resolve(true, false, false, Some("xterm"), None),
ColorLevel::Ansi16
);
assert_eq!(resolve(true, false, false, None, None), ColorLevel::Ansi16);
}
}