Skip to main content

owo_colors/
dyn_styles.rs

1use crate::{AnsiColors, Color, DynColor, DynColors};
2use core::fmt;
3
4#[cfg(doc)]
5use crate::OwoColorize;
6
7/// A runtime-configurable text effect for use with [`Style`]
8#[allow(missing_docs)]
9#[derive(Debug, Copy, Clone)]
10pub enum Effect {
11    Bold,
12    Dimmed,
13    Italic,
14    Underline,
15    Blink,
16    BlinkFast,
17    Reversed,
18    Hidden,
19    Strikethrough,
20}
21
22macro_rules! color_methods {
23    ($(
24        #[$fg_meta:meta] #[$bg_meta:meta] $color:ident $fg_method:ident $bg_method:ident
25    ),* $(,)?) => {
26        $(
27            #[$fg_meta]
28            #[must_use]
29            pub const fn $fg_method(mut self) -> Self {
30                self.fg = Some(DynColors::Ansi(AnsiColors::$color));
31                self
32            }
33
34            #[$fg_meta]
35            #[must_use]
36            pub const fn $bg_method(mut self) -> Self {
37                self.bg = Some(DynColors::Ansi(AnsiColors::$color));
38                self
39            }
40         )*
41    };
42}
43
44macro_rules! style_methods {
45    ($(#[$meta:meta] ($name:ident, $set_name:ident)),* $(,)?) => {
46        $(
47            #[$meta]
48            #[must_use]
49            pub const fn $name(mut self) -> Self {
50                self.style_flags.$set_name(true);
51                self
52            }
53        )*
54    };
55}
56
57const _: () = (); // workaround for syntax highlighting bug
58
59/// A wrapper type which applies a [`Style`] when displaying the inner type
60pub struct Styled<T> {
61    /// The target value to be styled
62    pub(crate) target: T,
63    /// The style to apply to target
64    pub style: Style,
65}
66
67/// A pre-computed style that can be applied to a struct using [`OwoColorize::style`].
68///
69/// Its interface mimics that of [`OwoColorize`], but instead of chaining methods on your
70/// object, you instead chain them on the `Style` object before applying it.
71///
72/// ```rust
73/// use owo_colors::{OwoColorize, Style};
74///
75/// let my_style = Style::new()
76///     .red()
77///     .on_white()
78///     .strikethrough();
79///
80/// println!("{}", "red text, white background, struck through".style(my_style));
81/// ```
82#[derive(Debug, Copy, Clone, PartialEq)]
83pub struct Style {
84    pub(crate) fg: Option<DynColors>,
85    pub(crate) bg: Option<DynColors>,
86    pub(crate) bold: bool,
87    pub(crate) style_flags: StyleFlags,
88}
89
90#[repr(transparent)]
91#[derive(Debug, Copy, Clone, PartialEq)]
92pub(crate) struct StyleFlags(pub(crate) u8);
93
94impl StyleFlags {
95    #[must_use]
96    #[inline]
97    const fn is_plain(&self) -> bool {
98        self.0 == 0
99    }
100}
101
102const DIMMED_SHIFT: u8 = 0;
103const ITALIC_SHIFT: u8 = 1;
104const UNDERLINE_SHIFT: u8 = 2;
105const BLINK_SHIFT: u8 = 3;
106const BLINK_FAST_SHIFT: u8 = 4;
107const REVERSED_SHIFT: u8 = 5;
108const HIDDEN_SHIFT: u8 = 6;
109const STRIKETHROUGH_SHIFT: u8 = 7;
110
111macro_rules! style_flags_methods {
112    ($(($shift:ident, $name:ident, $set_name:ident)),* $(,)?) => {
113        $(
114            #[must_use]
115            const fn $name(&self) -> bool {
116                ((self.0 >> $shift) & 1) != 0
117            }
118
119            const fn $set_name(&mut self, $name: bool) {
120                self.0 = (self.0 & !(1 << $shift)) | (($name as u8) << $shift);
121            }
122        )*
123    };
124}
125
126impl StyleFlags {
127    const fn new() -> Self {
128        Self(0)
129    }
130
131    style_flags_methods! {
132        (DIMMED_SHIFT, dimmed, set_dimmed),
133        (ITALIC_SHIFT, italic, set_italic),
134        (UNDERLINE_SHIFT, underline, set_underline),
135        (BLINK_SHIFT, blink, set_blink),
136        (BLINK_FAST_SHIFT, blink_fast, set_blink_fast),
137        (REVERSED_SHIFT, reversed, set_reversed),
138        (HIDDEN_SHIFT, hidden, set_hidden),
139        (STRIKETHROUGH_SHIFT, strikethrough, set_strikethrough),
140    }
141}
142
143impl Default for StyleFlags {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149impl Style {
150    /// Create a new style to be applied later
151    #[must_use]
152    pub const fn new() -> Self {
153        Self {
154            fg: None,
155            bg: None,
156            bold: false,
157            style_flags: StyleFlags::new(),
158        }
159    }
160
161    /// Apply the style to a given struct to output.
162    ///
163    /// # Example
164    ///
165    /// Usage in const contexts:
166    ///
167    /// ```rust
168    /// use owo_colors::{OwoColorize, Style, Styled};
169    ///
170    /// const STYLED_TEXT: Styled<&'static str> = Style::new().bold().italic().style("bold and italic text");
171    ///
172    /// println!("{}", STYLED_TEXT);
173    /// # assert_eq!(format!("{}", STYLED_TEXT), "\u{1b}[1;3mbold and italic text\u{1b}[0m");
174    /// ```
175    pub const fn style<T>(&self, target: T) -> Styled<T> {
176        Styled {
177            target,
178            style: *self,
179        }
180    }
181
182    /// Set the foreground color generically
183    ///
184    /// ```rust
185    /// use owo_colors::{OwoColorize, colors::*};
186    ///
187    /// println!("{}", "red foreground".fg::<Red>());
188    /// ```
189    #[must_use]
190    pub const fn fg<C: Color>(mut self) -> Self {
191        self.fg = Some(C::DYN_COLORS_EQUIVALENT);
192        self
193    }
194
195    /// Set the background color generically.
196    ///
197    /// ```rust
198    /// use owo_colors::{OwoColorize, colors::*};
199    ///
200    /// println!("{}", "black background".bg::<Black>());
201    /// ```
202    #[must_use]
203    pub const fn bg<C: Color>(mut self) -> Self {
204        self.bg = Some(C::DYN_COLORS_EQUIVALENT);
205        self
206    }
207
208    /// Removes the foreground color from the style. Note that this does not apply
209    /// the default color, but rather represents not changing the current terminal color.
210    ///
211    /// If you wish to actively change the terminal color back to the default, see
212    /// [`Style::default_color`].
213    #[must_use]
214    pub const fn remove_fg(mut self) -> Self {
215        self.fg = None;
216        self
217    }
218
219    /// Removes the background color from the style. Note that this does not apply
220    /// the default color, but rather represents not changing the current terminal color.
221    ///
222    /// If you wish to actively change the terminal color back to the default, see
223    /// [`Style::on_default_color`].
224    #[must_use]
225    pub const fn remove_bg(mut self) -> Self {
226        self.bg = None;
227        self
228    }
229
230    color_methods! {
231        /// Change the foreground color to black
232        /// Change the background color to black
233        Black    black    on_black,
234        /// Change the foreground color to red
235        /// Change the background color to red
236        Red      red      on_red,
237        /// Change the foreground color to green
238        /// Change the background color to green
239        Green    green    on_green,
240        /// Change the foreground color to yellow
241        /// Change the background color to yellow
242        Yellow   yellow   on_yellow,
243        /// Change the foreground color to blue
244        /// Change the background color to blue
245        Blue     blue     on_blue,
246        /// Change the foreground color to magenta
247        /// Change the background color to magenta
248        Magenta  magenta  on_magenta,
249        /// Change the foreground color to purple
250        /// Change the background color to purple
251        Magenta  purple   on_purple,
252        /// Change the foreground color to cyan
253        /// Change the background color to cyan
254        Cyan     cyan     on_cyan,
255        /// Change the foreground color to white
256        /// Change the background color to white
257        White    white    on_white,
258
259        /// Change the foreground color to the terminal default
260        /// Change the background color to the terminal default
261        Default default_color on_default_color,
262
263        /// Change the foreground color to bright black
264        /// Change the background color to bright black
265        BrightBlack    bright_black    on_bright_black,
266        /// Change the foreground color to bright red
267        /// Change the background color to bright red
268        BrightRed      bright_red      on_bright_red,
269        /// Change the foreground color to bright green
270        /// Change the background color to bright green
271        BrightGreen    bright_green    on_bright_green,
272        /// Change the foreground color to bright yellow
273        /// Change the background color to bright yellow
274        BrightYellow   bright_yellow   on_bright_yellow,
275        /// Change the foreground color to bright blue
276        /// Change the background color to bright blue
277        BrightBlue     bright_blue     on_bright_blue,
278        /// Change the foreground color to bright magenta
279        /// Change the background color to bright magenta
280        BrightMagenta  bright_magenta  on_bright_magenta,
281        /// Change the foreground color to bright purple
282        /// Change the background color to bright purple
283        BrightMagenta  bright_purple   on_bright_purple,
284        /// Change the foreground color to bright cyan
285        /// Change the background color to bright cyan
286        BrightCyan     bright_cyan     on_bright_cyan,
287        /// Change the foreground color to bright white
288        /// Change the background color to bright white
289        BrightWhite    bright_white    on_bright_white,
290    }
291
292    /// Make the text bold
293    #[must_use]
294    pub const fn bold(mut self) -> Self {
295        self.bold = true;
296        self
297    }
298
299    style_methods! {
300        /// Make the text dim
301        (dimmed, set_dimmed),
302        /// Make the text italicized
303        (italic, set_italic),
304        /// Make the text underlined
305        (underline, set_underline),
306        /// Make the text blink
307        (blink, set_blink),
308        /// Make the text blink (but fast!)
309        (blink_fast, set_blink_fast),
310        /// Swap the foreground and background colors
311        (reversed, set_reversed),
312        /// Hide the text
313        (hidden, set_hidden),
314        /// Cross out the text
315        (strikethrough, set_strikethrough),
316    }
317
318    #[must_use]
319    const fn set_effect(mut self, effect: Effect, to: bool) -> Self {
320        use Effect::*;
321        match effect {
322            Bold => {
323                self.bold = to;
324            }
325            Dimmed => {
326                self.style_flags.set_dimmed(to);
327            }
328            Italic => {
329                self.style_flags.set_italic(to);
330            }
331            Underline => {
332                self.style_flags.set_underline(to);
333            }
334            Blink => {
335                self.style_flags.set_blink(to);
336            }
337            BlinkFast => {
338                self.style_flags.set_blink_fast(to);
339            }
340            Reversed => {
341                self.style_flags.set_reversed(to);
342            }
343            Hidden => {
344                self.style_flags.set_hidden(to);
345            }
346            Strikethrough => {
347                self.style_flags.set_strikethrough(to);
348            }
349        }
350        self
351    }
352
353    #[must_use]
354    const fn set_effects(mut self, mut effects: &[Effect], to: bool) -> Self {
355        // This is basically a for loop that also works in const contexts.
356        while let [first, rest @ ..] = effects {
357            self = self.set_effect(*first, to);
358            effects = rest;
359        }
360        self
361    }
362
363    /// Apply a given effect from the style
364    #[must_use]
365    pub const fn effect(self, effect: Effect) -> Self {
366        self.set_effect(effect, true)
367    }
368
369    /// Remove a given effect from the style
370    #[must_use]
371    pub const fn remove_effect(self, effect: Effect) -> Self {
372        self.set_effect(effect, false)
373    }
374
375    /// Apply a given set of effects to the style
376    #[must_use]
377    pub const fn effects(self, effects: &[Effect]) -> Self {
378        self.set_effects(effects, true)
379    }
380
381    /// Remove a given set of effects from the style
382    #[must_use]
383    pub const fn remove_effects(self, effects: &[Effect]) -> Self {
384        self.set_effects(effects, false)
385    }
386
387    /// Disables all the given effects from the style
388    #[must_use]
389    pub const fn remove_all_effects(mut self) -> Self {
390        self.bold = false;
391        self.style_flags = StyleFlags::new();
392        self
393    }
394
395    /// Set the foreground color at runtime. Only use if you do not know which color will be used at
396    /// compile-time. If the color is constant, use either [`OwoColorize::fg`](crate::OwoColorize::fg) or
397    /// a color-specific method, such as [`OwoColorize::green`](crate::OwoColorize::green),
398    ///
399    /// ```rust
400    /// use owo_colors::{OwoColorize, AnsiColors};
401    ///
402    /// println!("{}", "green".color(AnsiColors::Green));
403    /// ```
404    #[must_use]
405    pub fn color<Color: DynColor>(mut self, color: Color) -> Self {
406        // Can't be const because `get_dyncolors_fg` is a trait method.
407        self.fg = Some(color.get_dyncolors_fg());
408        self
409    }
410
411    /// Set the background color at runtime. Only use if you do not know what color to use at
412    /// compile-time. If the color is constant, use either [`OwoColorize::bg`](crate::OwoColorize::bg) or
413    /// a color-specific method, such as [`OwoColorize::on_yellow`](crate::OwoColorize::on_yellow),
414    ///
415    /// ```rust
416    /// use owo_colors::{OwoColorize, AnsiColors};
417    ///
418    /// println!("{}", "yellow background".on_color(AnsiColors::BrightYellow));
419    /// ```
420    #[must_use]
421    pub fn on_color<Color: DynColor>(mut self, color: Color) -> Self {
422        // Can't be const because `get_dyncolors_bg` is a trait method.
423        self.bg = Some(color.get_dyncolors_bg());
424        self
425    }
426
427    /// Set the foreground color to a specific RGB value.
428    #[must_use]
429    pub const fn fg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
430        self.fg = Some(DynColors::Rgb(R, G, B));
431
432        self
433    }
434
435    /// Set the background color to a specific RGB value.
436    #[must_use]
437    pub const fn bg_rgb<const R: u8, const G: u8, const B: u8>(mut self) -> Self {
438        self.bg = Some(DynColors::Rgb(R, G, B));
439
440        self
441    }
442
443    /// Sets the foreground color to an RGB value.
444    #[must_use]
445    pub const fn truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
446        self.fg = Some(DynColors::Rgb(r, g, b));
447        self
448    }
449
450    /// Sets the background color to an RGB value.
451    #[must_use]
452    pub const fn on_truecolor(mut self, r: u8, g: u8, b: u8) -> Self {
453        self.bg = Some(DynColors::Rgb(r, g, b));
454        self
455    }
456
457    /// Returns true if the style does not apply any formatting.
458    #[must_use]
459    #[inline]
460    pub const fn is_plain(&self) -> bool {
461        let s = &self;
462        !(s.fg.is_some() || s.bg.is_some() || s.bold) && s.style_flags.is_plain()
463    }
464
465    /// Returns a formatter for the style's ANSI prefix.
466    ///
467    /// This can be used to separate out the prefix and suffix of a style.
468    ///
469    /// # Example
470    ///
471    /// ```
472    /// use owo_colors::Style;
473    /// use std::fmt::Write;
474    ///
475    /// let style = Style::new().red().on_blue();
476    /// let prefix = style.prefix_formatter();
477    /// let suffix = style.suffix_formatter();
478    ///
479    /// // Write the prefix and suffix separately.
480    /// let mut output = String::new();
481    /// write!(output, "{}", prefix);
482    /// output.push_str("Hello");
483    /// write!(output, "{}", suffix);
484    ///
485    /// assert_eq!(output, "\x1b[31;44mHello\x1b[0m");
486    /// ```
487    pub const fn prefix_formatter(&self) -> StylePrefixFormatter {
488        StylePrefixFormatter(*self)
489    }
490
491    /// Returns a formatter for the style's ANSI suffix.
492    ///
493    /// This can be used to separate out the prefix and suffix of a style.
494    ///
495    /// # Example
496    ///
497    /// See [`Style::prefix_formatter`].
498    pub const fn suffix_formatter(&self) -> StyleSuffixFormatter {
499        StyleSuffixFormatter(*self)
500    }
501
502    /// Applies the ANSI-prefix for this style to the given formatter
503    #[inline]
504    #[allow(unused_assignments)]
505    pub fn fmt_prefix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
506        let s = self;
507        let format_less_important_effects = s.style_flags != StyleFlags::default();
508        let format_effect = s.bold || format_less_important_effects;
509        let format_any = !self.is_plain();
510
511        let mut semicolon = false;
512
513        if format_any {
514            f.write_str("\x1b[")?;
515        }
516
517        if let Some(fg) = s.fg {
518            <DynColors as DynColor>::fmt_raw_ansi_fg(&fg, f)?;
519            semicolon = true;
520        }
521
522        if let Some(bg) = s.bg {
523            if s.fg.is_some() {
524                f.write_str(";")?;
525            }
526            <DynColors as DynColor>::fmt_raw_ansi_bg(&bg, f)?;
527            semicolon = true;
528        }
529
530        if format_effect {
531            if s.bold {
532                if semicolon {
533                    f.write_str(";")?;
534                }
535
536                f.write_str("1")?;
537
538                semicolon = true;
539            }
540
541            macro_rules! text_effect_fmt {
542                ($style:ident, $formatter:ident, $semicolon:ident, $(($attr:ident, $value:literal)),* $(,)?) => {
543                    $(
544                        if $style.style_flags.$attr() {
545                            if $semicolon {
546                                $formatter.write_str(";")?;
547                            }
548                            $formatter.write_str($value)?;
549
550                            $semicolon = true;
551                        }
552                    )+
553                }
554            }
555
556            if format_less_important_effects {
557                text_effect_fmt! {
558                    s, f, semicolon,
559                    (dimmed,        "2"),
560                    (italic,        "3"),
561                    (underline,     "4"),
562                    (blink,         "5"),
563                    (blink_fast,    "6"),
564                    (reversed,      "7"),
565                    (hidden,        "8"),
566                    (strikethrough, "9"),
567                }
568            }
569        }
570
571        if format_any {
572            f.write_str("m")?;
573        }
574        Ok(())
575    }
576
577    /// Applies the ANSI-suffix for this style to the given formatter
578    #[inline]
579    pub fn fmt_suffix(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580        if !self.is_plain() {
581            f.write_str("\x1b[0m")?;
582        }
583        Ok(())
584    }
585}
586
587/// Formatter for the prefix of a [`Style`].
588///
589/// This is used to get the ANSI escape codes for the style without
590/// the suffix, which is useful for formatting the prefix separately.
591#[derive(Debug, Clone, Copy, PartialEq)]
592#[must_use = "this formatter does nothing unless displayed"]
593pub struct StylePrefixFormatter(Style);
594
595impl fmt::Display for StylePrefixFormatter {
596    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597        self.0.fmt_prefix(f)
598    }
599}
600
601/// Formatter for the suffix of a [`Style`].
602///
603/// This is used to get the ANSI escape codes for the style without
604/// the prefix, which is useful for formatting the suffix separately.
605#[derive(Debug, Clone, Copy, PartialEq)]
606#[must_use = "this formatter does nothing unless displayed"]
607pub struct StyleSuffixFormatter(Style);
608
609impl fmt::Display for StyleSuffixFormatter {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        self.0.fmt_suffix(f)
612    }
613}
614
615impl Default for Style {
616    fn default() -> Self {
617        Self::new()
618    }
619}
620
621/// Helper to create [`Style`]s more ergonomically
622pub const fn style() -> Style {
623    Style::new()
624}
625
626impl<T> Styled<T> {
627    /// Returns a reference to the inner value to be styled
628    pub const fn inner(&self) -> &T {
629        &self.target
630    }
631
632    /// Returns a mutable reference to the inner value to be styled.
633    pub const fn inner_mut(&mut self) -> &mut T {
634        &mut self.target
635    }
636}
637
638macro_rules! impl_fmt {
639    ($($trait:path),* $(,)?) => {
640        $(
641            impl<T: $trait> $trait for Styled<T> {
642                #[allow(unused_assignments)]
643                fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
644                    self.style.fmt_prefix(f)?;
645                    <T as $trait>::fmt(&self.target, f)?;
646                    self.style.fmt_suffix(f)
647                }
648            }
649        )*
650    };
651}
652
653impl_fmt! {
654    fmt::Display,
655    fmt::Debug,
656    fmt::UpperHex,
657    fmt::LowerHex,
658    fmt::Binary,
659    fmt::UpperExp,
660    fmt::LowerExp,
661    fmt::Octal,
662    fmt::Pointer,
663}
664
665#[cfg(test)]
666mod tests {
667    use super::*;
668    use crate::{AnsiColors, OwoColorize};
669
670    #[test]
671    fn size_of() {
672        let size = std::mem::size_of::<Style>();
673        assert_eq!(size, 10, "size of Style should be 10 bytes");
674    }
675
676    #[test]
677    fn test_it() {
678        let style = Style::new()
679            .bright_white()
680            .on_blue()
681            .bold()
682            .dimmed()
683            .italic()
684            .underline()
685            .blink()
686            //.blink_fast()
687            //.reversed()
688            //.hidden()
689            .strikethrough();
690        let s = style.style("TEST");
691        let s2 = format!("{}", &s);
692        println!("{}", &s2);
693        assert_eq!(&s2, "\u{1b}[97;44;1;2;3;4;5;9mTEST\u{1b}[0m");
694
695        let prefix = format!("{}", style.prefix_formatter());
696        assert_eq!(&prefix, "\u{1b}[97;44;1;2;3;4;5;9m");
697
698        let suffix = format!("{}", style.suffix_formatter());
699        assert_eq!(&suffix, "\u{1b}[0m");
700    }
701
702    #[test]
703    fn test_effects() {
704        use Effect::*;
705        let style = Style::new().effects(&[Strikethrough, Underline]);
706
707        let s = style.style("TEST");
708        let s2 = format!("{}", &s);
709        println!("{}", &s2);
710        assert_eq!(&s2, "\u{1b}[4;9mTEST\u{1b}[0m");
711    }
712
713    #[test]
714    fn test_color() {
715        let style = Style::new()
716            .color(AnsiColors::White)
717            .on_color(AnsiColors::Black);
718
719        let s = style.style("TEST");
720        let s2 = format!("{}", &s);
721        println!("{}", &s2);
722        assert_eq!(&s2, "\u{1b}[37;40mTEST\u{1b}[0m");
723    }
724
725    #[test]
726    fn test_truecolor() {
727        let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
728
729        let s = style.style("TEST");
730        let s2 = format!("{}", &s);
731        println!("{}", &s2);
732        assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
733    }
734
735    #[test]
736    fn test_string_reference() {
737        let style = Style::new().truecolor(255, 255, 255).on_truecolor(0, 0, 0);
738
739        let string = String::from("TEST");
740        let s = style.style(&string);
741        let s2 = format!("{}", &s);
742        println!("{}", &s2);
743        assert_eq!(&s2, "\u{1b}[38;2;255;255;255;48;2;0;0;0mTEST\u{1b}[0m");
744    }
745
746    #[test]
747    fn test_owocolorize() {
748        let style = Style::new().bright_white().on_blue();
749
750        let s = "TEST".style(style);
751        let s2 = format!("{}", &s);
752        println!("{}", &s2);
753        assert_eq!(&s2, "\u{1b}[97;44mTEST\u{1b}[0m");
754    }
755
756    #[test]
757    fn test_is_plain() {
758        let style = Style::new().bright_white().on_blue();
759
760        assert!(!style.is_plain());
761        assert!(Style::default().is_plain());
762
763        let string = String::from("TEST");
764        let s = Style::default().style(&string);
765        let s2 = format!("{}", &s);
766
767        assert_eq!(string, s2)
768    }
769
770    #[test]
771    fn test_inner() {
772        let style = Style::default();
773
774        let mut s = "TEST".style(style);
775
776        assert_eq!(&&"TEST", s.inner());
777
778        *s.inner_mut() = &"changed";
779        assert_eq!(&&"changed", s.inner());
780        assert_eq!("changed", format!("{}", s));
781    }
782}