anstyle/
effect.rs

1/// A set of text effects
2///
3/// # Examples
4///
5/// ```rust
6/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
7/// ```
8#[derive(Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
9pub struct Effects(u16);
10
11impl Effects {
12    /// No [`Effects`] applied
13    const PLAIN: Self = Effects(0);
14
15    #[allow(missing_docs)]
16    pub const BOLD: Self = Effects(1 << 0);
17    #[allow(missing_docs)]
18    pub const DIMMED: Self = Effects(1 << 1);
19    /// Not widely supported. Sometimes treated as inverse or blink
20    pub const ITALIC: Self = Effects(1 << 2);
21    /// Style extensions exist for Kitty, VTE, mintty and iTerm2.
22    pub const UNDERLINE: Self = Effects(1 << 3);
23    #[allow(missing_docs)]
24    pub const DOUBLE_UNDERLINE: Self = Effects(1 << 4);
25    #[allow(missing_docs)]
26    pub const CURLY_UNDERLINE: Self = Effects(1 << 5);
27    #[allow(missing_docs)]
28    pub const DOTTED_UNDERLINE: Self = Effects(1 << 6);
29    #[allow(missing_docs)]
30    pub const DASHED_UNDERLINE: Self = Effects(1 << 7);
31    #[allow(missing_docs)]
32    pub const BLINK: Self = Effects(1 << 8);
33    /// Swap foreground and background colors; inconsistent emulation
34    pub const INVERT: Self = Effects(1 << 9);
35    #[allow(missing_docs)]
36    pub const HIDDEN: Self = Effects(1 << 10);
37    ///  Characters legible but marked as if for deletion. Not supported in Terminal.app
38    pub const STRIKETHROUGH: Self = Effects(1 << 11);
39
40    /// No effects enabled
41    ///
42    /// # Examples
43    ///
44    /// ```rust
45    /// let effects = anstyle::Effects::new();
46    /// ```
47    #[inline]
48    pub const fn new() -> Self {
49        Self::PLAIN
50    }
51
52    /// Check if no effects are enabled
53    ///
54    /// # Examples
55    ///
56    /// ```rust
57    /// let effects = anstyle::Effects::new();
58    /// assert!(effects.is_plain());
59    ///
60    /// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
61    /// assert!(!effects.is_plain());
62    /// ```
63    #[inline]
64    pub const fn is_plain(self) -> bool {
65        self.0 == Self::PLAIN.0
66    }
67
68    /// Returns `true` if all of the effects in `other` are contained within `self`.
69    ///
70    /// # Examples
71    ///
72    /// ```rust
73    /// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
74    /// assert!(effects.contains(anstyle::Effects::BOLD));
75    ///
76    /// let effects = anstyle::Effects::new();
77    /// assert!(!effects.contains(anstyle::Effects::BOLD));
78    /// ```
79    #[inline(always)]
80    pub const fn contains(self, other: Effects) -> bool {
81        (other.0 & self.0) == other.0
82    }
83
84    /// Inserts the specified effects in-place.
85    ///
86    /// # Examples
87    ///
88    /// ```rust
89    /// let effects = anstyle::Effects::new().insert(anstyle::Effects::new());
90    /// assert!(effects.is_plain());
91    ///
92    /// let effects = anstyle::Effects::new().insert(anstyle::Effects::BOLD);
93    /// assert!(effects.contains(anstyle::Effects::BOLD));
94    /// ```
95    #[inline(always)]
96    #[must_use]
97    pub const fn insert(mut self, other: Effects) -> Self {
98        self.0 |= other.0;
99        self
100    }
101
102    /// Removes the specified effects in-place.
103    ///
104    /// # Examples
105    ///
106    /// ```rust
107    /// let effects = (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).remove(anstyle::Effects::BOLD);
108    /// assert!(!effects.contains(anstyle::Effects::BOLD));
109    /// assert!(effects.contains(anstyle::Effects::UNDERLINE));
110    /// ```
111    #[inline(always)]
112    #[must_use]
113    pub const fn remove(mut self, other: Effects) -> Self {
114        self.0 &= !other.0;
115        self
116    }
117
118    /// Reset all effects in-place
119    /// ```rust
120    /// let effects = (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE).clear();
121    /// assert!(!effects.contains(anstyle::Effects::BOLD));
122    /// assert!(!effects.contains(anstyle::Effects::UNDERLINE));
123    /// ```
124    #[inline(always)]
125    #[must_use]
126    pub const fn clear(self) -> Self {
127        Self::new()
128    }
129
130    /// Enable or disable the specified effects depending on the passed value.
131    ///
132    /// # Examples
133    ///
134    /// ```rust
135    /// let effects = anstyle::Effects::new().set(anstyle::Effects::BOLD, true);
136    /// assert!(effects.contains(anstyle::Effects::BOLD));
137    /// ```
138    #[inline]
139    #[must_use]
140    pub const fn set(self, other: Self, enable: bool) -> Self {
141        if enable {
142            self.insert(other)
143        } else {
144            self.remove(other)
145        }
146    }
147
148    /// Iterate over enabled effects
149    #[inline(always)]
150    pub fn iter(self) -> EffectIter {
151        EffectIter {
152            index: 0,
153            effects: self,
154        }
155    }
156
157    /// Iterate over enabled effect indices
158    #[inline(always)]
159    pub(crate) fn index_iter(self) -> EffectIndexIter {
160        EffectIndexIter {
161            index: 0,
162            effects: self,
163        }
164    }
165
166    /// Render the ANSI code
167    #[inline]
168    pub fn render(self) -> impl core::fmt::Display + Copy {
169        EffectsDisplay(self)
170    }
171
172    #[inline]
173    #[cfg(feature = "std")]
174    pub(crate) fn write_to(self, write: &mut dyn std::io::Write) -> std::io::Result<()> {
175        for index in self.index_iter() {
176            write.write_all(METADATA[index].escape.as_bytes())?;
177        }
178        Ok(())
179    }
180}
181
182/// # Examples
183///
184/// ```rust
185/// let effects = anstyle::Effects::new();
186/// assert_eq!(format!("{:?}", effects), "Effects()");
187///
188/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
189/// assert_eq!(format!("{:?}", effects), "Effects(BOLD | UNDERLINE)");
190/// ```
191impl core::fmt::Debug for Effects {
192    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
193        write!(f, "Effects(")?;
194        for (i, index) in self.index_iter().enumerate() {
195            if i != 0 {
196                write!(f, " | ")?;
197            }
198            write!(f, "{}", METADATA[index].name)?;
199        }
200        write!(f, ")")?;
201        Ok(())
202    }
203}
204
205/// # Examples
206///
207/// ```rust
208/// let effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
209/// assert_eq!(format!("{:?}", effects), "Effects(BOLD | UNDERLINE)");
210/// ```
211impl core::ops::BitOr for Effects {
212    type Output = Self;
213
214    #[inline(always)]
215    fn bitor(self, rhs: Self) -> Self {
216        self.insert(rhs)
217    }
218}
219
220/// # Examples
221///
222/// ```rust
223/// let mut effects = anstyle::Effects::BOLD;
224/// effects |= anstyle::Effects::UNDERLINE;
225/// assert_eq!(format!("{:?}", effects), "Effects(BOLD | UNDERLINE)");
226/// ```
227impl core::ops::BitOrAssign for Effects {
228    #[inline]
229    fn bitor_assign(&mut self, other: Self) {
230        *self = self.insert(other);
231    }
232}
233
234/// # Examples
235///
236/// ```rust
237/// let effects = (anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE) - anstyle::Effects::BOLD;
238/// assert_eq!(format!("{:?}", effects), "Effects(UNDERLINE)");
239/// ```
240impl core::ops::Sub for Effects {
241    type Output = Self;
242
243    #[inline]
244    fn sub(self, other: Self) -> Self {
245        self.remove(other)
246    }
247}
248
249/// # Examples
250///
251/// ```rust
252/// let mut effects = anstyle::Effects::BOLD | anstyle::Effects::UNDERLINE;
253/// effects -= anstyle::Effects::BOLD;
254/// assert_eq!(format!("{:?}", effects), "Effects(UNDERLINE)");
255/// ```
256impl core::ops::SubAssign for Effects {
257    #[inline]
258    fn sub_assign(&mut self, other: Self) {
259        *self = self.remove(other);
260    }
261}
262
263pub(crate) struct Metadata {
264    pub(crate) name: &'static str,
265    pub(crate) escape: &'static str,
266}
267
268pub(crate) const METADATA: [Metadata; 12] = [
269    Metadata {
270        name: "BOLD",
271        escape: escape!("1"),
272    },
273    Metadata {
274        name: "DIMMED",
275        escape: escape!("2"),
276    },
277    Metadata {
278        name: "ITALIC",
279        escape: escape!("3"),
280    },
281    Metadata {
282        name: "UNDERLINE",
283        escape: escape!("4"),
284    },
285    Metadata {
286        name: "DOUBLE_UNDERLINE",
287        escape: escape!("21"),
288    },
289    Metadata {
290        name: "CURLY_UNDERLINE",
291        escape: escape!("4:3"),
292    },
293    Metadata {
294        name: "DOTTED_UNDERLINE",
295        escape: escape!("4:4"),
296    },
297    Metadata {
298        name: "DASHED_UNDERLINE",
299        escape: escape!("4:5"),
300    },
301    Metadata {
302        name: "BLINK",
303        escape: escape!("5"),
304    },
305    Metadata {
306        name: "INVERT",
307        escape: escape!("7"),
308    },
309    Metadata {
310        name: "HIDDEN",
311        escape: escape!("8"),
312    },
313    Metadata {
314        name: "STRIKETHROUGH",
315        escape: escape!("9"),
316    },
317];
318
319#[derive(Copy, Clone, Default, Debug)]
320struct EffectsDisplay(Effects);
321
322impl core::fmt::Display for EffectsDisplay {
323    #[inline]
324    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
325        for index in self.0.index_iter() {
326            f.write_str(METADATA[index].escape)?;
327        }
328        Ok(())
329    }
330}
331
332/// Enumerate each enabled value in [`Effects`]
333#[derive(Clone, Debug, PartialEq, Eq)]
334pub struct EffectIter {
335    index: usize,
336    effects: Effects,
337}
338
339impl Iterator for EffectIter {
340    type Item = Effects;
341
342    fn next(&mut self) -> Option<Self::Item> {
343        while self.index < METADATA.len() {
344            let index = self.index;
345            self.index += 1;
346
347            let effect = Effects(1 << index);
348            if self.effects.contains(effect) {
349                return Some(effect);
350            }
351        }
352
353        None
354    }
355}
356
357#[derive(Clone, Debug, PartialEq, Eq)]
358pub(crate) struct EffectIndexIter {
359    index: usize,
360    effects: Effects,
361}
362
363impl Iterator for EffectIndexIter {
364    type Item = usize;
365
366    fn next(&mut self) -> Option<Self::Item> {
367        while self.index < METADATA.len() {
368            let index = self.index;
369            self.index += 1;
370
371            let effect = Effects(1 << index);
372            if self.effects.contains(effect) {
373                return Some(index);
374            }
375        }
376
377        None
378    }
379}
380
381#[cfg(test)]
382#[cfg(feature = "std")]
383mod test {
384    use super::*;
385
386    #[test]
387    fn print_size_of() {
388        use std::mem::size_of;
389        dbg!(size_of::<Effects>());
390        dbg!(size_of::<EffectsDisplay>());
391    }
392
393    #[test]
394    fn no_align() {
395        #[track_caller]
396        fn assert_no_align(d: impl core::fmt::Display) {
397            let expected = format!("{d}");
398            let actual = format!("{d:<10}");
399            assert_eq!(expected, actual);
400        }
401
402        assert_no_align(Effects::BOLD.render());
403    }
404}