use std::{
env, fmt,
io::{self, IsTerminal},
};
#[derive(Debug, Clone)]
pub struct GraphicalTheme {
pub(super) characters: ThemeCharacters,
pub(super) styles: ThemeStyles,
}
fn force_color() -> bool {
env::var_os("CI").is_some() || env::var_os("FORCE_COLOR").is_some_and(|value| value != "0")
}
impl Default for GraphicalTheme {
fn default() -> Self {
Self::new(io::stdout().is_terminal() && io::stderr().is_terminal())
}
}
impl GraphicalTheme {
#[must_use]
pub(super) fn new(is_terminal: bool) -> Self {
if force_color() {
return Self::unicode();
}
match env::var_os("NO_COLOR") {
_ if !is_terminal => Self::none(),
Some(value) if value != "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() }
}
pub fn write_warning(&self, f: &mut impl fmt::Write, value: impl fmt::Display) -> fmt::Result {
self.styles.warning.write(f, value)
}
}
const ANSI_RESET: &str = "\x1b[0m";
const ANSI_PREFIXES: [&str; 9] = [
"",
"\x1b[38;2;225;80;80;1m",
"\x1b[38;2;244;191;117;1m",
"\x1b[38;2;106;159;181m",
"\x1b[38;2;92;157;255;1m",
"\x1b[2m",
"\x1b[38;2;246;87;248m",
"\x1b[38;2;30;201;212m",
"\x1b[38;2;145;246;111m",
];
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
#[repr(u8)]
pub(super) enum Style {
Plain,
Error,
Warning,
Info,
Link,
LineNumber,
Highlight1,
Highlight2,
Highlight3,
}
impl Style {
const fn prefix(self) -> &'static str {
ANSI_PREFIXES[self as usize]
}
pub(super) const fn is_plain(self) -> bool {
matches!(self, Self::Plain)
}
fn write(self, f: &mut impl fmt::Write, value: impl fmt::Display) -> fmt::Result {
let prefix = self.prefix();
f.write_str(prefix)?;
write!(f, "{value}")?;
if self.is_plain() { Ok(()) } else { f.write_str(ANSI_RESET) }
}
}
pub(super) struct Styled<T> {
target: T,
style: Style,
}
impl<T: fmt::Display> fmt::Display for Styled<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let prefix = self.style.prefix();
f.write_str(prefix)?;
self.target.fmt(f)?;
if self.style.is_plain() { Ok(()) } else { f.write_str(ANSI_RESET) }
}
}
pub(super) trait DiagnosticColorize: Sized {
fn style(self, style: Style) -> Styled<Self> {
Styled { target: self, style }
}
}
impl<T> DiagnosticColorize for T {}
#[derive(Debug, Clone)]
pub(super) struct ThemeStyles {
pub(super) error: Style,
pub(super) warning: Style,
pub(super) advice: Style,
pub(super) help: Style,
pub(super) note: Style,
pub(super) link: Style,
pub(super) linum: Style,
pub(super) highlights: [Style; 3],
}
impl ThemeStyles {
fn rgb() -> Self {
Self {
error: Style::Error,
warning: Style::Warning,
advice: Style::Info,
help: Style::Info,
note: Style::Info,
link: Style::Link,
linum: Style::LineNumber,
highlights: [Style::Highlight1, Style::Highlight2, Style::Highlight3],
}
}
fn none() -> Self {
Self {
error: Style::Plain,
warning: Style::Plain,
advice: Style::Plain,
help: Style::Plain,
note: Style::Plain,
link: Style::Plain,
linum: Style::Plain,
highlights: [Style::Plain; 3],
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub(super) struct ThemeCharacters {
pub(super) hbar: char,
pub(super) vbar: char,
pub(super) vbar_break: char,
pub(super) uarrow: char,
pub(super) rarrow: char,
pub(super) ltop: char,
pub(super) lbot: char,
pub(super) lcross: char,
pub(super) rcross: char,
pub(super) underbar: char,
pub(super) underline: char,
pub(super) error: &'static str,
pub(super) warning: &'static str,
pub(super) 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: ">",
}
}
}