ratatui_core/style/
stylize.rs

1use alloc::borrow::Cow;
2use alloc::string::{String, ToString};
3use core::fmt;
4
5use crate::style::{Color, Modifier, Style};
6use crate::text::Span;
7
8/// A trait for objects that have a `Style`.
9///
10/// This trait enables generic code to be written that can interact with any object that has a
11/// `Style`. This is used by the `Stylize` trait to allow generic code to be written that can
12/// interact with any object that can be styled.
13pub trait Styled {
14    type Item;
15
16    /// Returns the style of the object.
17    fn style(&self) -> Style;
18
19    /// Sets the style of the object.
20    ///
21    /// `style` accepts any type that is convertible to [`Style`] (e.g. [`Style`], [`Color`], or
22    /// your own type that implements [`Into<Style>`]).
23    fn set_style<S: Into<Style>>(self, style: S) -> Self::Item;
24}
25
26/// A helper struct to make it easy to debug using the `Stylize` method names
27pub(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            // TODO: .underline_color_xxx is not implemented on Stylize yet, but it should be
67            #[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
95/// Generates two methods for each color, one for setting the foreground color (`red()`, `blue()`,
96/// etc) and one for setting the background color (`on_red()`, `on_blue()`, etc.). Each method sets
97/// the color of the style to the corresponding color.
98///
99/// ```rust,ignore
100/// color!(Color::Black, black(), on_black() -> T);
101///
102/// // generates
103///
104/// #[doc = "Sets the foreground color to [`black`](Color::Black)."]
105/// fn black(self) -> T {
106///     self.fg(Color::Black)
107/// }
108///
109/// #[doc = "Sets the background color to [`black`](Color::Black)."]
110/// fn on_black(self) -> T {
111///     self.bg(Color::Black)
112/// }
113/// ```
114macro_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
144/// Generates a method for a modifier (`bold()`, `italic()`, etc.). Each method sets the modifier
145/// of the style to the corresponding modifier.
146///
147/// # Examples
148///
149/// ```rust,ignore
150/// modifier!(Modifier::BOLD, bold(), not_bold() -> T);
151///
152/// // generates
153///
154/// #[doc = "Adds the [`bold`](Modifier::BOLD) modifier."]
155/// fn bold(self) -> T {
156///     self.add_modifier(Modifier::BOLD)
157/// }
158///
159/// #[doc = "Removes the [`bold`](Modifier::BOLD) modifier."]
160/// fn not_bold(self) -> T {
161///     self.remove_modifier(Modifier::BOLD)
162/// }
163/// ```
164macro_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
194/// An extension trait for styling objects.
195///
196/// For any type that implements `Stylize`, the provided methods in this trait can be used to style
197/// the type further. This trait is automatically implemented for any type that implements the
198/// [`Styled`] trait which e.g.: [`String`], [`&str`], [`Span`], [`Style`] and many Widget types.
199///
200/// This results in much more ergonomic styling of text and widgets. For example, instead of
201/// writing:
202///
203/// ```rust,ignore
204/// let text = Span::styled("Hello", Style::default().fg(Color::Red).bg(Color::Blue));
205/// ```
206///
207/// You can write:
208///
209/// ```rust,ignore
210/// let text = "Hello".red().on_blue();
211/// ```
212///
213/// This trait implements a provided method for every color as both foreground and background
214/// (prefixed by `on_`), and all modifiers as both an additive and subtractive modifier (prefixed
215/// by `not_`). The `reset()` method is also provided to reset the style.
216///
217/// # Examples
218/// ```ignore
219/// use ratatui_core::{
220///     style::{Color, Modifier, Style, Stylize},
221///     text::Line,
222///     widgets::{Block, Paragraph},
223/// };
224///
225/// let span = "hello".red().on_blue().bold();
226/// let line = Line::from(vec![
227///     "hello".red().on_blue().bold(),
228///     "world".green().on_yellow().not_bold(),
229/// ]);
230/// let paragraph = Paragraph::new(line).italic().underlined();
231/// let block = Block::bordered().title("Title").on_white().bold();
232/// ```
233pub 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        // to_string() is used to create a temporary String, which is then styled. Without the
489        // `Styled` trait impl for `String`, this would fail to compile with the error: "temporary
490        // value dropped while borrowed"
491        let s = "hello".to_string().red();
492        assert_eq!(s, Span::from("hello").red());
493
494        // format!() is used to create a temporary String inside a closure, which suffers the same
495        // issue as above without the `Styled` trait impl for `String`
496        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        // Behavior: the last one set is the definitive one
565        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}