use std::{
env,
io::{self, IsTerminal},
};
use owo_colors::Style;
#[derive(Debug, Clone)]
pub struct GraphicalTheme {
pub(crate) characters: ThemeCharacters,
pub(crate) styles: ThemeStyles,
}
fn force_color() -> bool {
env::var("CI").is_ok() || env::var("FORCE_COLOR").is_ok_and(|env| env != "0")
}
impl Default for GraphicalTheme {
fn default() -> Self {
Self::new(io::stdout().is_terminal() && io::stderr().is_terminal())
}
}
impl GraphicalTheme {
#[must_use]
pub(crate) fn new(is_terminal: bool) -> Self {
if force_color() {
return Self::unicode();
}
match env::var("NO_COLOR") {
_ if !is_terminal => Self::none(),
Ok(string) if string != "0" => Self::unicode_nocolor(),
_ => Self::unicode(),
}
}
#[must_use]
pub fn unicode() -> Self {
Self { characters: ThemeCharacters::unicode(), styles: ThemeStyles::rgb() }
}
#[must_use]
pub fn unicode_nocolor() -> Self {
Self { characters: ThemeCharacters::unicode(), styles: ThemeStyles::none() }
}
#[must_use]
pub fn none() -> Self {
Self { characters: ThemeCharacters::ascii(), styles: ThemeStyles::none() }
}
#[must_use]
pub const fn warning_style(&self) -> Style {
self.styles.warning
}
}
#[derive(Debug, Clone)]
#[expect(clippy::redundant_pub_crate, reason = "prevents public glob re-export")]
pub(crate) struct ThemeStyles {
pub(crate) error: Style,
pub(crate) warning: Style,
pub(crate) advice: Style,
pub(crate) help: Style,
pub(crate) note: Style,
pub(crate) link: Style,
pub(crate) linum: Style,
pub(crate) highlights: [Style; 3],
}
fn style() -> Style {
Style::new()
}
impl ThemeStyles {
fn rgb() -> Self {
Self {
error: style().fg_rgb::<225, 80, 80>().bold(), warning: style().fg_rgb::<244, 191, 117>().bold(),
advice: style().fg_rgb::<106, 159, 181>(),
help: style().fg_rgb::<106, 159, 181>(),
note: style().fg_rgb::<106, 159, 181>(),
link: style().fg_rgb::<92, 157, 255>().bold(),
linum: style().dimmed(),
highlights: [
style().fg_rgb::<246, 87, 248>(),
style().fg_rgb::<30, 201, 212>(),
style().fg_rgb::<145, 246, 111>(),
],
}
}
fn none() -> Self {
Self {
error: style(),
warning: style(),
advice: style(),
help: style(),
note: style(),
link: style(),
linum: style(),
highlights: [style(); 3],
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[expect(clippy::redundant_pub_crate, reason = "prevents public glob re-export")]
pub(crate) struct ThemeCharacters {
pub(crate) hbar: char,
pub(crate) vbar: char,
pub(crate) vbar_break: char,
pub(crate) uarrow: char,
pub(crate) rarrow: char,
pub(crate) ltop: char,
pub(crate) lbot: char,
pub(crate) lcross: char,
pub(crate) rcross: char,
pub(crate) underbar: char,
pub(crate) underline: char,
pub(crate) error: &'static str,
pub(crate) warning: &'static str,
pub(crate) advice: &'static str,
}
impl ThemeCharacters {
const fn unicode() -> Self {
Self {
hbar: '─',
vbar: '│',
vbar_break: '·',
uarrow: '▲',
rarrow: '▶',
ltop: '╭',
lbot: '╰',
lcross: '├',
rcross: '┤',
underbar: '┬',
underline: '─',
error: "×",
warning: "⚠",
advice: "☞",
}
}
const fn ascii() -> Self {
Self {
hbar: '-',
vbar: '|',
vbar_break: ':',
uarrow: '^',
rarrow: '>',
ltop: ',',
lbot: '`',
lcross: '|',
rcross: '|',
underbar: '|',
underline: '^',
error: "x",
warning: "!",
advice: ">",
}
}
}