use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum Color {
Reset,
Text,
Muted,
Dim,
Accent,
Primary,
Info,
Link,
Success,
Warning,
Error,
Harness,
Thinking,
Tool,
User,
Assistant,
Code,
Border,
BorderAccent,
BorderMuted,
C1,
C2,
C3,
C4,
C5,
C6,
C7,
C8,
C9,
C10,
C11,
C12,
Surface1,
Surface2,
Surface3,
SelectedBg,
SuccessBg,
ErrorBg,
WarningBg,
InfoBg,
AccentBg,
DiffAdded,
DiffRemoved,
DiffContext,
Other(String),
}
pub const COLOR_TOKENS: [&str; 44] = [
"reset",
"text",
"muted",
"dim",
"accent",
"primary",
"info",
"link",
"success",
"warning",
"error",
"harness",
"thinking",
"tool",
"user",
"assistant",
"code",
"border",
"borderAccent",
"borderMuted",
"c1",
"c2",
"c3",
"c4",
"c5",
"c6",
"c7",
"c8",
"c9",
"c10",
"c11",
"c12",
"surface1",
"surface2",
"surface3",
"selectedBg",
"successBg",
"errorBg",
"warningBg",
"infoBg",
"accentBg",
"diffAdded",
"diffRemoved",
"diffContext",
];
impl Color {
#[must_use]
pub fn as_str(&self) -> Option<&str> {
let name = match self {
Self::Reset => "reset",
Self::Text => "text",
Self::Muted => "muted",
Self::Dim => "dim",
Self::Accent => "accent",
Self::Primary => "primary",
Self::Info => "info",
Self::Link => "link",
Self::Success => "success",
Self::Warning => "warning",
Self::Error => "error",
Self::Harness => "harness",
Self::Thinking => "thinking",
Self::Tool => "tool",
Self::User => "user",
Self::Assistant => "assistant",
Self::Code => "code",
Self::Border => "border",
Self::BorderAccent => "borderAccent",
Self::BorderMuted => "borderMuted",
Self::C1 => "c1",
Self::C2 => "c2",
Self::C3 => "c3",
Self::C4 => "c4",
Self::C5 => "c5",
Self::C6 => "c6",
Self::C7 => "c7",
Self::C8 => "c8",
Self::C9 => "c9",
Self::C10 => "c10",
Self::C11 => "c11",
Self::C12 => "c12",
Self::Surface1 => "surface1",
Self::Surface2 => "surface2",
Self::Surface3 => "surface3",
Self::SelectedBg => "selectedBg",
Self::SuccessBg => "successBg",
Self::ErrorBg => "errorBg",
Self::WarningBg => "warningBg",
Self::InfoBg => "infoBg",
Self::AccentBg => "accentBg",
Self::DiffAdded => "diffAdded",
Self::DiffRemoved => "diffRemoved",
Self::DiffContext => "diffContext",
Self::Other(_) => return None,
};
Some(name)
}
#[must_use]
pub fn parse(name: &str) -> Self {
Self::parse_known(name).unwrap_or_else(|| Self::Other(name.to_owned()))
}
fn parse_known(name: &str) -> Option<Self> {
Some(match name {
"reset" => Self::Reset,
"text" => Self::Text,
"muted" => Self::Muted,
"dim" => Self::Dim,
"accent" => Self::Accent,
"primary" => Self::Primary,
"info" => Self::Info,
"link" => Self::Link,
"success" => Self::Success,
"warning" => Self::Warning,
"error" => Self::Error,
"harness" => Self::Harness,
"thinking" => Self::Thinking,
"tool" => Self::Tool,
"user" => Self::User,
"assistant" => Self::Assistant,
"code" => Self::Code,
"border" => Self::Border,
"borderAccent" => Self::BorderAccent,
"borderMuted" => Self::BorderMuted,
"c1" => Self::C1,
"c2" => Self::C2,
"c3" => Self::C3,
"c4" => Self::C4,
"c5" => Self::C5,
"c6" => Self::C6,
"c7" => Self::C7,
"c8" => Self::C8,
"c9" => Self::C9,
"c10" => Self::C10,
"c11" => Self::C11,
"c12" => Self::C12,
"surface1" => Self::Surface1,
"surface2" => Self::Surface2,
"surface3" => Self::Surface3,
"selectedBg" => Self::SelectedBg,
"successBg" => Self::SuccessBg,
"errorBg" => Self::ErrorBg,
"warningBg" => Self::WarningBg,
"infoBg" => Self::InfoBg,
"accentBg" => Self::AccentBg,
"diffAdded" => Self::DiffAdded,
"diffRemoved" => Self::DiffRemoved,
"diffContext" => Self::DiffContext,
_ => return None,
})
}
#[must_use]
pub fn is_unknown(&self) -> bool {
matches!(self, Self::Other(_))
}
}
impl Serialize for Color {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
if let Self::Other(name) = self {
return serializer.serialize_str(name);
}
serializer.serialize_str(self.as_str().unwrap_or_default())
}
}
impl<'de> Deserialize<'de> for Color {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let name = String::deserialize(deserializer)?;
Ok(Self::parse(&name))
}
}
#[allow(
non_camel_case_types,
reason = "wire names are ratatui's constant names (§4.2)"
)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Modifier {
BOLD,
DIM,
ITALIC,
UNDERLINED,
SLOW_BLINK,
RAPID_BLINK,
REVERSED,
HIDDEN,
CROSSED_OUT,
}
pub const ALL_MODIFIERS: [Modifier; 9] = [
Modifier::BOLD,
Modifier::DIM,
Modifier::ITALIC,
Modifier::UNDERLINED,
Modifier::SLOW_BLINK,
Modifier::RAPID_BLINK,
Modifier::REVERSED,
Modifier::HIDDEN,
Modifier::CROSSED_OUT,
];
#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
pub struct Modifiers(u16);
impl Modifiers {
#[must_use]
pub fn new() -> Self {
Self(0)
}
pub fn insert(&mut self, m: Modifier) {
self.0 |= 1 << (m as u16);
}
pub fn remove(&mut self, m: Modifier) {
self.0 &= !(1 << (m as u16));
}
#[must_use]
pub fn contains(&self, m: Modifier) -> bool {
self.0 & (1 << (m as u16)) != 0
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0 == 0
}
#[must_use]
pub fn union(mut self, other: Self) -> Self {
self.0 |= other.0;
self
}
pub fn iter(&self) -> impl Iterator<Item = Modifier> + '_ {
ALL_MODIFIERS.into_iter().filter(|m| self.contains(*m))
}
#[must_use]
pub fn from_iter_const<I: IntoIterator<Item = Modifier>>(mods: I) -> Self {
let mut set = Self::new();
for m in mods {
set.insert(m);
}
set
}
}
impl Serialize for Modifiers {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_seq(self.iter())
}
}
impl<'de> Deserialize<'de> for Modifiers {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let mods = Vec::<Modifier>::deserialize(deserializer)?;
Ok(Self::from_iter_const(mods))
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", default)]
pub struct Style {
#[serde(skip_serializing_if = "Option::is_none")]
pub fg: Option<Color>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bg: Option<Color>,
#[serde(skip_serializing_if = "Option::is_none")]
pub underline_color: Option<Color>,
#[serde(skip_serializing_if = "Modifiers::is_empty")]
pub add_modifier: Modifiers,
#[serde(skip_serializing_if = "Modifiers::is_empty")]
pub sub_modifier: Modifiers,
}
impl Style {
#[must_use]
pub fn new() -> Self {
Self {
fg: None,
bg: None,
underline_color: None,
add_modifier: Modifiers::new(),
sub_modifier: Modifiers::new(),
}
}
#[must_use]
pub fn fg(mut self, color: Color) -> Self {
self.fg = Some(color);
self
}
#[must_use]
pub fn bg(mut self, color: Color) -> Self {
self.bg = Some(color);
self
}
#[must_use]
pub fn underline_color(mut self, color: Color) -> Self {
self.underline_color = Some(color);
self
}
#[must_use]
pub fn add_modifier(mut self, m: impl IntoIterator<Item = Modifier>) -> Self {
self.add_modifier = self.add_modifier.union(Modifiers::from_iter_const(m));
self
}
#[must_use]
pub fn sub_modifier(mut self, m: impl IntoIterator<Item = Modifier>) -> Self {
self.sub_modifier = self.sub_modifier.union(Modifiers::from_iter_const(m));
self
}
#[must_use]
pub fn patch(mut self, other: Self) -> Self {
if other.fg.is_some() {
self.fg = other.fg;
}
if other.bg.is_some() {
self.bg = other.bg;
}
if other.underline_color.is_some() {
self.underline_color = other.underline_color;
}
self.add_modifier = self.add_modifier.union(other.add_modifier);
self.sub_modifier = self.sub_modifier.union(other.sub_modifier);
self
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.fg.is_none()
&& self.bg.is_none()
&& self.underline_color.is_none()
&& self.add_modifier.is_empty()
&& self.sub_modifier.is_empty()
}
}
pub trait Stylize<T>: Sized {
fn style(self, style: Style) -> T;
fn fg(self, color: Color) -> T {
self.style(Style::new().fg(color))
}
fn bg(self, color: Color) -> T {
self.style(Style::new().bg(color))
}
fn underline_color(self, color: Color) -> T {
self.style(Style::new().underline_color(color))
}
fn bold(self) -> T {
self.style(Style::new().add_modifier([Modifier::BOLD]))
}
fn dim(self) -> T {
self.style(Style::new().add_modifier([Modifier::DIM]))
}
fn italic(self) -> T {
self.style(Style::new().add_modifier([Modifier::ITALIC]))
}
fn underlined(self) -> T {
self.style(Style::new().add_modifier([Modifier::UNDERLINED]))
}
fn slow_blink(self) -> T {
self.style(Style::new().add_modifier([Modifier::SLOW_BLINK]))
}
fn rapid_blink(self) -> T {
self.style(Style::new().add_modifier([Modifier::RAPID_BLINK]))
}
fn reversed(self) -> T {
self.style(Style::new().add_modifier([Modifier::REVERSED]))
}
fn hidden(self) -> T {
self.style(Style::new().add_modifier([Modifier::HIDDEN]))
}
fn crossed_out(self) -> T {
self.style(Style::new().add_modifier([Modifier::CROSSED_OUT]))
}
}