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}