use std::sync::{Arc, OnceLock, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ColorProfileKind {
TrueColor,
ANSI256,
ANSI,
NoColor,
}
#[derive(Debug, Clone)]
pub struct Renderer {
inner: Arc<RwLock<Inner>>,
}
#[derive(Debug)]
struct Inner {
output: Option<Output>,
color_profile: ColorProfileKind,
explicit_color_profile: bool,
color_profile_once: OnceLock<()>,
has_dark_background: bool,
explicit_background: bool,
has_dark_background_once: OnceLock<()>,
}
impl Default for Renderer {
fn default() -> Self {
let inner = Inner {
output: Some(detect_output()),
color_profile: ColorProfileKind::ANSI, explicit_color_profile: false,
color_profile_once: OnceLock::new(),
has_dark_background: true, explicit_background: false,
has_dark_background_once: OnceLock::new(),
};
Self {
inner: Arc::new(RwLock::new(inner)),
}
}
}
impl Renderer {
pub fn new() -> Self {
Self::default()
}
pub fn new_with_output(o: Output) -> Self {
let r = Self::default();
if let Ok(mut inner) = r.inner.write() {
inner.output = Some(o);
}
r
}
pub fn color_profile(&self) -> ColorProfileKind {
let mut guard = self.inner.write().expect("renderer lock poisoned");
if !guard.explicit_color_profile && guard.color_profile_once.get().is_none() {
let output_ref = guard.output.as_ref();
guard.color_profile = detect_color_profile(output_ref);
let _ = guard.color_profile_once.set(());
}
guard.color_profile
}
pub fn set_color_profile(&mut self, p: ColorProfileKind) {
let mut guard = self.inner.write().expect("renderer lock poisoned");
guard.color_profile = p;
guard.explicit_color_profile = true;
}
pub fn has_dark_background(&self) -> bool {
let mut guard = self.inner.write().expect("renderer lock poisoned");
if !guard.explicit_background && guard.has_dark_background_once.get().is_none() {
guard.has_dark_background = detect_dark_background();
let _ = guard.has_dark_background_once.set(());
}
guard.has_dark_background
}
pub fn set_has_dark_background(&mut self, b: bool) {
let mut guard = self.inner.write().expect("renderer lock poisoned");
guard.has_dark_background = b;
guard.explicit_background = true;
}
pub fn output(&self) -> Option<Output> {
self.inner
.read()
.ok()
.and_then(|g| g.output.as_ref().cloned())
}
pub fn set_output(&mut self, o: Output) {
if let Ok(mut guard) = self.inner.write() {
guard.output = Some(o);
}
}
pub fn take_output_clone(&self) -> Option<Output> {
self.output()
}
}
static DEFAULT_RENDERER: OnceLock<Renderer> = OnceLock::new();
fn default_renderer_cell() -> &'static Renderer {
DEFAULT_RENDERER.get_or_init(Renderer::default)
}
pub fn default_renderer() -> &'static Renderer {
default_renderer_cell()
}
pub fn set_default_renderer(r: Renderer) {
if DEFAULT_RENDERER.set(r.clone()).is_err() {
if let Some(existing) = DEFAULT_RENDERER.get() {
if let (Ok(mut dst), Ok(src)) = (existing.inner.write(), r.inner.read()) {
dst.output = src.output.clone();
dst.color_profile = src.color_profile;
dst.explicit_color_profile = src.explicit_color_profile;
if src.color_profile_once.get().is_some() {
let _ = dst.color_profile_once.set(());
}
dst.has_dark_background = src.has_dark_background;
dst.explicit_background = src.explicit_background;
if src.has_dark_background_once.get().is_some() {
let _ = dst.has_dark_background_once.set(());
}
}
}
}
}
pub fn color_profile() -> ColorProfileKind {
default_renderer().color_profile()
}
pub fn set_color_profile(p: ColorProfileKind) {
let _ = DEFAULT_RENDERER.get_or_init(Renderer::default);
if let Some(r) = DEFAULT_RENDERER.get() {
if let Ok(mut inner) = r.inner.write() {
inner.color_profile = p;
inner.explicit_color_profile = true;
let _ = inner.color_profile_once.set(());
}
}
}
pub fn has_dark_background() -> bool {
default_renderer().has_dark_background()
}
pub fn set_has_dark_background(b: bool) {
let _ = DEFAULT_RENDERER.get_or_init(Renderer::default);
if let Some(r) = DEFAULT_RENDERER.get() {
if let Ok(mut inner) = r.inner.write() {
inner.has_dark_background = b;
inner.explicit_background = true;
let _ = inner.has_dark_background_once.set(());
}
}
}
#[derive(Debug, Clone)]
pub struct Output {
pub supports_ansi: bool,
pub is_tty_like: bool,
}
fn detect_color_profile(output: Option<&Output>) -> ColorProfileKind {
let no_color = std::env::var("NO_COLOR").ok().is_some();
if no_color {
return ColorProfileKind::NoColor;
}
let colorterm = std::env::var("COLORTERM")
.unwrap_or_default()
.to_lowercase();
let term = std::env::var("TERM").unwrap_or_default().to_lowercase();
if colorterm.contains("truecolor") || colorterm.contains("24bit") {
return ColorProfileKind::TrueColor;
}
if term.contains("256color") {
return ColorProfileKind::ANSI256;
}
if term.contains("color") {
return ColorProfileKind::ANSI;
}
if let Some(out) = output {
if !out.supports_ansi {
return ColorProfileKind::NoColor;
}
}
ColorProfileKind::NoColor
}
fn detect_dark_background() -> bool {
if let Ok(val) = std::env::var("COLORFGBG") {
let parts: Vec<&str> = val.split(';').collect();
if let Some(bg_str) = parts.last() {
if let Ok(bg) = bg_str.parse::<u8>() {
return bg <= 6;
}
}
}
true
}
fn detect_output() -> Output {
use std::io::IsTerminal;
let is_tty_like = std::io::stdout().is_terminal();
let no_color = std::env::var("NO_COLOR").is_ok();
let supports_ansi = is_tty_like && !no_color;
Output {
supports_ansi,
is_tty_like,
}
}