use crossterm::style::Color;
use crate::highlight::theme as md_theme;
use crate::terminal::TerminalCaps;
pub struct Palette;
impl Palette {
pub const BRAND: Color = Color::Magenta;
pub const MUTED_LIGHT: Color = Color::DarkGrey;
pub const MUTED_DARK: Color = Color::White;
pub const MUTED: Color = Self::MUTED_LIGHT;
pub const ACCENT: Color = Color::Cyan; pub const BORDER: Color = Color::Cyan; pub const WARNING: Color = Color::Yellow; pub const ERROR: Color = Color::Red; pub const DIFF_ADD: Color = Color::Green; pub const DIFF_REMOVE: Color = Color::Red; pub const CODE: Color = Color::Cyan; }
pub fn muted_for_current_theme() -> Color {
if md_theme::is_light_for_render() {
Palette::MUTED_LIGHT
} else {
Palette::MUTED_DARK
}
}
pub fn role(caps: TerminalCaps, role: Role) -> Option<Color> {
if !caps.colors {
return None;
}
match role {
Role::Brand => Some(Palette::BRAND),
Role::Muted => Some(muted_for_current_theme()),
Role::Accent => Some(Palette::ACCENT),
Role::AccentDim => Some(muted_for_current_theme()),
Role::Secondary => None,
Role::Border => Some(Palette::BORDER),
Role::Warning => Some(Palette::WARNING),
Role::Error => Some(Palette::ERROR),
Role::Success => Some(Palette::DIFF_ADD),
Role::DiffAdd => Some(Palette::DIFF_ADD),
Role::DiffRemove => Some(Palette::DIFF_REMOVE),
Role::ToolName => None,
}
}
#[derive(Debug, Clone, Copy)]
pub enum Role {
Brand,
Muted,
Accent,
AccentDim,
Secondary,
Border,
Warning,
Error,
Success,
DiffAdd,
DiffRemove,
ToolName,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::terminal::{EnvView, TerminalCaps};
fn caps(colors: bool) -> TerminalCaps {
TerminalCaps::from_env(EnvView {
is_stdout_tty: true,
no_color: !colors,
term: Some("xterm".to_string()),
colorterm: Some("truecolor".to_string()),
lang: Some("en_US.UTF-8".to_string()),
..Default::default()
})
}
#[test]
fn role_returns_none_when_colors_disabled() {
assert!(role(caps(false), Role::Brand).is_none());
}
#[test]
fn role_returns_palette_when_colors_enabled() {
assert_eq!(role(caps(true), Role::Brand), Some(Palette::BRAND));
}
#[test]
fn muted_switches_with_theme() {
use crate::highlight::theme as md_theme;
md_theme::set_theme_mode(false); assert_eq!(
role(caps(true), Role::Muted),
Some(Palette::MUTED_DARK),
"dark theme must use SGR 37 (regular white) for muted — \
SGR 90 reads invisible on Warp / iTerm2 / Mac Terminal dark"
);
assert_eq!(
role(caps(true), Role::AccentDim),
Some(Palette::MUTED_DARK),
"AccentDim must track the same muted shade as Role::Muted"
);
md_theme::set_theme_mode(true); assert_eq!(
role(caps(true), Role::Muted),
Some(Palette::MUTED_LIGHT),
"light theme must use SGR 90 (bright black) — `white` would \
be invisible against the white background"
);
assert_eq!(
role(caps(true), Role::AccentDim),
Some(Palette::MUTED_LIGHT)
);
md_theme::set_theme_mode(false);
}
#[test]
fn back_compat_muted_alias_is_light_variant() {
assert_eq!(Palette::MUTED, Palette::MUTED_LIGHT);
}
#[test]
fn secondary_and_toolname_return_none() {
assert!(role(caps(true), Role::Secondary).is_none());
assert!(role(caps(true), Role::ToolName).is_none());
}
}