1use alloc::borrow::Cow;
2use alloc::string::{String, ToString};
3use core::fmt;
4
5use crate::style::{Color, Modifier, Style};
6use crate::text::Span;
7
8pub trait Styled {
14 type Item;
15
16 fn style(&self) -> Style;
18
19 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item;
24}
25
26pub(crate) struct ColorDebug {
28 pub kind: ColorDebugKind,
29 pub color: Color,
30}
31
32#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
33pub(crate) enum ColorDebugKind {
34 Foreground,
35 Background,
36 #[cfg(feature = "underline-color")]
37 Underline,
38}
39
40impl fmt::Debug for ColorDebug {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 #[cfg(feature = "underline-color")]
43 let is_underline = self.kind == ColorDebugKind::Underline;
44 #[cfg(not(feature = "underline-color"))]
45 let is_underline = false;
46 if is_underline
47 || matches!(
48 self.color,
49 Color::Reset | Color::Indexed(_) | Color::Rgb(_, _, _)
50 )
51 {
52 match self.kind {
53 ColorDebugKind::Foreground => write!(f, ".fg(")?,
54 ColorDebugKind::Background => write!(f, ".bg(")?,
55 #[cfg(feature = "underline-color")]
56 ColorDebugKind::Underline => write!(f, ".underline_color(")?,
57 }
58 write!(f, "Color::{:?}", self.color)?;
59 write!(f, ")")?;
60 return Ok(());
61 }
62
63 match self.kind {
64 ColorDebugKind::Foreground => write!(f, ".")?,
65 ColorDebugKind::Background => write!(f, ".on_")?,
66 #[cfg(feature = "underline-color")]
68 ColorDebugKind::Underline => {
69 unreachable!("covered by the first part of the if statement")
70 }
71 }
72 match self.color {
73 Color::Black => write!(f, "black")?,
74 Color::Red => write!(f, "red")?,
75 Color::Green => write!(f, "green")?,
76 Color::Yellow => write!(f, "yellow")?,
77 Color::Blue => write!(f, "blue")?,
78 Color::Magenta => write!(f, "magenta")?,
79 Color::Cyan => write!(f, "cyan")?,
80 Color::Gray => write!(f, "gray")?,
81 Color::DarkGray => write!(f, "dark_gray")?,
82 Color::LightRed => write!(f, "light_red")?,
83 Color::LightGreen => write!(f, "light_green")?,
84 Color::LightYellow => write!(f, "light_yellow")?,
85 Color::LightBlue => write!(f, "light_blue")?,
86 Color::LightMagenta => write!(f, "light_magenta")?,
87 Color::LightCyan => write!(f, "light_cyan")?,
88 Color::White => write!(f, "white")?,
89 _ => unreachable!("covered by the first part of the if statement"),
90 }
91 write!(f, "()")
92 }
93}
94
95macro_rules! color {
115 ( $variant:expr, $color:ident(), $on_color:ident() -> $ty:ty ) => {
116 #[doc = concat!("Sets the foreground color to [`", stringify!($color), "`](", stringify!($variant), ").")]
117 #[must_use = concat!("`", stringify!($color), "` returns the modified style without modifying the original")]
118 fn $color(self) -> $ty {
119 self.fg($variant)
120 }
121
122 #[doc = concat!("Sets the background color to [`", stringify!($color), "`](", stringify!($variant), ").")]
123 #[must_use = concat!("`", stringify!($on_color), "` returns the modified style without modifying the original")]
124 fn $on_color(self) -> $ty {
125 self.bg($variant)
126 }
127 };
128
129 (pub const $variant:expr, $color:ident(), $on_color:ident() -> $ty:ty ) => {
130 #[doc = concat!("Sets the foreground color to [`", stringify!($color), "`](", stringify!($variant), ").")]
131 #[must_use = concat!("`", stringify!($color), "` returns the modified style without modifying the original")]
132 pub const fn $color(self) -> $ty {
133 self.fg($variant)
134 }
135
136 #[doc = concat!("Sets the background color to [`", stringify!($color), "`](", stringify!($variant), ").")]
137 #[must_use = concat!("`", stringify!($on_color), "` returns the modified style without modifying the original")]
138 pub const fn $on_color(self) -> $ty {
139 self.bg($variant)
140 }
141 };
142}
143
144macro_rules! modifier {
165 ( $variant:expr, $modifier:ident(), $not_modifier:ident() -> $ty:ty ) => {
166 #[doc = concat!("Adds the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
167 #[must_use = concat!("`", stringify!($modifier), "` returns the modified style without modifying the original")]
168 fn $modifier(self) -> $ty {
169 self.add_modifier($variant)
170 }
171
172 #[doc = concat!("Removes the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
173 #[must_use = concat!("`", stringify!($not_modifier), "` returns the modified style without modifying the original")]
174 fn $not_modifier(self) -> $ty {
175 self.remove_modifier($variant)
176 }
177 };
178
179 (pub const $variant:expr, $modifier:ident(), $not_modifier:ident() -> $ty:ty ) => {
180 #[doc = concat!("Adds the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
181 #[must_use = concat!("`", stringify!($modifier), "` returns the modified style without modifying the original")]
182 pub const fn $modifier(self) -> $ty {
183 self.add_modifier($variant)
184 }
185
186 #[doc = concat!("Removes the [`", stringify!($modifier), "`](", stringify!($variant), ") modifier.")]
187 #[must_use = concat!("`", stringify!($not_modifier), "` returns the modified style without modifying the original")]
188 pub const fn $not_modifier(self) -> $ty {
189 self.remove_modifier($variant)
190 }
191 };
192}
193
194pub trait Stylize<'a, T>: Sized {
234 #[must_use = "`bg` returns the modified style without modifying the original"]
235 fn bg<C: Into<Color>>(self, color: C) -> T;
236 #[must_use = "`fg` returns the modified style without modifying the original"]
237 fn fg<C: Into<Color>>(self, color: C) -> T;
238 #[must_use = "`reset` returns the modified style without modifying the original"]
239 fn reset(self) -> T;
240 #[must_use = "`add_modifier` returns the modified style without modifying the original"]
241 fn add_modifier(self, modifier: Modifier) -> T;
242 #[must_use = "`remove_modifier` returns the modified style without modifying the original"]
243 fn remove_modifier(self, modifier: Modifier) -> T;
244
245 color!(Color::Black, black(), on_black() -> T);
246 color!(Color::Red, red(), on_red() -> T);
247 color!(Color::Green, green(), on_green() -> T);
248 color!(Color::Yellow, yellow(), on_yellow() -> T);
249 color!(Color::Blue, blue(), on_blue() -> T);
250 color!(Color::Magenta, magenta(), on_magenta() -> T);
251 color!(Color::Cyan, cyan(), on_cyan() -> T);
252 color!(Color::Gray, gray(), on_gray() -> T);
253 color!(Color::DarkGray, dark_gray(), on_dark_gray() -> T);
254 color!(Color::LightRed, light_red(), on_light_red() -> T);
255 color!(Color::LightGreen, light_green(), on_light_green() -> T);
256 color!(Color::LightYellow, light_yellow(), on_light_yellow() -> T);
257 color!(Color::LightBlue, light_blue(), on_light_blue() -> T);
258 color!(Color::LightMagenta, light_magenta(), on_light_magenta() -> T);
259 color!(Color::LightCyan, light_cyan(), on_light_cyan() -> T);
260 color!(Color::White, white(), on_white() -> T);
261
262 modifier!(Modifier::BOLD, bold(), not_bold() -> T);
263 modifier!(Modifier::DIM, dim(), not_dim() -> T);
264 modifier!(Modifier::ITALIC, italic(), not_italic() -> T);
265 modifier!(Modifier::UNDERLINED, underlined(), not_underlined() -> T);
266 modifier!(Modifier::SLOW_BLINK, slow_blink(), not_slow_blink() -> T);
267 modifier!(Modifier::RAPID_BLINK, rapid_blink(), not_rapid_blink() -> T);
268 modifier!(Modifier::REVERSED, reversed(), not_reversed() -> T);
269 modifier!(Modifier::HIDDEN, hidden(), not_hidden() -> T);
270 modifier!(Modifier::CROSSED_OUT, crossed_out(), not_crossed_out() -> T);
271}
272
273impl<T, U> Stylize<'_, T> for U
274where
275 U: Styled<Item = T>,
276{
277 fn bg<C: Into<Color>>(self, color: C) -> T {
278 let style = self.style().bg(color.into());
279 self.set_style(style)
280 }
281
282 fn fg<C: Into<Color>>(self, color: C) -> T {
283 let style = self.style().fg(color.into());
284 self.set_style(style)
285 }
286
287 fn add_modifier(self, modifier: Modifier) -> T {
288 let style = self.style().add_modifier(modifier);
289 self.set_style(style)
290 }
291
292 fn remove_modifier(self, modifier: Modifier) -> T {
293 let style = self.style().remove_modifier(modifier);
294 self.set_style(style)
295 }
296
297 fn reset(self) -> T {
298 self.set_style(Style::reset())
299 }
300}
301
302impl<'a> Styled for &'a str {
303 type Item = Span<'a>;
304
305 fn style(&self) -> Style {
306 Style::default()
307 }
308
309 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
310 Span::styled(self, style)
311 }
312}
313
314impl<'a> Styled for Cow<'a, str> {
315 type Item = Span<'a>;
316
317 fn style(&self) -> Style {
318 Style::default()
319 }
320
321 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
322 Span::styled(self, style)
323 }
324}
325
326impl Styled for String {
327 type Item = Span<'static>;
328
329 fn style(&self) -> Style {
330 Style::default()
331 }
332
333 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
334 Span::styled(self, style)
335 }
336}
337
338macro_rules! styled {
339 ($impl_type:ty) => {
340 impl Styled for $impl_type {
341 type Item = Span<'static>;
342
343 fn style(&self) -> Style {
344 Style::default()
345 }
346
347 fn set_style<S: Into<Style>>(self, style: S) -> Self::Item {
348 Span::styled(self.to_string(), style)
349 }
350 }
351 };
352}
353
354styled!(bool);
355styled!(char);
356styled!(f32);
357styled!(f64);
358styled!(i8);
359styled!(i16);
360styled!(i32);
361styled!(i64);
362styled!(i128);
363styled!(isize);
364styled!(u8);
365styled!(u16);
366styled!(u32);
367styled!(u64);
368styled!(u128);
369styled!(usize);
370
371#[cfg(test)]
372mod tests {
373 use alloc::format;
374
375 use itertools::Itertools;
376 use rstest::rstest;
377
378 use super::*;
379
380 #[test]
381 fn str_styled() {
382 assert_eq!("hello".style(), Style::default());
383 assert_eq!(
384 "hello".set_style(Style::new().cyan()),
385 Span::styled("hello", Style::new().cyan())
386 );
387 assert_eq!("hello".black(), Span::from("hello").black());
388 assert_eq!("hello".red(), Span::from("hello").red());
389 assert_eq!("hello".green(), Span::from("hello").green());
390 assert_eq!("hello".yellow(), Span::from("hello").yellow());
391 assert_eq!("hello".blue(), Span::from("hello").blue());
392 assert_eq!("hello".magenta(), Span::from("hello").magenta());
393 assert_eq!("hello".cyan(), Span::from("hello").cyan());
394 assert_eq!("hello".gray(), Span::from("hello").gray());
395 assert_eq!("hello".dark_gray(), Span::from("hello").dark_gray());
396 assert_eq!("hello".light_red(), Span::from("hello").light_red());
397 assert_eq!("hello".light_green(), Span::from("hello").light_green());
398 assert_eq!("hello".light_yellow(), Span::from("hello").light_yellow());
399 assert_eq!("hello".light_blue(), Span::from("hello").light_blue());
400 assert_eq!("hello".light_magenta(), Span::from("hello").light_magenta());
401 assert_eq!("hello".light_cyan(), Span::from("hello").light_cyan());
402 assert_eq!("hello".white(), Span::from("hello").white());
403
404 assert_eq!("hello".on_black(), Span::from("hello").on_black());
405 assert_eq!("hello".on_red(), Span::from("hello").on_red());
406 assert_eq!("hello".on_green(), Span::from("hello").on_green());
407 assert_eq!("hello".on_yellow(), Span::from("hello").on_yellow());
408 assert_eq!("hello".on_blue(), Span::from("hello").on_blue());
409 assert_eq!("hello".on_magenta(), Span::from("hello").on_magenta());
410 assert_eq!("hello".on_cyan(), Span::from("hello").on_cyan());
411 assert_eq!("hello".on_gray(), Span::from("hello").on_gray());
412 assert_eq!("hello".on_dark_gray(), Span::from("hello").on_dark_gray());
413 assert_eq!("hello".on_light_red(), Span::from("hello").on_light_red());
414 assert_eq!(
415 "hello".on_light_green(),
416 Span::from("hello").on_light_green()
417 );
418 assert_eq!(
419 "hello".on_light_yellow(),
420 Span::from("hello").on_light_yellow()
421 );
422 assert_eq!("hello".on_light_blue(), Span::from("hello").on_light_blue());
423 assert_eq!(
424 "hello".on_light_magenta(),
425 Span::from("hello").on_light_magenta()
426 );
427 assert_eq!("hello".on_light_cyan(), Span::from("hello").on_light_cyan());
428 assert_eq!("hello".on_white(), Span::from("hello").on_white());
429
430 assert_eq!("hello".bold(), Span::from("hello").bold());
431 assert_eq!("hello".dim(), Span::from("hello").dim());
432 assert_eq!("hello".italic(), Span::from("hello").italic());
433 assert_eq!("hello".underlined(), Span::from("hello").underlined());
434 assert_eq!("hello".slow_blink(), Span::from("hello").slow_blink());
435 assert_eq!("hello".rapid_blink(), Span::from("hello").rapid_blink());
436 assert_eq!("hello".reversed(), Span::from("hello").reversed());
437 assert_eq!("hello".hidden(), Span::from("hello").hidden());
438 assert_eq!("hello".crossed_out(), Span::from("hello").crossed_out());
439
440 assert_eq!("hello".not_bold(), Span::from("hello").not_bold());
441 assert_eq!("hello".not_dim(), Span::from("hello").not_dim());
442 assert_eq!("hello".not_italic(), Span::from("hello").not_italic());
443 assert_eq!(
444 "hello".not_underlined(),
445 Span::from("hello").not_underlined()
446 );
447 assert_eq!(
448 "hello".not_slow_blink(),
449 Span::from("hello").not_slow_blink()
450 );
451 assert_eq!(
452 "hello".not_rapid_blink(),
453 Span::from("hello").not_rapid_blink()
454 );
455 assert_eq!("hello".not_reversed(), Span::from("hello").not_reversed());
456 assert_eq!("hello".not_hidden(), Span::from("hello").not_hidden());
457 assert_eq!(
458 "hello".not_crossed_out(),
459 Span::from("hello").not_crossed_out()
460 );
461
462 assert_eq!("hello".reset(), Span::from("hello").reset());
463 }
464
465 #[test]
466 fn string_styled() {
467 let s = String::from("hello");
468 assert_eq!(s.style(), Style::default());
469 assert_eq!(
470 s.clone().set_style(Style::new().cyan()),
471 Span::styled("hello", Style::new().cyan())
472 );
473 assert_eq!(s.clone().black(), Span::from("hello").black());
474 assert_eq!(s.clone().on_black(), Span::from("hello").on_black());
475 assert_eq!(s.clone().bold(), Span::from("hello").bold());
476 assert_eq!(s.clone().not_bold(), Span::from("hello").not_bold());
477 assert_eq!(s.clone().reset(), Span::from("hello").reset());
478 }
479
480 #[test]
481 fn cow_string_styled() {
482 let s = Cow::Borrowed("a");
483 assert_eq!(s.red(), "a".red());
484 }
485
486 #[test]
487 fn temporary_string_styled() {
488 let s = "hello".to_string().red();
492 assert_eq!(s, Span::from("hello").red());
493
494 let items = [String::from("a"), String::from("b")];
497 let sss = items.iter().map(|s| format!("{s}{s}").red()).collect_vec();
498 assert_eq!(sss, [Span::from("aa").red(), Span::from("bb").red()]);
499 }
500
501 #[test]
502 fn other_primitives_styled() {
503 assert_eq!(true.red(), "true".red());
504 assert_eq!('a'.red(), "a".red());
505 assert_eq!(0.1f32.red(), "0.1".red());
506 assert_eq!(0.1f64.red(), "0.1".red());
507 assert_eq!(0i8.red(), "0".red());
508 assert_eq!(0i16.red(), "0".red());
509 assert_eq!(0i32.red(), "0".red());
510 assert_eq!(0i64.red(), "0".red());
511 assert_eq!(0i128.red(), "0".red());
512 assert_eq!(0isize.red(), "0".red());
513 assert_eq!(0u8.red(), "0".red());
514 assert_eq!(0u16.red(), "0".red());
515 assert_eq!(0u32.red(), "0".red());
516 assert_eq!(0u64.red(), "0".red());
517 assert_eq!(0u64.red(), "0".red());
518 assert_eq!(0usize.red(), "0".red());
519 }
520
521 #[test]
522 fn reset() {
523 assert_eq!(
524 "hello".on_cyan().light_red().bold().underlined().reset(),
525 Span::styled("hello", Style::reset())
526 );
527 }
528
529 #[test]
530 fn fg() {
531 let cyan_fg = Style::default().fg(Color::Cyan);
532
533 assert_eq!("hello".cyan(), Span::styled("hello", cyan_fg));
534 }
535
536 #[test]
537 fn bg() {
538 let cyan_bg = Style::default().bg(Color::Cyan);
539
540 assert_eq!("hello".on_cyan(), Span::styled("hello", cyan_bg));
541 }
542
543 #[test]
544 fn color_modifier() {
545 let cyan_bold = Style::default()
546 .fg(Color::Cyan)
547 .add_modifier(Modifier::BOLD);
548
549 assert_eq!("hello".cyan().bold(), Span::styled("hello", cyan_bold));
550 }
551
552 #[test]
553 fn fg_bg() {
554 let cyan_fg_bg = Style::default().bg(Color::Cyan).fg(Color::Cyan);
555
556 assert_eq!("hello".cyan().on_cyan(), Span::styled("hello", cyan_fg_bg));
557 }
558
559 #[test]
560 fn repeated_attributes() {
561 let bg = Style::default().bg(Color::Cyan);
562 let fg = Style::default().fg(Color::Cyan);
563
564 assert_eq!("hello".on_red().on_cyan(), Span::styled("hello", bg));
566 assert_eq!("hello".red().cyan(), Span::styled("hello", fg));
567 }
568
569 #[test]
570 fn all_chained() {
571 let all_modifier_black = Style::default()
572 .bg(Color::Black)
573 .fg(Color::Black)
574 .add_modifier(
575 Modifier::UNDERLINED
576 | Modifier::BOLD
577 | Modifier::DIM
578 | Modifier::SLOW_BLINK
579 | Modifier::REVERSED
580 | Modifier::CROSSED_OUT,
581 );
582 assert_eq!(
583 "hello"
584 .on_black()
585 .black()
586 .bold()
587 .underlined()
588 .dim()
589 .slow_blink()
590 .crossed_out()
591 .reversed(),
592 Span::styled("hello", all_modifier_black)
593 );
594 }
595
596 #[rstest]
597 #[case(Color::Black, ".black()")]
598 #[case(Color::Red, ".red()")]
599 #[case(Color::Green, ".green()")]
600 #[case(Color::Yellow, ".yellow()")]
601 #[case(Color::Blue, ".blue()")]
602 #[case(Color::Magenta, ".magenta()")]
603 #[case(Color::Cyan, ".cyan()")]
604 #[case(Color::Gray, ".gray()")]
605 #[case(Color::DarkGray, ".dark_gray()")]
606 #[case(Color::LightRed, ".light_red()")]
607 #[case(Color::LightGreen, ".light_green()")]
608 #[case(Color::LightYellow, ".light_yellow()")]
609 #[case(Color::LightBlue, ".light_blue()")]
610 #[case(Color::LightMagenta, ".light_magenta()")]
611 #[case(Color::LightCyan, ".light_cyan()")]
612 #[case(Color::White, ".white()")]
613 #[case(Color::Indexed(10), ".fg(Color::Indexed(10))")]
614 #[case(Color::Rgb(255, 0, 0), ".fg(Color::Rgb(255, 0, 0))")]
615 fn stylize_debug_foreground(#[case] color: Color, #[case] expected: &str) {
616 let debug = color.stylize_debug(ColorDebugKind::Foreground);
617 assert_eq!(format!("{debug:?}"), expected);
618 }
619
620 #[rstest]
621 #[case(Color::Black, ".on_black()")]
622 #[case(Color::Red, ".on_red()")]
623 #[case(Color::Green, ".on_green()")]
624 #[case(Color::Yellow, ".on_yellow()")]
625 #[case(Color::Blue, ".on_blue()")]
626 #[case(Color::Magenta, ".on_magenta()")]
627 #[case(Color::Cyan, ".on_cyan()")]
628 #[case(Color::Gray, ".on_gray()")]
629 #[case(Color::DarkGray, ".on_dark_gray()")]
630 #[case(Color::LightRed, ".on_light_red()")]
631 #[case(Color::LightGreen, ".on_light_green()")]
632 #[case(Color::LightYellow, ".on_light_yellow()")]
633 #[case(Color::LightBlue, ".on_light_blue()")]
634 #[case(Color::LightMagenta, ".on_light_magenta()")]
635 #[case(Color::LightCyan, ".on_light_cyan()")]
636 #[case(Color::White, ".on_white()")]
637 #[case(Color::Indexed(10), ".bg(Color::Indexed(10))")]
638 #[case(Color::Rgb(255, 0, 0), ".bg(Color::Rgb(255, 0, 0))")]
639 fn stylize_debug_background(#[case] color: Color, #[case] expected: &str) {
640 let debug = color.stylize_debug(ColorDebugKind::Background);
641 assert_eq!(format!("{debug:?}"), expected);
642 }
643
644 #[cfg(feature = "underline-color")]
645 #[rstest]
646 #[case(Color::Black, ".underline_color(Color::Black)")]
647 #[case(Color::Red, ".underline_color(Color::Red)")]
648 #[case(Color::Green, ".underline_color(Color::Green)")]
649 #[case(Color::Yellow, ".underline_color(Color::Yellow)")]
650 #[case(Color::Blue, ".underline_color(Color::Blue)")]
651 #[case(Color::Magenta, ".underline_color(Color::Magenta)")]
652 #[case(Color::Cyan, ".underline_color(Color::Cyan)")]
653 #[case(Color::Gray, ".underline_color(Color::Gray)")]
654 #[case(Color::DarkGray, ".underline_color(Color::DarkGray)")]
655 #[case(Color::LightRed, ".underline_color(Color::LightRed)")]
656 #[case(Color::LightGreen, ".underline_color(Color::LightGreen)")]
657 #[case(Color::LightYellow, ".underline_color(Color::LightYellow)")]
658 #[case(Color::LightBlue, ".underline_color(Color::LightBlue)")]
659 #[case(Color::LightMagenta, ".underline_color(Color::LightMagenta)")]
660 #[case(Color::LightCyan, ".underline_color(Color::LightCyan)")]
661 #[case(Color::White, ".underline_color(Color::White)")]
662 #[case(Color::Indexed(10), ".underline_color(Color::Indexed(10))")]
663 #[case(Color::Rgb(255, 0, 0), ".underline_color(Color::Rgb(255, 0, 0))")]
664 fn stylize_debug_underline(#[case] color: Color, #[case] expected: &str) {
665 let debug = color.stylize_debug(ColorDebugKind::Underline);
666 assert_eq!(format!("{debug:?}"), expected);
667 }
668}