1use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9#[derive(Clone, Debug, PartialEq, Eq, Hash)]
12pub enum Color {
13 Reset,
15 Text,
17 Muted,
19 Dim,
21 Accent,
23 Primary,
25 Info,
27 Link,
29 Success,
31 Warning,
33 Error,
35 Harness,
37 Thinking,
39 Tool,
41 User,
43 Assistant,
45 Code,
47 Border,
49 BorderAccent,
51 BorderMuted,
53 C1,
55 C2,
57 C3,
59 C4,
61 C5,
63 C6,
65 C7,
67 C8,
69 C9,
71 C10,
73 C11,
75 C12,
77 Surface1,
79 Surface2,
81 Surface3,
83 SelectedBg,
85 SuccessBg,
87 ErrorBg,
89 WarningBg,
91 InfoBg,
93 AccentBg,
95 DiffAdded,
97 DiffRemoved,
99 DiffContext,
101 Other(String),
103}
104
105pub const COLOR_TOKENS: [&str; 44] = [
107 "reset",
108 "text",
109 "muted",
110 "dim",
111 "accent",
112 "primary",
113 "info",
114 "link",
115 "success",
116 "warning",
117 "error",
118 "harness",
119 "thinking",
120 "tool",
121 "user",
122 "assistant",
123 "code",
124 "border",
125 "borderAccent",
126 "borderMuted",
127 "c1",
128 "c2",
129 "c3",
130 "c4",
131 "c5",
132 "c6",
133 "c7",
134 "c8",
135 "c9",
136 "c10",
137 "c11",
138 "c12",
139 "surface1",
140 "surface2",
141 "surface3",
142 "selectedBg",
143 "successBg",
144 "errorBg",
145 "warningBg",
146 "infoBg",
147 "accentBg",
148 "diffAdded",
149 "diffRemoved",
150 "diffContext",
151];
152
153impl Color {
154 #[must_use]
156 pub fn as_str(&self) -> Option<&str> {
157 let name = match self {
158 Self::Reset => "reset",
159 Self::Text => "text",
160 Self::Muted => "muted",
161 Self::Dim => "dim",
162 Self::Accent => "accent",
163 Self::Primary => "primary",
164 Self::Info => "info",
165 Self::Link => "link",
166 Self::Success => "success",
167 Self::Warning => "warning",
168 Self::Error => "error",
169 Self::Harness => "harness",
170 Self::Thinking => "thinking",
171 Self::Tool => "tool",
172 Self::User => "user",
173 Self::Assistant => "assistant",
174 Self::Code => "code",
175 Self::Border => "border",
176 Self::BorderAccent => "borderAccent",
177 Self::BorderMuted => "borderMuted",
178 Self::C1 => "c1",
179 Self::C2 => "c2",
180 Self::C3 => "c3",
181 Self::C4 => "c4",
182 Self::C5 => "c5",
183 Self::C6 => "c6",
184 Self::C7 => "c7",
185 Self::C8 => "c8",
186 Self::C9 => "c9",
187 Self::C10 => "c10",
188 Self::C11 => "c11",
189 Self::C12 => "c12",
190 Self::Surface1 => "surface1",
191 Self::Surface2 => "surface2",
192 Self::Surface3 => "surface3",
193 Self::SelectedBg => "selectedBg",
194 Self::SuccessBg => "successBg",
195 Self::ErrorBg => "errorBg",
196 Self::WarningBg => "warningBg",
197 Self::InfoBg => "infoBg",
198 Self::AccentBg => "accentBg",
199 Self::DiffAdded => "diffAdded",
200 Self::DiffRemoved => "diffRemoved",
201 Self::DiffContext => "diffContext",
202 Self::Other(_) => return None,
203 };
204 Some(name)
205 }
206
207 #[must_use]
209 pub fn parse(name: &str) -> Self {
210 Self::parse_known(name).unwrap_or_else(|| Self::Other(name.to_owned()))
211 }
212
213 fn parse_known(name: &str) -> Option<Self> {
214 Some(match name {
215 "reset" => Self::Reset,
216 "text" => Self::Text,
217 "muted" => Self::Muted,
218 "dim" => Self::Dim,
219 "accent" => Self::Accent,
220 "primary" => Self::Primary,
221 "info" => Self::Info,
222 "link" => Self::Link,
223 "success" => Self::Success,
224 "warning" => Self::Warning,
225 "error" => Self::Error,
226 "harness" => Self::Harness,
227 "thinking" => Self::Thinking,
228 "tool" => Self::Tool,
229 "user" => Self::User,
230 "assistant" => Self::Assistant,
231 "code" => Self::Code,
232 "border" => Self::Border,
233 "borderAccent" => Self::BorderAccent,
234 "borderMuted" => Self::BorderMuted,
235 "c1" => Self::C1,
236 "c2" => Self::C2,
237 "c3" => Self::C3,
238 "c4" => Self::C4,
239 "c5" => Self::C5,
240 "c6" => Self::C6,
241 "c7" => Self::C7,
242 "c8" => Self::C8,
243 "c9" => Self::C9,
244 "c10" => Self::C10,
245 "c11" => Self::C11,
246 "c12" => Self::C12,
247 "surface1" => Self::Surface1,
248 "surface2" => Self::Surface2,
249 "surface3" => Self::Surface3,
250 "selectedBg" => Self::SelectedBg,
251 "successBg" => Self::SuccessBg,
252 "errorBg" => Self::ErrorBg,
253 "warningBg" => Self::WarningBg,
254 "infoBg" => Self::InfoBg,
255 "accentBg" => Self::AccentBg,
256 "diffAdded" => Self::DiffAdded,
257 "diffRemoved" => Self::DiffRemoved,
258 "diffContext" => Self::DiffContext,
259 _ => return None,
260 })
261 }
262
263 #[must_use]
265 pub fn is_unknown(&self) -> bool {
266 matches!(self, Self::Other(_))
267 }
268}
269
270impl Serialize for Color {
271 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
272 if let Self::Other(name) = self {
273 return serializer.serialize_str(name);
274 }
275 serializer.serialize_str(self.as_str().unwrap_or_default())
276 }
277}
278
279impl<'de> Deserialize<'de> for Color {
280 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
281 let name = String::deserialize(deserializer)?;
282 Ok(Self::parse(&name))
283 }
284}
285
286#[allow(
288 non_camel_case_types,
289 reason = "wire names are ratatui's constant names (§4.2)"
290)]
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
292pub enum Modifier {
293 BOLD,
295 DIM,
297 ITALIC,
299 UNDERLINED,
301 SLOW_BLINK,
303 RAPID_BLINK,
305 REVERSED,
307 HIDDEN,
309 CROSSED_OUT,
311}
312
313pub const ALL_MODIFIERS: [Modifier; 9] = [
315 Modifier::BOLD,
316 Modifier::DIM,
317 Modifier::ITALIC,
318 Modifier::UNDERLINED,
319 Modifier::SLOW_BLINK,
320 Modifier::RAPID_BLINK,
321 Modifier::REVERSED,
322 Modifier::HIDDEN,
323 Modifier::CROSSED_OUT,
324];
325
326#[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash)]
329pub struct Modifiers(u16);
330
331impl Modifiers {
332 #[must_use]
334 pub fn new() -> Self {
335 Self(0)
336 }
337
338 pub fn insert(&mut self, m: Modifier) {
340 self.0 |= 1 << (m as u16);
341 }
342
343 pub fn remove(&mut self, m: Modifier) {
345 self.0 &= !(1 << (m as u16));
346 }
347
348 #[must_use]
350 pub fn contains(&self, m: Modifier) -> bool {
351 self.0 & (1 << (m as u16)) != 0
352 }
353
354 #[must_use]
356 pub fn is_empty(&self) -> bool {
357 self.0 == 0
358 }
359
360 #[must_use]
362 pub fn union(mut self, other: Self) -> Self {
363 self.0 |= other.0;
364 self
365 }
366
367 pub fn iter(&self) -> impl Iterator<Item = Modifier> + '_ {
369 ALL_MODIFIERS.into_iter().filter(|m| self.contains(*m))
370 }
371
372 #[must_use]
374 pub fn from_iter_const<I: IntoIterator<Item = Modifier>>(mods: I) -> Self {
375 let mut set = Self::new();
376 for m in mods {
377 set.insert(m);
378 }
379 set
380 }
381}
382
383impl Serialize for Modifiers {
384 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
385 serializer.collect_seq(self.iter())
386 }
387}
388
389impl<'de> Deserialize<'de> for Modifiers {
390 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
391 let mods = Vec::<Modifier>::deserialize(deserializer)?;
392 Ok(Self::from_iter_const(mods))
393 }
394}
395
396#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
400#[serde(rename_all = "camelCase", default)]
401pub struct Style {
402 #[serde(skip_serializing_if = "Option::is_none")]
404 pub fg: Option<Color>,
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub bg: Option<Color>,
408 #[serde(skip_serializing_if = "Option::is_none")]
410 pub underline_color: Option<Color>,
411 #[serde(skip_serializing_if = "Modifiers::is_empty")]
413 pub add_modifier: Modifiers,
414 #[serde(skip_serializing_if = "Modifiers::is_empty")]
416 pub sub_modifier: Modifiers,
417}
418
419impl Style {
420 #[must_use]
422 pub fn new() -> Self {
423 Self {
424 fg: None,
425 bg: None,
426 underline_color: None,
427 add_modifier: Modifiers::new(),
428 sub_modifier: Modifiers::new(),
429 }
430 }
431
432 #[must_use]
434 pub fn fg(mut self, color: Color) -> Self {
435 self.fg = Some(color);
436 self
437 }
438
439 #[must_use]
441 pub fn bg(mut self, color: Color) -> Self {
442 self.bg = Some(color);
443 self
444 }
445
446 #[must_use]
448 pub fn underline_color(mut self, color: Color) -> Self {
449 self.underline_color = Some(color);
450 self
451 }
452
453 #[must_use]
455 pub fn add_modifier(mut self, m: impl IntoIterator<Item = Modifier>) -> Self {
456 self.add_modifier = self.add_modifier.union(Modifiers::from_iter_const(m));
457 self
458 }
459
460 #[must_use]
462 pub fn sub_modifier(mut self, m: impl IntoIterator<Item = Modifier>) -> Self {
463 self.sub_modifier = self.sub_modifier.union(Modifiers::from_iter_const(m));
464 self
465 }
466
467 #[must_use]
469 pub fn patch(mut self, other: Self) -> Self {
470 if other.fg.is_some() {
471 self.fg = other.fg;
472 }
473 if other.bg.is_some() {
474 self.bg = other.bg;
475 }
476 if other.underline_color.is_some() {
477 self.underline_color = other.underline_color;
478 }
479 self.add_modifier = self.add_modifier.union(other.add_modifier);
480 self.sub_modifier = self.sub_modifier.union(other.sub_modifier);
481 self
482 }
483
484 #[must_use]
486 pub fn is_empty(&self) -> bool {
487 self.fg.is_none()
488 && self.bg.is_none()
489 && self.underline_color.is_none()
490 && self.add_modifier.is_empty()
491 && self.sub_modifier.is_empty()
492 }
493}
494
495pub trait Stylize<T>: Sized {
504 fn style(self, style: Style) -> T;
506
507 fn fg(self, color: Color) -> T {
509 self.style(Style::new().fg(color))
510 }
511
512 fn bg(self, color: Color) -> T {
514 self.style(Style::new().bg(color))
515 }
516
517 fn underline_color(self, color: Color) -> T {
519 self.style(Style::new().underline_color(color))
520 }
521
522 fn bold(self) -> T {
524 self.style(Style::new().add_modifier([Modifier::BOLD]))
525 }
526
527 fn dim(self) -> T {
529 self.style(Style::new().add_modifier([Modifier::DIM]))
530 }
531
532 fn italic(self) -> T {
534 self.style(Style::new().add_modifier([Modifier::ITALIC]))
535 }
536
537 fn underlined(self) -> T {
539 self.style(Style::new().add_modifier([Modifier::UNDERLINED]))
540 }
541
542 fn slow_blink(self) -> T {
544 self.style(Style::new().add_modifier([Modifier::SLOW_BLINK]))
545 }
546
547 fn rapid_blink(self) -> T {
549 self.style(Style::new().add_modifier([Modifier::RAPID_BLINK]))
550 }
551
552 fn reversed(self) -> T {
554 self.style(Style::new().add_modifier([Modifier::REVERSED]))
555 }
556
557 fn hidden(self) -> T {
559 self.style(Style::new().add_modifier([Modifier::HIDDEN]))
560 }
561
562 fn crossed_out(self) -> T {
564 self.style(Style::new().add_modifier([Modifier::CROSSED_OUT]))
565 }
566}