use std::{
env, io,
io::IsTerminal,
sync::{
LazyLock,
atomic::{AtomicU8, Ordering},
},
};
pub static COLOR_LEVEL_DETECTED: LazyLock<ColorLevel> =
LazyLock::new(ColorLevel::detect);
pub static SHOULD_COLORIZE: LazyLock<AtomicU8> = LazyLock::new(|| {
AtomicU8::new(
#[cfg(feature = "terminal-detection")]
{
ShouldColorize::from_env() as u8
},
#[cfg(not(feature = "terminal-detection"))]
{
ShouldColorize::YesWithTrueColor as u8
},
)
});
#[allow(clippy::result_unit_err)]
#[cfg(windows)]
pub fn set_virtual_terminal(use_virtual: bool) {
use windows_sys::Win32::System::Console::{
ENABLE_VIRTUAL_TERMINAL_PROCESSING, GetConsoleMode, GetStdHandle,
STD_OUTPUT_HANDLE, SetConsoleMode,
};
unsafe {
let handle = GetStdHandle(STD_OUTPUT_HANDLE);
if handle.is_null() {
return;
}
let mut mode = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return;
}
let new_mode = if use_virtual {
mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING
} else {
mode & !ENABLE_VIRTUAL_TERMINAL_PROCESSING
};
if new_mode != mode {
SetConsoleMode(handle, new_mode);
}
}
}
pub fn set_should_colorize(should_colorize: ShouldColorize) {
SHOULD_COLORIZE.store(should_colorize as u8, Ordering::Relaxed);
}
pub fn get_should_colorize() -> ShouldColorize {
SHOULD_COLORIZE.load(Ordering::Relaxed).into()
}
pub fn get_current_color_level() -> ColorLevel {
match get_should_colorize() {
ShouldColorize::No => ColorLevel::None,
ShouldColorize::Yes => *COLOR_LEVEL_DETECTED,
level => level.into(),
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ColorLevel {
None,
Ansi16,
Ansi256,
TrueColor,
}
impl ColorLevel {
fn detect() -> Self {
if env::var("COLORTERM")
.is_ok_and(|v| matches!(v.as_str(), "truecolor" | "24bit"))
{
return Self::TrueColor;
}
if env::var_os("WT_SESSION").is_some() {
return Self::TrueColor;
}
if env::var_os("CI").is_some() {
return Self::Ansi256;
}
#[cfg(target_os = "windows")]
{
use windows_version::OsVersion;
let version = OsVersion::current();
if version >= OsVersion::new(10, 0, 0, 14931) {
return Self::TrueColor;
}
if version >= OsVersion::new(10, 0, 0, 10586) {
return Self::Ansi256;
}
}
if let Some(term) =
env::var_os("TERM").and_then(|term| term.into_string().ok())
{
if term.ends_with("-256color") || term.ends_with("256") {
return Self::Ansi256;
}
}
Self::Ansi16
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum ShouldColorize {
No,
Yes,
YesWithAnsi16,
YesWithAnsi256,
YesWithTrueColor,
}
impl ShouldColorize {
#[must_use]
pub fn from_env() -> Self {
if env::var("CLICOLOR_FORCE").is_ok_and(|v| v != "0") {
return Self::Yes;
}
if env::var("NO_COLOR").is_ok() {
return Self::No;
}
if env::var("CLICOLOR").is_ok_and(|v| v != "0") {
return Self::Yes;
}
if io::stdout().is_terminal() {
Self::Yes
} else {
Self::No
}
}
}
impl From<u8> for ShouldColorize {
fn from(value: u8) -> Self {
match value {
0 => Self::No,
2 => Self::YesWithAnsi16,
3 => Self::YesWithAnsi256,
4 => Self::YesWithTrueColor,
_ => Self::Yes, }
}
}
impl From<ShouldColorize> for ColorLevel {
fn from(value: ShouldColorize) -> Self {
match value {
ShouldColorize::YesWithAnsi16 => Self::Ansi16,
ShouldColorize::YesWithAnsi256 => Self::Ansi256,
ShouldColorize::YesWithTrueColor => Self::TrueColor,
_ => Self::None,
}
}
}