Skip to main content

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