use std::{
env,
error::Error,
fmt::{self, Display, Formatter},
io::{self, IsTerminal},
sync::Once,
};
static mut SHOULD_COLOUR: bool = false;
static ONCE: Once = Once::new();
pub const DEFAULT_COLOUR: &str = "\x1b[22;39m";
pub const BLACK: &str = "\x1b[30m"; pub const DARK_RED: &str = "\x1b[31m"; pub const DARK_GREEN: &str = "\x1b[32m"; pub const DARK_YELLOW: &str = "\x1b[33m"; pub const DARK_BLUE: &str = "\x1b[34m"; pub const DARK_MAGENTA: &str = "\x1b[35m"; pub const DARK_CYAN: &str = "\x1b[36m"; pub const GREY: &str = "\x1b[37m";
pub const DARK_GREY: &str = "\x1b[90m"; pub const RED: &str = "\x1b[91m"; pub const GREEN: &str = "\x1b[92m"; pub const YELLOW: &str = "\x1b[93m"; pub const BLUE: &str = "\x1b[94m"; pub const MAGENTA: &str = "\x1b[95m"; pub const CYAN: &str = "\x1b[96m"; pub const WHITE: &str = "\x1b[97m";
pub const BOLD_DEFAULT_COLOUR: &str = "\x1b[1;39m";
pub const BOLD_BLACK: &str = "\x1b[1;30m"; pub const BOLD_DARK_RED: &str = "\x1b[1;31m"; pub const BOLD_DARK_GREEN: &str = "\x1b[1;32m"; pub const BOLD_DARK_YELLOW: &str = "\x1b[1;33m"; pub const BOLD_DARK_BLUE: &str = "\x1b[1;34m"; pub const BOLD_DARK_MAGENTA: &str = "\x1b[1;35m"; pub const BOLD_DARK_CYAN: &str = "\x1b[1;36m"; pub const BOLD_GREY: &str = "\x1b[1;37m";
pub const BOLD_DARK_GREY: &str = "\x1b[1;90m"; pub const BOLD_RED: &str = "\x1b[1;91m"; pub const BOLD_GREEN: &str = "\x1b[1;92m"; pub const BOLD_YELLOW: &str = "\x1b[1;93m"; pub const BOLD_BLUE: &str = "\x1b[1;94m"; pub const BOLD_MAGENTA: &str = "\x1b[1;95m"; pub const BOLD_CYAN: &str = "\x1b[1;96m"; pub const BOLD_WHITE: &str = "\x1b[1;97m";
#[derive(Clone, Copy, Debug)]
pub struct AlreadySet;
impl Display for AlreadySet {
fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
write!(formatter, "colour has already been set")
}
}
impl Error for AlreadySet {}
pub(super) fn force(value: bool) -> Result<(), AlreadySet> {
unsafe {
ONCE.call_once(|| {
SHOULD_COLOUR = value;
});
if SHOULD_COLOUR == value {
Ok(())
} else {
Err(AlreadySet)
}
}
}
pub fn should_colour() -> bool {
unsafe {
ONCE.call_once(|| {
SHOULD_COLOUR = initialise_from_env();
});
SHOULD_COLOUR
}
}
fn initialise_from_env() -> bool {
if env_var_is_set_and_not_empty_string("NO_COLOR") {
false
} else if env_var_is_set_and_not_empty_string("CLICOLOR_FORCE")
|| enabled_virtual_terminal()
|| term_env_var_is_set_and_not_dumb()
{
true
} else {
io::stdout().is_terminal()
}
}
fn env_var_is_set_and_not_empty_string(var: &str) -> bool {
let Ok(value) = env::var(var) else {
return false;
};
!value.is_empty()
}
#[cfg(windows)]
fn enabled_virtual_terminal() -> bool {
use std::ptr;
use winapi::um::{
consoleapi::{GetConsoleMode, SetConsoleMode},
fileapi::{CreateFileW, OPEN_EXISTING},
handleapi::INVALID_HANDLE_VALUE,
wincon::ENABLE_VIRTUAL_TERMINAL_PROCESSING,
winnt::{FILE_SHARE_READ, FILE_SHARE_WRITE, GENERIC_READ, GENERIC_WRITE},
};
let name = "CONOUT$\0".encode_utf16().collect::<Vec<u16>>();
unsafe {
let handle = CreateFileW(
name.as_ptr(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
ptr::null_mut(),
OPEN_EXISTING,
0,
ptr::null_mut(),
);
if handle == INVALID_HANDLE_VALUE {
return false;
}
let mut mode = 0;
if GetConsoleMode(handle, &mut mode) == 0 {
return false;
}
if mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
return true;
}
SetConsoleMode(handle, mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0
}
}
#[cfg(not(windows))]
fn enabled_virtual_terminal() -> bool {
false
}
fn term_env_var_is_set_and_not_dumb() -> bool {
env::var("TERM").map_or(false, |var| var != "dumb")
}