diagnostic/style/
style.rs

1use crate::{Color, Paint};
2use std::{
3    fmt::{self, Display},
4    hash::{Hash, Hasher},
5    ops::BitOr,
6};
7
8#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
9pub struct Property(u8);
10
11impl Property {
12    pub const BOLD: Self = Property(1 << 0);
13    pub const DIMMED: Self = Property(1 << 1);
14    pub const ITALIC: Self = Property(1 << 2);
15    pub const UNDERLINE: Self = Property(1 << 3);
16    pub const BLINK: Self = Property(1 << 4);
17    pub const INVERT: Self = Property(1 << 5);
18    pub const HIDDEN: Self = Property(1 << 6);
19    pub const STRIKETHROUGH: Self = Property(1 << 7);
20
21    #[inline(always)]
22    pub fn contains(self, other: Property) -> bool {
23        (other.0 & self.0) == other.0
24    }
25
26    #[inline(always)]
27    pub fn set(&mut self, other: Property) {
28        self.0 |= other.0;
29    }
30
31    #[inline(always)]
32    pub fn iter(self) -> Iter {
33        Iter { index: 0, properties: self }
34    }
35}
36
37impl BitOr for Property {
38    type Output = Self;
39
40    #[inline(always)]
41    fn bitor(self, rhs: Self) -> Self {
42        Property(self.0 | rhs.0)
43    }
44}
45
46pub struct Iter {
47    index: u8,
48    properties: Property,
49}
50
51impl Iterator for Iter {
52    type Item = usize;
53
54    fn next(&mut self) -> Option<Self::Item> {
55        while self.index < 8 {
56            let index = self.index;
57            self.index += 1;
58
59            if self.properties.contains(Property(1 << index)) {
60                return Some(index as usize);
61            }
62        }
63
64        None
65    }
66}
67
68/// Represents a set of styling options.
69///
70/// See the [crate level documentation](./) for usage information.
71///
72/// # Method Glossary
73///
74/// The `Style` structure exposes many methods for convenience. The majority of
75/// these methods are shared with [`Paint`](Paint).
76///
77/// ### Foreground Color Constructors
78///
79/// Return a new `Style` structure with a foreground `color` applied.
80///
81///   * [`Style::new(color: Color)`](Style::new())
82///
83/// ### Setters
84///
85/// Set a style property on a given `Style` structure.
86///
87///   * [`style.fg(color: Color)`](Style::fg())
88///   * [`style.bg(color: Color)`](Style::bg())
89///   * [`style.mask()`](Style::mask())
90///   * [`style.wrap()`](Style::wrap())
91///   * [`style.bold()`](Style::bold())
92///   * [`style.dimmed()`](Style::dimmed())
93///   * [`style.italic()`](Style::italic())
94///   * [`style.underline()`](Style::underline())
95///   * [`style.blink()`](Style::blink())
96///   * [`style.invert()`](Style::invert())
97///   * [`style.hidden()`](Style::hidden())
98///   * [`style.strikethrough()`](Style::strikethrough())
99///
100/// These methods can be chained:
101///
102/// ```rust
103/// use diagnostic::{
104///     Color::{Magenta, Red},
105///     Style,
106/// };
107///
108/// Style::new(Red).bg(Magenta).underline().invert().italic().dimmed().bold();
109/// ```
110///
111/// ### Converters
112///
113/// Convert a `Style` into another structure.
114///
115///   * [`style.paint<T>(item: T) -> Paint<T>`](Style::paint())
116///
117/// ### Getters
118///
119/// Return information about a `Style` structure.
120///
121///   * [`style.fg_color()`](Style::fg_color())
122///   * [`style.bg_color()`](Style::bg_color())
123///   * [`style.is_masked()`](Style::is_masked())
124///   * [`style.is_wrapping()`](Style::is_wrapping())
125///   * [`style.is_bold()`](Style::is_bold())
126///   * [`style.is_dimmed()`](Style::is_dimmed())
127///   * [`style.is_italic()`](Style::is_italic())
128///   * [`style.is_underline()`](Style::is_underline())
129///   * [`style.is_blink()`](Style::is_blink())
130///   * [`style.is_invert()`](Style::is_invert())
131///   * [`style.is_hidden()`](Style::is_hidden())
132///   * [`style.is_strikethrough()`](Style::is_strikethrough())
133///
134/// ### Raw Formatters
135///
136/// Write the raw ANSI codes for a given `Style` to any `fmt::Write`.
137///
138///   * [`style.fmt_prefix(f: &mut fmt::Write)`](Style::fmt_prefix())
139///   * [`style.fmt_suffix(f: &mut fmt::Write)`](Style::fmt_suffix())
140#[repr(packed)]
141#[derive(Default, Debug, Eq, Ord, PartialOrd, Copy, Clone)]
142pub struct Style {
143    pub(crate) foreground: Color,
144    pub(crate) background: Color,
145    pub(crate) properties: Property,
146    pub(crate) masked: bool,
147    pub(crate) wrap: bool,
148}
149
150impl PartialEq for Style {
151    fn eq(&self, other: &Style) -> bool {
152        self.foreground == other.foreground && self.background == other.background && self.properties == other.properties
153    }
154}
155
156impl Hash for Style {
157    fn hash<H: Hasher>(&self, state: &mut H) {
158        self.foreground.hash(state);
159        self.background.hash(state);
160        self.properties.hash(state);
161    }
162}
163
164macro_rules! checker_for {
165    ($($name:ident ($fn_name:ident): $property:ident),*) => ($(
166        #[doc = concat!(
167            "Returns `true` if the _", stringify!($name), "_ property is set on `self`.\n",
168            "```rust\n",
169            "use diagnostic::Style;\n",
170            "\n",
171            "let plain = Style::default();\n",
172            "assert!(!plain.", stringify!($fn_name), "());\n",
173            "\n",
174            "let styled = plain.", stringify!($name), "();\n",
175            "assert!(styled.", stringify!($fn_name), "());\n",
176            "```\n"
177        )]
178        #[inline]
179        pub fn $fn_name(&self) -> bool {
180            self.properties.contains(Property::$property)
181        }
182    )*)
183}
184
185#[inline]
186fn write_spliced<T: Display>(c: &mut bool, f: &mut impl std::fmt::Write, t: T) -> fmt::Result {
187    if *c {
188        write!(f, ";{}", t)
189    }
190    else {
191        *c = true;
192        write!(f, "{}", t)
193    }
194}
195
196impl Style {
197    /// Default style with the foreground set to `color` and no other set
198    /// properties.
199    ///
200    /// ```rust
201    /// use diagnostic::Style;
202    ///
203    /// let plain = Style::default();
204    /// assert_eq!(plain, Style::default());
205    /// ```
206    #[inline]
207    pub fn new(color: Color) -> Style {
208        Self::default().fg(color)
209    }
210
211    /// Sets the foreground to `color`.
212    ///
213    /// ```rust
214    /// use diagnostic::{Color, Style};
215    ///
216    /// let red_fg = Style::default().fg(Color::Red);
217    /// ```
218    #[inline]
219    pub fn fg(mut self, color: Color) -> Style {
220        self.foreground = color;
221        self
222    }
223
224    /// Sets the background to `color`.
225    ///
226    /// ```rust
227    /// use diagnostic::{Color, Style};
228    ///
229    /// let red_bg = Style::default().bg(Color::Red);
230    /// ```
231    #[inline]
232    pub fn bg(mut self, color: Color) -> Style {
233        self.background = color;
234        self
235    }
236
237    /// Sets `self` to be masked.
238    ///
239    /// An item with _masked_ styling is not written out when painting is
240    /// disabled during `Display` or `Debug` invocations. When painting is
241    /// enabled, masking has no effect.
242    ///
243    /// ```rust
244    /// use diagnostic::Style;
245    ///
246    /// let masked = Style::default().mask();
247    ///
248    /// // "Whoops! " will only print when coloring is enabled.
249    /// println!("{}Something happened.", masked.paint("Whoops! "));
250    /// ```
251    #[inline]
252    pub fn mask(mut self) -> Style {
253        self.masked = true;
254        self
255    }
256
257    /// Sets `self` to be wrapping.
258    ///
259    /// A wrapping `Style` converts all color resets written out by the internal
260    /// value to the styling of itself. This allows for seamless color wrapping
261    /// of other colored text.
262    ///
263    /// # Performance
264    ///
265    /// In order to wrap an internal value, the internal value must first be
266    /// written out to a local buffer and examined. As a result, displaying a
267    /// wrapped value is likely to result in a heap allocation and copy.
268    ///
269    /// ```rust
270    /// use diagnostic::{Color, Paint, Style};
271    ///
272    /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
273    /// let wrapping = Style::new(Color::Blue).wrap();
274    ///
275    /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
276    /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
277    /// // unstyled.
278    /// println!("Hey! {}", wrapping.paint(inner));
279    /// ```
280    #[inline]
281    pub fn wrap(mut self) -> Style {
282        self.wrap = true;
283        self
284    }
285
286    style_builder_for!(Style, |style| style.properties,
287                       bold: BOLD, dimmed: DIMMED, italic: ITALIC,
288                       underline: UNDERLINE, blink: BLINK, invert: INVERT,
289                       hidden: HIDDEN, strikethrough: STRIKETHROUGH);
290
291    /// Constructs a new `Paint` structure that encapsulates `item` with the
292    /// style set to `self`.
293    ///
294    /// ```rust
295    /// use diagnostic::{Color, Style};
296    ///
297    /// let alert = Style::new(Color::Red).bold().underline();
298    /// println!("Alert: {}", alert.paint("This thing happened!"));
299    /// ```
300    #[inline]
301    pub fn paint<T>(self, item: T) -> Paint<T> {
302        Paint::new(item).with_style(self)
303    }
304
305    /// Returns the foreground color of `self`.
306    ///
307    /// ```rust
308    /// use diagnostic::{Color, Style};
309    ///
310    /// let plain = Style::default();
311    /// assert_eq!(plain.fg_color(), Color::Unset);
312    ///
313    /// let red = plain.fg(Color::Red);
314    /// assert_eq!(red.fg_color(), Color::Red);
315    /// ```
316    #[inline]
317    pub fn fg_color(&self) -> Color {
318        self.foreground
319    }
320
321    /// Returns the foreground color of `self`.
322    ///
323    /// ```rust
324    /// use diagnostic::{Color, Style};
325    ///
326    /// let plain = Style::default();
327    /// assert_eq!(plain.bg_color(), Color::Unset);
328    ///
329    /// let white = plain.bg(Color::White);
330    /// assert_eq!(white.bg_color(), Color::White);
331    /// ```
332    #[inline]
333    pub fn bg_color(&self) -> Color {
334        self.background
335    }
336
337    /// Returns `true` if `self` is masked.
338    ///
339    /// ```rust
340    /// use diagnostic::Style;
341    ///
342    /// let plain = Style::default();
343    /// assert!(!plain.is_masked());
344    ///
345    /// let masked = plain.mask();
346    /// assert!(masked.is_masked());
347    /// ```
348    #[inline]
349    pub fn is_masked(&self) -> bool {
350        self.masked
351    }
352
353    /// Returns `true` if `self` is wrapping.
354    ///
355    /// ```rust
356    /// use diagnostic::Style;
357    ///
358    /// let plain = Style::default();
359    /// assert!(!plain.is_wrapping());
360    ///
361    /// let wrapping = plain.wrap();
362    /// assert!(wrapping.is_wrapping());
363    /// ```
364    #[inline]
365    pub fn is_wrapping(&self) -> bool {
366        self.wrap
367    }
368
369    checker_for!(bold (is_bold): BOLD, dimmed (is_dimmed): DIMMED,
370        italic (is_italic): ITALIC, underline (is_underline): UNDERLINE,
371        blink (is_blink): BLINK, invert (is_invert): INVERT,
372        hidden (is_hidden): HIDDEN,
373        strikethrough (is_strikethrough): STRIKETHROUGH);
374
375    #[inline(always)]
376    fn is_plain(&self) -> bool {
377        self == &Style::default()
378    }
379
380    /// Writes the ANSI code prefix for the currently set styles.
381    ///
382    /// This method is intended to be used inside of [`fmt::Display`] and
383    /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
384    /// users should use [`Paint`] for all painting needs.
385    ///
386    /// This method writes the ANSI code prefix irrespective of whether painting
387    /// is currently enabled or disabled. To write the prefix only if painting
388    /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
389    ///
390    /// [`fmt::Display`]: fmt::Display
391    /// [`fmt::Debug`]: fmt::Debug
392    /// [`Paint`]: Paint
393    /// [`Paint::is_enabled()`]: Paint::is_enabled()
394    ///
395    /// # Example
396    ///
397    /// ```rust
398    /// use diagnostic::Style;
399    /// use std::fmt;
400    ///
401    /// struct CustomItem {
402    ///     item: u32,
403    ///     style: Style,
404    /// }
405    ///
406    /// impl fmt::Display for CustomItem {
407    ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
408    ///         self.style.fmt_prefix(f)?;
409    ///         write!(f, "number: {}", self.item)?;
410    ///         self.style.fmt_suffix(f)
411    ///     }
412    /// }
413    /// ```
414    pub fn fmt_prefix(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
415        // A user may just want a code-free string when no styles are applied.
416        if self.is_plain() {
417            return Ok(());
418        }
419
420        let mut splice = false;
421        write!(f, "\x1B[")?;
422
423        for i in self.properties.iter() {
424            let k = if i >= 5 { i + 2 } else { i + 1 };
425            write_spliced(&mut splice, f, k)?;
426        }
427
428        if self.background != Color::Unset {
429            write_spliced(&mut splice, f, "4")?;
430            self.background.ascii_fmt(f)?;
431        }
432
433        if self.foreground != Color::Unset {
434            write_spliced(&mut splice, f, "3")?;
435            self.foreground.ascii_fmt(f)?;
436        }
437
438        // All the codes end with an `m`.
439        write!(f, "m")
440    }
441
442    /// Writes the ANSI code suffix for the currently set styles.
443    ///
444    /// This method is intended to be used inside of [`fmt::Display`] and
445    /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
446    /// users should use [`Paint`] for all painting needs.
447    ///
448    /// This method writes the ANSI code suffix irrespective of whether painting
449    /// is currently enabled or disabled. To write the suffix only if painting
450    /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
451    ///
452    /// [`fmt::Display`]: fmt::Display
453    /// [`fmt::Debug`]: fmt::Debug
454    /// [`Paint`]: Paint
455    /// [`Paint::is_enabled()`]: Paint::is_enabled()
456    ///
457    /// # Example
458    ///
459    /// ```rust
460    /// use diagnostic::Style;
461    /// use std::fmt;
462    ///
463    /// struct CustomItem {
464    ///     item: u32,
465    ///     style: Style,
466    /// }
467    ///
468    /// impl fmt::Display for CustomItem {
469    ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
470    ///         self.style.fmt_prefix(f)?;
471    ///         write!(f, "number: {}", self.item)?;
472    ///         self.style.fmt_suffix(f)
473    ///     }
474    /// }
475    /// ```
476    pub fn fmt_suffix(&self, f: &mut impl std::fmt::Write) -> fmt::Result {
477        if self.is_plain() {
478            return Ok(());
479        }
480
481        write!(f, "\x1B[0m")
482    }
483}