yansi/style.rs
1use std::hash::{Hash, Hasher};
2use std::fmt::{self, Display};
3use std::ops::BitOr;
4
5use {Paint, Color};
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 DIMMED: 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 #[inline(always)]
21 pub fn contains(self, other: Property) -> bool {
22 (other.0 & self.0) == other.0
23 }
24
25 #[inline(always)]
26 pub fn set(&mut self, other: Property) {
27 self.0 |= other.0;
28 }
29
30 #[inline(always)]
31 pub fn iter(self) -> Iter {
32 Iter { index: 0, properties: self }
33 }
34}
35
36impl BitOr for Property {
37 type Output = Self;
38
39 #[inline(always)]
40 fn bitor(self, rhs: Self) -> Self {
41 Property(self.0 | rhs.0)
42 }
43}
44
45pub struct Iter {
46 index: u8,
47 properties: Property,
48}
49
50impl Iterator for Iter {
51 type Item = usize;
52
53 fn next(&mut self) -> Option<Self::Item> {
54 while self.index < 8 {
55 let index = self.index;
56 self.index += 1;
57
58 if self.properties.contains(Property(1 << index)) {
59 return Some(index as usize);
60 }
61 }
62
63 None
64 }
65}
66
67/// Represents a set of styling options.
68///
69/// See the [crate level documentation](./) for usage information.
70///
71/// # Method Glossary
72///
73/// The `Style` structure exposes many methods for convenience. The majority of
74/// these methods are shared with [`Paint`](Paint).
75///
76/// ### Foreground Color Constructors
77///
78/// Return a new `Style` structure with a foreground `color` applied.
79///
80/// * [`Style::new(color: Color)`](Style::new())
81///
82/// ### Setters
83///
84/// Set a style property on a given `Style` structure.
85///
86/// * [`style.fg(color: Color)`](Style::fg())
87/// * [`style.bg(color: Color)`](Style::bg())
88/// * [`style.mask()`](Style::mask())
89/// * [`style.wrap()`](Style::wrap())
90/// * [`style.bold()`](Style::bold())
91/// * [`style.dimmed()`](Style::dimmed())
92/// * [`style.italic()`](Style::italic())
93/// * [`style.underline()`](Style::underline())
94/// * [`style.blink()`](Style::blink())
95/// * [`style.invert()`](Style::invert())
96/// * [`style.hidden()`](Style::hidden())
97/// * [`style.strikethrough()`](Style::strikethrough())
98///
99/// These methods can be chained:
100///
101/// ```rust
102/// use yansi::{Style, Color::{Red, Magenta}};
103///
104/// Style::new(Red).bg(Magenta).underline().invert().italic().dimmed().bold();
105/// ```
106///
107/// ### Converters
108///
109/// Convert a `Style` into another structure.
110///
111/// * [`style.paint<T>(item: T) -> Paint<T>`](Style::paint())
112///
113/// ### Getters
114///
115/// Return information about a `Style` structure.
116///
117/// * [`style.fg_color()`](Style::fg_color())
118/// * [`style.bg_color()`](Style::bg_color())
119/// * [`style.is_masked()`](Style::is_masked())
120/// * [`style.is_wrapping()`](Style::is_wrapping())
121/// * [`style.is_bold()`](Style::is_bold())
122/// * [`style.is_dimmed()`](Style::is_dimmed())
123/// * [`style.is_italic()`](Style::is_italic())
124/// * [`style.is_underline()`](Style::is_underline())
125/// * [`style.is_blink()`](Style::is_blink())
126/// * [`style.is_invert()`](Style::is_invert())
127/// * [`style.is_hidden()`](Style::is_hidden())
128/// * [`style.is_strikethrough()`](Style::is_strikethrough())
129///
130/// ### Raw Formatters
131///
132/// Write the raw ANSI codes for a given `Style` to any `fmt::Write`.
133///
134/// * [`style.fmt_prefix(f: &mut fmt::Write)`](Style::fmt_prefix())
135/// * [`style.fmt_suffix(f: &mut fmt::Write)`](Style::fmt_suffix())
136#[repr(packed)]
137#[derive(Default, Debug, Eq, Ord, PartialOrd, Copy, Clone)]
138pub struct Style {
139 pub(crate) foreground: Color,
140 pub(crate) background: Color,
141 pub(crate) properties: Property,
142 pub(crate) masked: bool,
143 pub(crate) wrap: bool,
144}
145
146impl PartialEq for Style {
147 fn eq(&self, other: &Style) -> bool {
148 self.foreground == other.foreground
149 && self.background == other.background
150 && self.properties == other.properties
151 }
152}
153
154impl Hash for Style {
155 fn hash<H: Hasher>(&self, state: &mut H) {
156 self.foreground.hash(state);
157 self.background.hash(state);
158 self.properties.hash(state);
159 }
160}
161
162macro_rules! checker_for {
163 ($($name:ident ($fn_name:ident): $property:ident),*) => ($(
164 #[doc = concat!(
165 "Returns `true` if the _", stringify!($name), "_ property is set on `self`.\n",
166 "```rust\n",
167 "use yansi::Style;\n",
168 "\n",
169 "let plain = Style::default();\n",
170 "assert!(!plain.", stringify!($fn_name), "());\n",
171 "\n",
172 "let styled = plain.", stringify!($name), "();\n",
173 "assert!(styled.", stringify!($fn_name), "());\n",
174 "```\n"
175 )]
176 #[inline]
177 pub fn $fn_name(&self) -> bool {
178 self.properties.contains(Property::$property)
179 }
180 )*)
181}
182
183#[inline]
184fn write_spliced<T: Display>(c: &mut bool, f: &mut fmt::Write, t: T) -> fmt::Result {
185 if *c {
186 write!(f, ";{}", t)
187 } else {
188 *c = true;
189 write!(f, "{}", t)
190 }
191}
192
193impl Style {
194 /// Default style with the foreground set to `color` and no other set
195 /// properties.
196 ///
197 /// ```rust
198 /// use yansi::Style;
199 ///
200 /// let plain = Style::default();
201 /// assert_eq!(plain, Style::default());
202 /// ```
203 #[inline]
204 pub fn new(color: Color) -> Style {
205 Self::default().fg(color)
206 }
207
208 /// Sets the foreground to `color`.
209 ///
210 /// ```rust
211 /// use yansi::{Color, Style};
212 ///
213 /// let red_fg = Style::default().fg(Color::Red);
214 /// ```
215 #[inline]
216 pub fn fg(mut self, color: Color) -> Style {
217 self.foreground = color;
218 self
219 }
220
221 /// Sets the background to `color`.
222 ///
223 /// ```rust
224 /// use yansi::{Color, Style};
225 ///
226 /// let red_bg = Style::default().bg(Color::Red);
227 /// ```
228 #[inline]
229 pub fn bg(mut self, color: Color) -> Style {
230 self.background = color;
231 self
232 }
233
234 /// Sets `self` to be masked.
235 ///
236 /// An item with _masked_ styling is not written out when painting is
237 /// disabled during `Display` or `Debug` invocations. When painting is
238 /// enabled, masking has no effect.
239 ///
240 /// ```rust
241 /// use yansi::Style;
242 ///
243 /// let masked = Style::default().mask();
244 ///
245 /// // "Whoops! " will only print when coloring is enabled.
246 /// println!("{}Something happened.", masked.paint("Whoops! "));
247 /// ```
248 #[inline]
249 pub fn mask(mut self) -> Style {
250 self.masked = true;
251 self
252 }
253
254 /// Sets `self` to be wrapping.
255 ///
256 /// A wrapping `Style` converts all color resets written out by the internal
257 /// value to the styling of itself. This allows for seamless color wrapping
258 /// of other colored text.
259 ///
260 /// # Performance
261 ///
262 /// In order to wrap an internal value, the internal value must first be
263 /// written out to a local buffer and examined. As a result, displaying a
264 /// wrapped value is likely to result in a heap allocation and copy.
265 ///
266 /// ```rust
267 /// use yansi::{Paint, Style, Color};
268 ///
269 /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
270 /// let wrapping = Style::new(Color::Blue).wrap();
271 ///
272 /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
273 /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
274 /// // unstyled.
275 /// println!("Hey! {}", wrapping.paint(inner));
276 /// ```
277 #[inline]
278 pub fn wrap(mut self) -> Style {
279 self.wrap = true;
280 self
281 }
282
283 style_builder_for!(Style, |style| style.properties,
284 bold: BOLD, dimmed: DIMMED, italic: ITALIC,
285 underline: UNDERLINE, blink: BLINK, invert: INVERT,
286 hidden: HIDDEN, strikethrough: STRIKETHROUGH);
287
288 /// Constructs a new `Paint` structure that encapsulates `item` with the
289 /// style set to `self`.
290 ///
291 /// ```rust
292 /// use yansi::{Style, Color};
293 ///
294 /// let alert = Style::new(Color::Red).bold().underline();
295 /// println!("Alert: {}", alert.paint("This thing happened!"));
296 /// ```
297 #[inline]
298 pub fn paint<T>(self, item: T) -> Paint<T> {
299 Paint::new(item).with_style(self)
300 }
301
302 /// Returns the foreground color of `self`.
303 ///
304 /// ```rust
305 /// use yansi::{Style, Color};
306 ///
307 /// let plain = Style::default();
308 /// assert_eq!(plain.fg_color(), Color::Unset);
309 ///
310 /// let red = plain.fg(Color::Red);
311 /// assert_eq!(red.fg_color(), Color::Red);
312 /// ```
313 #[inline]
314 pub fn fg_color(&self) -> Color {
315 self.foreground
316 }
317
318 /// Returns the foreground color of `self`.
319 ///
320 /// ```rust
321 /// use yansi::{Style, Color};
322 ///
323 /// let plain = Style::default();
324 /// assert_eq!(plain.bg_color(), Color::Unset);
325 ///
326 /// let white = plain.bg(Color::White);
327 /// assert_eq!(white.bg_color(), Color::White);
328 /// ```
329 #[inline]
330 pub fn bg_color(&self) -> Color {
331 self.background
332 }
333
334 /// Returns `true` if `self` is masked.
335 ///
336 /// ```rust
337 /// use yansi::Style;
338 ///
339 /// let plain = Style::default();
340 /// assert!(!plain.is_masked());
341 ///
342 /// let masked = plain.mask();
343 /// assert!(masked.is_masked());
344 /// ```
345 #[inline]
346 pub fn is_masked(&self) -> bool {
347 self.masked
348 }
349
350 /// Returns `true` if `self` is wrapping.
351 ///
352 /// ```rust
353 /// use yansi::Style;
354 ///
355 /// let plain = Style::default();
356 /// assert!(!plain.is_wrapping());
357 ///
358 /// let wrapping = plain.wrap();
359 /// assert!(wrapping.is_wrapping());
360 /// ```
361 #[inline]
362 pub fn is_wrapping(&self) -> bool {
363 self.wrap
364 }
365
366 checker_for!(bold (is_bold): BOLD, dimmed (is_dimmed): DIMMED,
367 italic (is_italic): ITALIC, underline (is_underline): UNDERLINE,
368 blink (is_blink): BLINK, invert (is_invert): INVERT,
369 hidden (is_hidden): HIDDEN,
370 strikethrough (is_strikethrough): STRIKETHROUGH);
371
372 #[inline(always)]
373 fn is_plain(&self) -> bool {
374 self == &Style::default()
375 }
376
377 /// Writes the ANSI code prefix for the currently set styles.
378 ///
379 /// This method is intended to be used inside of [`fmt::Display`] and
380 /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
381 /// users should use [`Paint`] for all painting needs.
382 ///
383 /// This method writes the ANSI code prefix irrespective of whether painting
384 /// is currently enabled or disabled. To write the prefix only if painting
385 /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
386 ///
387 /// [`fmt::Display`]: fmt::Display
388 /// [`fmt::Debug`]: fmt::Debug
389 /// [`Paint`]: Paint
390 /// [`Paint::is_enabled()`]: Paint::is_enabled()
391 ///
392 /// # Example
393 ///
394 /// ```rust
395 /// use std::fmt;
396 /// use yansi::Style;
397 ///
398 /// struct CustomItem {
399 /// item: u32,
400 /// style: Style
401 /// }
402 ///
403 /// impl fmt::Display for CustomItem {
404 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
405 /// self.style.fmt_prefix(f)?;
406 /// write!(f, "number: {}", self.item)?;
407 /// self.style.fmt_suffix(f)
408 /// }
409 /// }
410 /// ```
411 pub fn fmt_prefix(&self, f: &mut fmt::Write) -> fmt::Result {
412 // A user may just want a code-free string when no styles are applied.
413 if self.is_plain() {
414 return Ok(());
415 }
416
417 let mut splice = false;
418 write!(f, "\x1B[")?;
419
420 for i in self.properties.iter() {
421 let k = if i >= 5 { i + 2 } else { i + 1 };
422 write_spliced(&mut splice, f, k)?;
423 }
424
425 if self.background != Color::Unset {
426 write_spliced(&mut splice, f, "4")?;
427 self.background.ascii_fmt(f)?;
428 }
429
430 if self.foreground != Color::Unset {
431 write_spliced(&mut splice, f, "3")?;
432 self.foreground.ascii_fmt(f)?;
433 }
434
435 // All the codes end with an `m`.
436 write!(f, "m")
437 }
438
439 /// Writes the ANSI code suffix for the currently set styles.
440 ///
441 /// This method is intended to be used inside of [`fmt::Display`] and
442 /// [`fmt::Debug`] implementations for custom or specialized use-cases. Most
443 /// users should use [`Paint`] for all painting needs.
444 ///
445 /// This method writes the ANSI code suffix irrespective of whether painting
446 /// is currently enabled or disabled. To write the suffix only if painting
447 /// is enabled, condition a call to this method on [`Paint::is_enabled()`].
448 ///
449 /// [`fmt::Display`]: fmt::Display
450 /// [`fmt::Debug`]: fmt::Debug
451 /// [`Paint`]: Paint
452 /// [`Paint::is_enabled()`]: Paint::is_enabled()
453 ///
454 /// # Example
455 ///
456 /// ```rust
457 /// use std::fmt;
458 /// use yansi::Style;
459 ///
460 /// struct CustomItem {
461 /// item: u32,
462 /// style: Style
463 /// }
464 ///
465 /// impl fmt::Display for CustomItem {
466 /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
467 /// self.style.fmt_prefix(f)?;
468 /// write!(f, "number: {}", self.item)?;
469 /// self.style.fmt_suffix(f)
470 /// }
471 /// }
472 /// ```
473 pub fn fmt_suffix(&self, f: &mut fmt::Write) -> fmt::Result {
474 if self.is_plain() {
475 return Ok(());
476 }
477
478 write!(f, "\x1B[0m")
479 }
480}