use ctl_text::Style;
use ctl_text::anstyle::{AnsiColor, Color};
use crate::document::{NoticeLevel, Role};
const fn bold(color: AnsiColor) -> Style {
Style::new().bold().fg_color(Some(Color::Ansi(color)))
}
#[cfg_attr(
feature = "view",
doc = "",
doc = "[`View::theme`]: crate::View::theme"
)]
#[cfg_attr(
not(feature = "view"),
doc = "",
doc = "[`View::theme`]: crate#cargo-features"
)]
#[cfg_attr(feature = "app", doc = "", doc = "[`App::theme`]: crate::App::theme")]
#[cfg_attr(
not(feature = "app"),
doc = "",
doc = "[`App::theme`]: crate#cargo-features"
)]
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct Theme {
plain: Style,
heading: Style,
success: Style,
warning: Style,
error: Style,
value: Style,
muted: Style,
token: Style,
id: Style,
success_notice: Option<Style>,
warning_notice: Option<Style>,
error_notice: Option<Style>,
}
impl Theme {
#[must_use]
pub const fn new() -> Self {
Self {
plain: Style::new(),
heading: bold(AnsiColor::Cyan),
success: bold(AnsiColor::Green),
warning: bold(AnsiColor::Yellow),
error: bold(AnsiColor::Red),
value: bold(AnsiColor::Yellow),
muted: Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightBlack))),
token: bold(AnsiColor::Green),
id: Style::new().bold(),
success_notice: None,
warning_notice: None,
error_notice: None,
}
}
#[must_use]
pub const fn role_style(self, role: Role) -> Style {
match role {
Role::Plain => self.plain,
Role::Heading => self.heading,
Role::Success => self.success,
Role::Warning => self.warning,
Role::Error => self.error,
Role::Value => self.value,
Role::Muted => self.muted,
Role::Token => self.token,
Role::Id => self.id,
}
}
#[must_use]
pub const fn style(mut self, role: Role, style: Style) -> Self {
match role {
Role::Plain => self.plain = style,
Role::Heading => self.heading = style,
Role::Success => self.success = style,
Role::Warning => self.warning = style,
Role::Error => self.error = style,
Role::Value => self.value = style,
Role::Muted => self.muted = style,
Role::Token => self.token = style,
Role::Id => self.id = style,
}
self
}
#[must_use]
pub const fn label_style(self, level: NoticeLevel) -> Style {
let (own, role) = match level {
NoticeLevel::Success => (self.success_notice, self.success),
NoticeLevel::Warning => (self.warning_notice, self.warning),
NoticeLevel::Error => (self.error_notice, self.error),
};
match own {
Some(style) => style,
None => role,
}
}
#[must_use]
pub const fn notice_style(mut self, level: NoticeLevel, style: Style) -> Self {
match level {
NoticeLevel::Success => self.success_notice = Some(style),
NoticeLevel::Warning => self.warning_notice = Some(style),
NoticeLevel::Error => self.error_notice = Some(style),
}
self
}
}
impl Default for Theme {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use ctl_text::Style;
use ctl_text::anstyle::{AnsiColor, Color, Effects};
use super::Theme;
use crate::document::{NoticeLevel, Role};
#[test]
fn the_default_palette_is_bold_named_colours() {
let theme = Theme::default();
let cyan = Style::new()
.bold()
.fg_color(Some(Color::Ansi(AnsiColor::Cyan)));
assert_eq!(theme.role_style(Role::Heading), cyan);
assert_eq!(theme.role_style(Role::Plain), Style::new());
assert_eq!(theme.role_style(Role::Id), Style::new().bold());
assert_eq!(
theme.label_style(NoticeLevel::Error),
theme.role_style(Role::Error)
);
}
#[test]
fn replacing_one_role_keeps_the_others() {
let underline = Style::new().effects(Effects::UNDERLINE);
let theme = Theme::new().style(Role::Muted, underline);
assert_eq!(theme.role_style(Role::Muted), underline);
assert_eq!(
theme.role_style(Role::Value),
Theme::new().role_style(Role::Value)
);
assert_eq!(
theme.label_style(NoticeLevel::Warning),
Theme::new().label_style(NoticeLevel::Warning)
);
}
#[test]
fn a_notice_label_has_its_own_style() {
let plain = Theme::new().notice_style(NoticeLevel::Success, Style::new());
assert_eq!(plain.label_style(NoticeLevel::Success), Style::new());
assert_eq!(
plain.role_style(Role::Success),
Theme::new().role_style(Role::Success)
);
}
#[test]
fn a_notice_label_follows_its_role_until_given_its_own_style() {
let magenta = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Magenta)));
let follows = Theme::new().style(Role::Error, magenta);
assert_eq!(follows.label_style(NoticeLevel::Error), magenta);
let underline = Style::new().effects(Effects::UNDERLINE);
let own = Theme::new()
.notice_style(NoticeLevel::Error, underline)
.style(Role::Error, magenta);
assert_eq!(own.label_style(NoticeLevel::Error), underline);
assert_eq!(own.role_style(Role::Error), magenta);
}
}