yansi/
style.rs

1use core::fmt::{self, Write};
2
3use crate::color::{Color, Variant};
4use crate::attr_quirk::{Attribute, Quirk};
5use crate::condition::Condition;
6use crate::set::Set;
7
8#[cfg(all(feature = "alloc", not(feature = "std")))]
9use alloc::{string::String, borrow::Cow};
10
11#[cfg(feature = "std")]
12use std::borrow::Cow;
13
14/// A set of styling options.
15///
16/// ## Equivalence and Ordering
17///
18/// Only a style's `foreground`, `background`, and set of `attributes` are
19/// considered when testing for equivalence or producing an ordering via
20/// `PartialEq` or `Eq`, and `PartialOrd` or `Ord`. A style's quirks and
21/// conditions are ignored.
22#[derive(Default, Debug, Copy, Clone)]
23pub struct Style {
24    /// The foreground color. Defaults to `None`.
25    ///
26    /// ```rust
27    /// use yansi::{Style, Color};
28    ///
29    /// assert_eq!(Style::new().foreground, None);
30    /// assert_eq!(Style::new().green().foreground, Some(Color::Green));
31    /// ```
32    pub foreground: Option<Color>,
33    /// The background color. Defaults to `None`.
34    ///
35    /// ```rust
36    /// use yansi::{Style, Color};
37    ///
38    /// assert_eq!(Style::new().background, None);
39    /// assert_eq!(Style::new().on_red().background, Some(Color::Red));
40    /// ```
41    pub background: Option<Color>,
42    pub(crate) attributes: Set<Attribute>,
43    pub(crate) quirks: Set<Quirk>,
44    /// The condition.
45    ///
46    /// To check a style's condition directly, use [`Style::enabled()`]:
47    ///
48    /// ```rust
49    /// use yansi::{Style, Condition};
50    ///
51    /// let style = Style::new().whenever(Condition::ALWAYS);
52    /// assert!(style.enabled());
53    ///
54    /// let style = Style::new().whenever(Condition::NEVER);
55    /// assert!(!style.enabled());
56    /// ```
57    pub condition: Option<Condition>,
58}
59
60struct AnsiSplicer<'a> {
61    f: &'a mut dyn fmt::Write,
62    splice: bool,
63}
64
65#[derive(Debug)]
66#[allow(non_camel_case_types)]
67pub enum Application {
68    fg(Color),
69    bg(Color),
70    attr(Attribute),
71    quirk(Quirk),
72    whenever(Condition),
73}
74
75impl Style {
76    const DEFAULT: Style = Style {
77        foreground: None,
78        background: None,
79        attributes: Set::EMPTY,
80        quirks: Set::EMPTY,
81        condition: None,
82    };
83
84    /// Returns a new style with no foreground or background, no attributes
85    /// or quirks, and [`Condition::DEFAULT`].
86    ///
87    /// This is the default returned by [`Default::default()`].
88    ///
89    /// # Example
90    ///
91    /// ```rust
92    /// use yansi::Style;
93    ///
94    /// assert_eq!(Style::new(), Style::default());
95    /// ```
96    #[inline]
97    pub const fn new() -> Style {
98        Style::DEFAULT
99    }
100
101    #[inline(always)]
102    pub(crate) const fn apply(mut self, a: Application) -> Style {
103        match a {
104            Application::fg(color) => self.foreground = Some(color),
105            Application::bg(color) => self.background = Some(color),
106            Application::whenever(cond) => self.condition = Some(cond),
107            Application::attr(attr) => self.attributes = self.attributes.insert(attr),
108            Application::quirk(quirk) => self.quirks = self.quirks.insert(quirk),
109        }
110
111        self
112    }
113
114    /// Returns `true` if this style is enabled, based on
115    /// [`condition`](Paint.condition).
116    ///
117    /// **Note:** _For a style to be effected, both this method **and**
118    /// [`yansi::is_enabled()`](crate::is_enabled) must return `true`._
119    ///
120    /// When there is no condition set, this method always returns `true`. When
121    /// a condition has been set, this evaluates the condition and returns the
122    /// result.
123    ///
124    /// # Example
125    ///
126    /// ```rust
127    /// use yansi::{Style, Condition};
128    ///
129    /// let style = Style::new().whenever(Condition::ALWAYS);
130    /// assert!(style.enabled());
131    ///
132    /// let style = Style::new().whenever(Condition::NEVER);
133    /// assert!(!style.enabled());
134    /// ```
135    pub fn enabled(&self) -> bool {
136        self.condition.map_or(true, |c| c())
137    }
138
139    /// Writes the ANSI code prefix for the currently set styles.
140    ///
141    /// This method is intended to be used inside of [`fmt::Display`] and
142    /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
143    /// users should use [`Painted`] for all painting needs.
144    ///
145    /// This method writes the ANSI code prefix irrespective of whether painting
146    /// is currently enabled or disabled. To write the prefix only if painting
147    /// is enabled, condition a call to this method on [`is_enabled()`].
148    ///
149    /// [`fmt::Display`]: fmt::Display
150    /// [`fmt::Debug`]: fmt::Debug
151    /// [`Painted`]: crate::Painted
152    /// [`is_enabled()`]: crate::is_enabled()
153    ///
154    /// # Example
155    ///
156    /// ```rust
157    /// use core::fmt;
158    /// use yansi::Style;
159    ///
160    /// struct CustomItem {
161    ///     item: u32,
162    ///     style: Style
163    /// }
164    ///
165    /// impl fmt::Display for CustomItem {
166    ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
167    ///         self.style.fmt_prefix(f)?;
168    ///         write!(f, "number: {}", self.item)?;
169    ///         self.style.fmt_suffix(f)
170    ///     }
171    /// }
172    /// ```
173    pub fn fmt_prefix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
174        // Give a sequence-free string when no styles are applied.
175        if self == &Style::DEFAULT {
176            return Ok(());
177        }
178
179        let brighten = |color: Option<Color>, bright: bool| match (color, bright) {
180            (Some(color), true) => Some(color.to_bright()),
181            _ => color
182        };
183
184        let mut f = AnsiSplicer { f, splice: false };
185        f.write_str("\x1B[")?;
186
187        for attr in self.attributes.iter() {
188            f.splice()?;
189            attr.fmt(&mut f)?;
190        }
191
192        if let Some(color) = brighten(self.background, self.quirks.contains(Quirk::OnBright)) {
193            f.splice()?;
194            color.fmt(&mut f, Variant::Bg)?;
195        }
196
197        if let Some(color) = brighten(self.foreground, self.quirks.contains(Quirk::Bright)) {
198            f.splice()?;
199            color.fmt(&mut f, Variant::Fg)?;
200        }
201
202        // All of the sequences end with an `m`.
203        f.write_char('m')
204    }
205
206    /// Returns the ANSI code sequence prefix for the style as a string.
207    ///
208    /// This returns a string with the exact same sequence written by
209    /// [`fmt_prefix()`](Self::fmt_prefix()). See that method for details.
210    #[cfg(feature = "alloc")]
211    #[cfg_attr(feature = "_nightly", doc(cfg(feature = "alloc")))]
212    pub fn prefix(&self) -> Cow<'static, str> {
213        let mut prefix = String::new();
214        let _ = self.fmt_prefix(&mut prefix);
215        prefix.into()
216    }
217
218    /// Writes the ANSI code sequence suffix for the style.
219    ///
220    /// This method is intended to be used inside of [`fmt::Display`] and
221    /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
222    /// users should use [`Painted`] for all painting needs.
223    ///
224    /// This method writes the ANSI code suffix irrespective of whether painting
225    /// is currently enabled or disabled. To write the suffix only if painting
226    /// is enabled, condition a call to this method on [`is_enabled()`].
227    ///
228    /// [`fmt::Display`]: fmt::Display
229    /// [`fmt::Debug`]: fmt::Debug
230    /// [`Painted`]: crate::Painted
231    /// [`is_enabled()`]: crate::is_enabled()
232    ///
233    /// # Example
234    ///
235    /// ```rust
236    /// use core::fmt;
237    /// use yansi::Style;
238    ///
239    /// struct CustomItem {
240    ///     item: u32,
241    ///     style: Style
242    /// }
243    ///
244    /// impl fmt::Display for CustomItem {
245    ///     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246    ///         self.style.fmt_prefix(f)?;
247    ///         write!(f, "number: {}", self.item)?;
248    ///         self.style.fmt_suffix(f)
249    ///     }
250    /// }
251    /// ```
252    pub fn fmt_suffix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
253        if !self.quirks.contains(Quirk::Resetting) && !self.quirks.contains(Quirk::Clear) {
254            if self.quirks.contains(Quirk::Linger) || self == &Style::DEFAULT {
255                return Ok(());
256            }
257        }
258
259        f.write_str("\x1B[0m")
260    }
261
262    /// Returns the ANSI code sequence suffix for the style as a string.
263    ///
264    /// This returns a string with the exact same sequence written by
265    /// [`fmt_suffix()`](Self::fmt_suffix()). See that method for details.
266    #[cfg(feature = "alloc")]
267    #[cfg_attr(feature = "_nightly", doc(cfg(feature = "alloc")))]
268    pub fn suffix(&self) -> Cow<'static, str> {
269        if !self.quirks.contains(Quirk::Resetting) && !self.quirks.contains(Quirk::Clear) {
270            if self.quirks.contains(Quirk::Linger) || self == &Style::DEFAULT {
271                return Cow::from("");
272            }
273        }
274
275        Cow::from("\x1B[0m")
276    }
277
278    properties!([pub const] constructor(Self) -> Self);
279}
280
281impl AnsiSplicer<'_> {
282    fn splice(&mut self) -> fmt::Result {
283        if self.splice { self.f.write_char(';')?; }
284        self.splice = true;
285        Ok(())
286    }
287}
288
289impl fmt::Write for AnsiSplicer<'_> {
290    fn write_str(&mut self, s: &str) -> fmt::Result {
291        self.f.write_str(s)
292    }
293}
294
295impl PartialEq for Style {
296    fn eq(&self, other: &Self) -> bool {
297        let Style {
298            foreground: fg_a,
299            background: bg_a,
300            attributes: attrs_a,
301            quirks: _,
302            condition: _,
303        } = self;
304
305        let Style {
306            foreground: fg_b,
307            background: bg_b,
308            attributes: attrs_b,
309            quirks: _,
310            condition: _,
311        } = other;
312
313        fg_a == fg_b && bg_a == bg_b && attrs_a == attrs_b
314    }
315}
316
317impl Eq for Style { }
318
319impl core::hash::Hash for Style {
320    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
321        let Style { foreground, background, attributes, quirks: _, condition: _, } = self;
322        foreground.hash(state);
323        background.hash(state);
324        attributes.hash(state);
325    }
326}
327
328impl PartialOrd for Style {
329    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
330        let Style {
331            foreground: fg_a,
332            background: bg_a,
333            attributes: attrs_a,
334            quirks: _,
335            condition: _,
336        } = self;
337
338        let Style {
339            foreground: fg_b,
340            background: bg_b,
341            attributes: attrs_b,
342            quirks: _,
343            condition: _,
344        } = other;
345
346        match fg_a.partial_cmp(&fg_b) {
347            Some(core::cmp::Ordering::Equal) => {}
348            ord => return ord,
349        }
350
351        match bg_a.partial_cmp(&bg_b) {
352            Some(core::cmp::Ordering::Equal) => {}
353            ord => return ord,
354        }
355
356        attrs_a.partial_cmp(&attrs_b)
357    }
358}
359
360impl Ord for Style {
361    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
362        let Style {
363            foreground: fg_a,
364            background: bg_a,
365            attributes: attrs_a,
366            quirks: _,
367            condition: _,
368        } = self;
369
370        let Style {
371            foreground: fg_b,
372            background: bg_b,
373            attributes: attrs_b,
374            quirks: _,
375            condition: _,
376        } = other;
377
378        match fg_a.cmp(&fg_b) {
379            core::cmp::Ordering::Equal => {}
380            ord => return ord,
381        }
382
383        match bg_a.cmp(&bg_b) {
384            core::cmp::Ordering::Equal => {}
385            ord => return ord,
386        }
387
388        attrs_a.cmp(&attrs_b)
389    }
390}