radicle_term/ansi/
style.rs

1use std::fmt::{self, Display};
2use std::hash::{Hash, Hasher};
3use std::ops::BitOr;
4
5use super::{Color, Paint};
6
7#[derive(Default, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
8pub struct Property(u8);
9
10impl Property {
11    pub const BOLD: Self = Property(1 << 0);
12    pub const DIM: Self = Property(1 << 1);
13    pub const ITALIC: Self = Property(1 << 2);
14    pub const UNDERLINE: Self = Property(1 << 3);
15    pub const BLINK: Self = Property(1 << 4);
16    pub const INVERT: Self = Property(1 << 5);
17    pub const HIDDEN: Self = Property(1 << 6);
18    pub const STRIKETHROUGH: Self = Property(1 << 7);
19
20    pub const fn new() -> Self {
21        Property(0)
22    }
23
24    #[inline(always)]
25    pub const fn contains(self, other: Property) -> bool {
26        (other.0 & self.0) == other.0
27    }
28
29    #[inline(always)]
30    pub fn set(&mut self, other: Property) {
31        self.0 |= other.0;
32    }
33
34    #[inline(always)]
35    pub fn iter(self) -> Iter {
36        Iter {
37            index: 0,
38            properties: self,
39        }
40    }
41}
42
43impl BitOr for Property {
44    type Output = Self;
45
46    #[inline(always)]
47    fn bitor(self, rhs: Self) -> Self {
48        Property(self.0 | rhs.0)
49    }
50}
51
52pub struct Iter {
53    index: u8,
54    properties: Property,
55}
56
57impl Iterator for Iter {
58    type Item = usize;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        while self.index < 8 {
62            let index = self.index;
63            self.index += 1;
64
65            if self.properties.contains(Property(1 << index)) {
66                return Some(index as usize);
67            }
68        }
69
70        None
71    }
72}
73
74/// Represents a set of styling options.
75#[repr(C, packed)]
76#[derive(Default, Debug, Eq, Ord, PartialOrd, Copy, Clone)]
77pub struct Style {
78    pub(crate) foreground: Color,
79    pub(crate) background: Color,
80    pub(crate) properties: Property,
81    pub(crate) wrap: bool,
82}
83
84impl PartialEq for Style {
85    fn eq(&self, other: &Style) -> bool {
86        self.foreground == other.foreground
87            && self.background == other.background
88            && self.properties == other.properties
89    }
90}
91
92impl Hash for Style {
93    fn hash<H: Hasher>(&self, state: &mut H) {
94        self.foreground.hash(state);
95        self.background.hash(state);
96        self.properties.hash(state);
97    }
98}
99
100#[inline]
101fn write_spliced<T: Display>(c: &mut bool, f: &mut dyn fmt::Write, t: T) -> fmt::Result {
102    if *c {
103        write!(f, ";{t}")
104    } else {
105        *c = true;
106        write!(f, "{t}")
107    }
108}
109
110impl Style {
111    /// Default style with the foreground set to `color` and no other set
112    /// properties.
113    #[inline]
114    pub const fn new(color: Color) -> Style {
115        // Avoiding `Default::default` since unavailable as `const`
116        Self {
117            foreground: color,
118            background: Color::Unset,
119            properties: Property::new(),
120            wrap: false,
121        }
122    }
123
124    /// Sets the foreground to `color`.
125    #[inline]
126    pub const fn fg(mut self, color: Color) -> Style {
127        self.foreground = color;
128        self
129    }
130
131    /// Sets the background to `color`.
132    #[inline]
133    pub const fn bg(mut self, color: Color) -> Style {
134        self.background = color;
135        self
136    }
137
138    /// Merge styles with other. This is an additive process, so colors will only
139    /// be changed if they aren't set on the receiver object.
140    pub fn merge(mut self, other: Style) -> Style {
141        if self.foreground == Color::Unset {
142            self.foreground = other.foreground;
143        }
144        if self.background == Color::Unset {
145            self.background = other.background;
146        }
147        self.properties.set(other.properties);
148        self
149    }
150
151    /// Sets `self` to be wrapping.
152    ///
153    /// A wrapping `Style` converts all color resets written out by the internal
154    /// value to the styling of itself. This allows for seamless color wrapping
155    /// of other colored text.
156    ///
157    /// # Performance
158    ///
159    /// In order to wrap an internal value, the internal value must first be
160    /// written out to a local buffer and examined. As a result, displaying a
161    /// wrapped value is likely to result in a heap allocation and copy.
162    #[inline]
163    pub const fn wrap(mut self) -> Style {
164        self.wrap = true;
165        self
166    }
167
168    pub fn bold(mut self) -> Self {
169        self.properties.set(Property::BOLD);
170        self
171    }
172
173    pub fn dim(mut self) -> Self {
174        self.properties.set(Property::DIM);
175        self
176    }
177
178    pub fn italic(mut self) -> Self {
179        self.properties.set(Property::ITALIC);
180        self
181    }
182
183    pub fn underline(mut self) -> Self {
184        self.properties.set(Property::UNDERLINE);
185        self
186    }
187
188    pub fn invert(mut self) -> Self {
189        self.properties.set(Property::INVERT);
190        self
191    }
192
193    pub fn strikethrough(mut self) -> Self {
194        self.properties.set(Property::STRIKETHROUGH);
195        self
196    }
197
198    /// Constructs a new `Paint` structure that encapsulates `item` with the
199    /// style set to `self`.
200    #[inline]
201    pub fn paint<T>(self, item: T) -> Paint<T> {
202        Paint::new(item).with_style(self)
203    }
204
205    /// Returns the foreground color of `self`.
206    #[inline]
207    pub const fn fg_color(&self) -> Color {
208        self.foreground
209    }
210
211    /// Returns the foreground color of `self`.
212    #[inline]
213    pub const fn bg_color(&self) -> Color {
214        self.background
215    }
216
217    /// Returns `true` if `self` is wrapping.
218    #[inline]
219    pub const fn is_wrapping(&self) -> bool {
220        self.wrap
221    }
222
223    #[inline(always)]
224    fn is_plain(&self) -> bool {
225        self == &Style::default()
226    }
227
228    /// Writes the ANSI code prefix for the currently set styles.
229    ///
230    /// This method is intended to be used inside of [`fmt::Display`] and
231    /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
232    /// users should use [`Paint`] for all painting needs.
233    ///
234    /// This method writes the ANSI code prefix irrespective of whether painting
235    /// is currently enabled or disabled. To write the prefix only if painting
236    /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
237    pub fn fmt_prefix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
238        // A user may just want a code-free string when no styles are applied.
239        if self.is_plain() {
240            return Ok(());
241        }
242
243        let mut splice = false;
244        write!(f, "\x1B[")?;
245
246        for i in self.properties.iter() {
247            let k = if i >= 5 { i + 2 } else { i + 1 };
248            write_spliced(&mut splice, f, k)?;
249        }
250
251        if self.background != Color::Unset {
252            write_spliced(&mut splice, f, "4")?;
253            self.background.ansi_fmt(f)?;
254        }
255
256        if self.foreground != Color::Unset {
257            write_spliced(&mut splice, f, "3")?;
258            self.foreground.ansi_fmt(f)?;
259        }
260
261        // All the codes end with an `m`.
262        write!(f, "m")
263    }
264
265    /// Writes the ANSI code suffix for the currently set styles.
266    ///
267    /// This method is intended to be used inside of [`fmt::Display`] and
268    /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
269    /// users should use [`Paint`] for all painting needs.
270    ///
271    /// This method writes the ANSI code suffix irrespective of whether painting
272    /// is currently enabled or disabled. To write the suffix only if painting
273    /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
274    pub fn fmt_suffix(&self, f: &mut dyn fmt::Write) -> fmt::Result {
275        if self.is_plain() {
276            return Ok(());
277        }
278        write!(f, "\x1B[0m")
279    }
280}