use std::cell::RefCell;
use std::io::IsTerminal;
use crate::terminal::{
color_level_for_capabilities, terminal_capabilities, ColorLevel, TerminalCapabilities,
};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ColorMode {
#[default]
Auto,
Always,
Never,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum RenderTarget {
Stdout,
Stderr,
Terminal(bool),
Capabilities(TerminalCapabilities),
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub enum ColorDepthMode {
#[default]
Auto,
NoColor,
Ansi16,
Ansi256,
TrueColor,
}
#[derive(Clone, Debug)]
pub struct ColorizeConfig {
color_mode: ColorMode,
color_depth_mode: ColorDepthMode,
}
thread_local! {
static CONFIG: RefCell<ColorizeConfig> = RefCell::new(ColorizeConfig::default());
#[cfg(test)]
#[allow(clippy::missing_const_for_thread_local)]
static STDOUT_TERMINAL_OVERRIDE: RefCell<Option<bool>> = RefCell::new(None);
#[cfg(test)]
#[allow(clippy::missing_const_for_thread_local)]
static STDERR_TERMINAL_OVERRIDE: RefCell<Option<bool>> = RefCell::new(None);
}
impl Default for ColorizeConfig {
fn default() -> Self {
Self {
color_mode: ColorMode::Auto,
color_depth_mode: ColorDepthMode::Auto,
}
}
}
impl ColorizeConfig {
pub fn set_color_mode(mode: ColorMode) {
CONFIG.with(|config| config.borrow_mut().color_mode = mode);
}
pub fn color_mode() -> ColorMode {
CONFIG.with(|config| config.borrow().color_mode)
}
pub fn set_color_depth_mode(mode: ColorDepthMode) {
CONFIG.with(|config| config.borrow_mut().color_depth_mode = mode);
}
pub fn color_depth_mode() -> ColorDepthMode {
CONFIG.with(|config| config.borrow().color_depth_mode)
}
pub fn terminal_capabilities(target: RenderTarget) -> TerminalCapabilities {
capabilities_for(target)
}
pub fn color_level(target: RenderTarget) -> ColorLevel {
capabilities_for(target).color_level
}
#[deprecated(note = "use ColorizeConfig::set_color_mode(ColorMode) instead")]
pub fn set_terminal_check(check: bool) {
let mode = if check {
ColorMode::Auto
} else {
ColorMode::Always
};
Self::set_color_mode(mode);
}
}
pub(crate) fn color_level() -> ColorLevel {
color_level_for(RenderTarget::Stdout)
}
pub(crate) fn color_level_for(target: RenderTarget) -> ColorLevel {
capabilities_for(target).color_level
}
fn capabilities_for(target: RenderTarget) -> TerminalCapabilities {
let color_mode = ColorizeConfig::color_mode();
let depth_mode = ColorizeConfig::color_depth_mode();
match target {
RenderTarget::Capabilities(capabilities) => TerminalCapabilities {
color_level: color_level_for_capabilities(capabilities, color_mode, depth_mode),
..capabilities
},
RenderTarget::Stdout => terminal_capabilities(stdout_is_terminal(), color_mode, depth_mode),
RenderTarget::Stderr => terminal_capabilities(stderr_is_terminal(), color_mode, depth_mode),
RenderTarget::Terminal(is_terminal) => {
terminal_capabilities(is_terminal, color_mode, depth_mode)
}
}
}
fn stdout_is_terminal() -> bool {
#[cfg(test)]
if let Some(value) = STDOUT_TERMINAL_OVERRIDE.with(|override_value| *override_value.borrow()) {
return value;
}
std::io::stdout().is_terminal()
}
fn stderr_is_terminal() -> bool {
#[cfg(test)]
if let Some(value) = STDERR_TERMINAL_OVERRIDE.with(|override_value| *override_value.borrow()) {
return value;
}
std::io::stderr().is_terminal()
}
#[cfg(test)]
pub(crate) fn set_terminal_override_for_tests(value: Option<bool>) {
STDOUT_TERMINAL_OVERRIDE.with(|override_value| *override_value.borrow_mut() = value);
}
#[cfg(test)]
pub(crate) fn get_terminal_override_for_tests() -> Option<bool> {
STDOUT_TERMINAL_OVERRIDE.with(|override_value| *override_value.borrow())
}
#[cfg(test)]
pub(crate) fn set_stderr_terminal_override_for_tests(value: Option<bool>) {
STDERR_TERMINAL_OVERRIDE.with(|override_value| *override_value.borrow_mut() = value);
}
#[cfg(test)]
pub(crate) fn get_stderr_terminal_override_for_tests() -> Option<bool> {
STDERR_TERMINAL_OVERRIDE.with(|override_value| *override_value.borrow())
}