pub mod color;
use color::Color;
#[derive(PartialEq, Default)]
pub enum TextStyle {
#[default]
Normal,
Bold,
Italics,
Underlined,
Strikethrough,
}
impl TextStyle {
pub fn style_key(&self) -> &str {
match self {
TextStyle::Normal => "",
TextStyle::Bold => "1",
TextStyle::Italics => "3",
TextStyle::Underlined => "4",
TextStyle::Strikethrough => "9",
}
}
}
pub struct ElementTheme {
pub fg: Option<Color>,
pub bg: Option<Color>,
pub style: TextStyle,
}
pub struct Theme {
pub header_1: ElementTheme,
pub header_x: ElementTheme,
pub code_block: ElementTheme,
pub indents: ElementTheme,
pub link: ElementTheme,
pub list: ElementTheme,
pub strong: ElementTheme,
pub emphasis: ElementTheme,
pub delete: ElementTheme,
}
const T_ESC: &str = "\u{1b}";
const T_FG: &str = "38";
const T_BG: &str = "48";
impl ElementTheme {
pub fn new(fg: Option<&str>, bg: Option<&str>, style: TextStyle) -> Self {
let bg_color = bg.map(Color::new);
let fg_color = fg.map(Color::new);
Self {
fg: fg_color,
bg: bg_color,
style,
}
}
pub fn write<F, T>(
&self,
write_text: F,
writer: &mut T,
is_writer_tty: &bool,
) -> Result<(), std::io::Error>
where
F: Fn(&mut T) -> Result<(), std::io::Error>,
T: std::io::Write,
{
if !is_writer_tty {
return write_text(writer);
}
let style_key = match self.style {
TextStyle::Normal => "".to_string(),
_ => format!("{};", self.style.style_key()),
};
match (&self.fg, &self.bg) {
(Some(fg), Some(bg)) => {
write!(
writer,
"{T_ESC}[{style_key}{T_BG};2;{};{T_FG};2;{}m",
bg.rgb(),
fg.rgb()
)?;
}
(Some(fg), None) => {
write!(writer, "{T_ESC}[{style_key}{T_FG};2;{}m", fg.rgb())?;
}
(None, Some(bg)) => {
write!(writer, "{T_ESC}[{style_key}{T_BG};2;{}m", bg.rgb())?;
}
(None, None) => {
if self.style != TextStyle::Normal {
write!(writer, "{T_ESC}[{}m", self.style.style_key())?;
}
}
};
write_text(writer)?;
match self {
ElementTheme {
fg: None,
bg: None,
style: TextStyle::Normal,
} => {
write!(writer, "")
}
_ => write!(writer, "{T_ESC}[0m"),
}
}
}
pub fn get_dark_theme() -> Theme {
Theme {
header_1: ElementTheme::new(None, Some("#6155FB"), TextStyle::Normal),
header_x: ElementTheme::new(Some("#01AFFD"), None, TextStyle::Normal),
code_block: ElementTheme::new(Some("#FF6060"), Some("#303030"), TextStyle::Normal),
indents: ElementTheme::new(Some("#555"), None, TextStyle::Normal),
link: ElementTheme::new(Some("#008787"), None, TextStyle::Underlined),
list: ElementTheme::new(None, None, TextStyle::Normal),
strong: ElementTheme::new(None, None, TextStyle::Bold),
emphasis: ElementTheme::new(None, None, TextStyle::Italics),
delete: ElementTheme::new(None, None, TextStyle::Strikethrough),
}
}
pub fn get_light_theme() -> Theme {
Theme {
header_1: ElementTheme::new(Some("#FFF"), Some("#6155FB"), TextStyle::Normal),
header_x: ElementTheme::new(Some("#01AFFD"), None, TextStyle::Normal),
code_block: ElementTheme::new(Some("#EA3323"), Some("#E4E4E4"), TextStyle::Normal),
indents: ElementTheme::new(None, None, TextStyle::Normal),
link: ElementTheme::new(Some("#5CBC9A"), None, TextStyle::Underlined),
list: ElementTheme::new(None, None, TextStyle::Normal),
strong: ElementTheme::new(None, None, TextStyle::Bold),
emphasis: ElementTheme::new(None, None, TextStyle::Italics),
delete: ElementTheme::new(None, None, TextStyle::Strikethrough),
}
}
pub fn get_default_theme() -> Theme {
let theme = get_terminal_theme().unwrap_or(termbg::Theme::Dark);
match theme {
termbg::Theme::Light => get_light_theme(),
_ => get_dark_theme(),
}
}
fn get_terminal_theme() -> Option<termbg::Theme> {
let timeout = std::time::Duration::from_millis(500);
let result = std::panic::catch_unwind(|| termbg::theme(timeout));
match result {
Ok(theme) => theme.ok(),
Err(_) => None,
}
}
#[cfg(test)]
mod test {
mod write {
use super::super::*;
use crate::ElementTheme;
use colored::Colorize;
use colored::CustomColor;
use std::io::Write;
macro_rules! should_equal {
($($name:ident: $value:expr,)*) => {
$(
#[test]
fn $name() {
let (value, fg, bg, style, expected) = $value;
let theme = ElementTheme::new(fg, bg, style);
let mut writer = Vec::new();
theme.write(|w| write!(w, "{}", value), &mut writer, &true).unwrap();
let text = std::str::from_utf8(&writer).unwrap();
let expected = format!("{}", expected);
assert_eq!(expected, text);
}
)*
}
}
should_equal! {
should_write_plain_text: ("Hello", None, None, TextStyle::Normal, "Hello".normal()),
should_write_fg: ("Hello", Some("#F52"), None, TextStyle::Normal, "Hello".custom_color(CustomColor::new(255, 85, 34))),
should_write_bg: ("Hello", None, Some("#54FD10"), TextStyle::Normal, "Hello".on_custom_color(CustomColor::new(84, 253, 16))),
should_write_fg_and_bg: ("Hello", Some("#F52"), Some("54FD10"), TextStyle::Normal, "Hello".custom_color(CustomColor::new(255, 85, 34)).on_custom_color(CustomColor::new(84, 253, 16))),
should_write_bold_text: ("Hello", None, None, TextStyle::Bold, "Hello".bold()),
should_write_italics_text: ("Hello", None, None, TextStyle::Italics, "Hello".italic()),
should_write_underlined_text: ("Hello", None, None, TextStyle::Underlined, "Hello".underline()),
should_write_strikethrough_text: ("Hello", None, None, TextStyle::Strikethrough, "Hello".strikethrough()),
}
}
}