yansi/
paint.rs

1use std::fmt;
2
3use style::{Style, Property};
4use color::Color;
5
6/// A structure encapsulating an item and styling.
7///
8/// See the [crate level documentation](./) for usage information.
9///
10/// # Method Glossary
11///
12/// The `Paint` structure exposes many methods for convenience.
13///
14/// ### Unstyled Constructors
15///
16/// Return a new `Paint` structure with no or default styling applied.
17///
18///   * [`Paint::new(item: T)`](Paint::new())
19///   * [`Paint::default(item: T)`](Paint::default())
20///   * [`Paint::masked(item: T)`](Paint::masked())
21///   * [`Paint::wrapping(item: T)`](Paint::wrapping())
22///
23/// ### Foreground Color Constructors
24///
25/// Return a new `Paint` structure with a foreground color applied.
26///
27///   * [`Paint::rgb(r: u8, g: u8, b: u8, item: T)`](Paint::rgb())
28///   * [`Paint::fixed(color: u8, item: T)`](Paint::fixed())
29///   * [`Paint::black(item: T)`](Paint::black())
30///   * [`Paint::red(item: T)`](Paint::red())
31///   * [`Paint::green(item: T)`](Paint::green())
32///   * [`Paint::yellow(item: T)`](Paint::yellow())
33///   * [`Paint::blue(item: T)`](Paint::blue())
34///   * [`Paint::magenta(item: T)`](Paint::magenta())
35///   * [`Paint::cyan(item: T)`](Paint::cyan())
36///   * [`Paint::white(item: T)`](Paint::white())
37///
38/// ### Getters
39///
40/// Return information about the `Paint` structure.
41///
42///   * [`paint.style()`](Paint::style())
43///   * [`paint.inner()`](Paint::inner())
44///
45/// ### Setters
46///
47/// Set a style property on a given `Paint` structure.
48///
49///   * [`paint.with_style(style: Style)`](Paint::with_style())
50///   * [`paint.mask()`](Paint::mask())
51///   * [`paint.wrap()`](Paint::wrap())
52///   * [`paint.fg(color: Color)`](Paint::fg())
53///   * [`paint.bg(color: Color)`](Paint::bg())
54///   * [`paint.bold()`](Paint::bold())
55///   * [`paint.dimmed()`](Paint::dimmed())
56///   * [`paint.italic()`](Paint::italic())
57///   * [`paint.underline()`](Paint::underline())
58///   * [`paint.blink()`](Paint::blink())
59///   * [`paint.invert()`](Paint::invert())
60///   * [`paint.hidden()`](Paint::hidden())
61///   * [`paint.strikethrough()`](Paint::strikethrough())
62///
63/// These methods can be chained:
64///
65/// ```rust
66/// use yansi::Paint;
67///
68/// Paint::new("hi").underline().invert().italic().dimmed().bold();
69/// ```
70///
71/// ### Global Methods
72///
73/// Modify or observe the global behavior of painting.
74///
75///   * [`Paint::enable()`](Paint::enable())
76///   * [`Paint::disable()`](Paint::disable())
77///   * [`Paint::is_enabled()`](Paint::is_enabled())
78///   * [`Paint::enable_windows_ascii()`](Paint::enable_windows_ascii())
79#[derive(Default, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
80pub struct Paint<T> {
81    item: T,
82    style: Style,
83}
84
85macro_rules! constructors_for {
86    ($T:ty, $($name:ident: $color:ident),*) => ($(
87        #[doc = concat!(
88            "Constructs a new `Paint` structure encapsulating `item` with the foreground color\n",
89            "set to ", stringify!($name), ".\n",
90            "```rust\n",
91            "use yansi::Paint;\n",
92            "\n",
93            "println!(\"This is going to be ", stringify!($name),
94                ": {}\", Paint::", stringify!($name), "(\"yay!\"));\n",
95            "```\n"
96        )]
97        #[inline]
98        pub fn $name(item: $T) -> Paint<$T> {
99            Paint::new(item).fg(Color::$color)
100        }
101    )*)
102}
103
104impl<T> Paint<T> {
105    /// Constructs a new `Paint` structure encapsulating `item` with no set
106    /// styling.
107    ///
108    /// ```rust
109    /// use yansi::Paint;
110    ///
111    /// assert_eq!(Paint::new("hello!").to_string(), "hello!".to_string());
112    /// ```
113    #[inline]
114    pub fn new(item: T) -> Paint<T> {
115        Paint { item, style: Style::default() }
116    }
117
118    /// Constructs a new `Paint` structure encapsulating `item` with the active
119    /// terminal's default foreground and background.
120    ///
121    /// ```rust
122    /// use yansi::Paint;
123    ///
124    /// println!("This is going to use {}!", Paint::default("default colors"));
125    /// ```
126    #[inline]
127    pub fn default(item: T) -> Paint<T> {
128        Paint::new(item).fg(Color::Default).bg(Color::Default)
129    }
130
131    /// Constructs a new _masked_ `Paint` structure encapsulating `item` with
132    /// no set styling.
133    ///
134    /// A masked `Paint` is not written out when painting is disabled during
135    /// `Display` or `Debug` invocations. When painting is enabled, masking has
136    /// no effect.
137    ///
138    /// ```rust
139    /// use yansi::Paint;
140    ///
141    /// // The emoji won't be printed when coloring is disabled.
142    /// println!("{}Sprout!", Paint::masked("🌱 "));
143    /// ```
144    #[inline]
145    pub fn masked(item: T) -> Paint<T> {
146        Paint::new(item).mask()
147    }
148
149    /// Constructs a new _wrapping_ `Paint` structure encapsulating `item` with
150    /// default styling.
151    ///
152    /// A wrapping `Paint` converts all color resets written out by the internal
153    /// value to the styling of itself. This allows for seamless color wrapping
154    /// of other colored text.
155    ///
156    /// # Performance
157    ///
158    /// In order to wrap an internal value, the internal value must first be
159    /// written out to a local buffer and examined. As a result, displaying a
160    /// wrapped value is likely to result in a heap allocation and copy.
161    ///
162    /// # Example
163    ///
164    /// ```rust
165    /// use yansi::{Paint, Color};
166    ///
167    /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
168    ///
169    /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
170    /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
171    /// // unstyled.
172    /// println!("Hey! {}", Paint::wrapping(inner).fg(Color::Blue));
173    /// ```
174    #[inline]
175    pub fn wrapping(item: T) -> Paint<T> {
176        Paint::new(item).wrap()
177    }
178
179    /// Constructs a new `Paint` structure encapsulating `item` with the
180    /// foreground color set to the RGB color `r`, `g`, `b`.
181    ///
182    /// ```rust
183    /// use yansi::Paint;
184    ///
185    /// println!("This is going to be funky: {}", Paint::rgb(70, 130, 122, "hi!"));
186    /// ```
187    #[inline]
188    pub fn rgb(r: u8, g: u8, b: u8, item: T) -> Paint<T> {
189        Paint::new(item).fg(Color::RGB(r, g, b))
190    }
191
192    /// Constructs a new `Paint` structure encapsulating `item` with the
193    /// foreground color set to the fixed 8-bit color `color`.
194    ///
195    /// ```rust
196    /// use yansi::Paint;
197    ///
198    /// println!("This is going to be funky: {}", Paint::fixed(100, "hi!"));
199    /// ```
200    #[inline]
201    pub fn fixed(color: u8, item: T) -> Paint<T> {
202        Paint::new(item).fg(Color::Fixed(color))
203    }
204
205    constructors_for!(T, black: Black, red: Red, green: Green, yellow: Yellow,
206        blue: Blue, magenta: Magenta, cyan: Cyan, white: White);
207
208    /// Retrieves the style currently set on `self`.
209    ///
210    /// ```rust
211    /// use yansi::{Style, Color, Paint};
212    ///
213    /// let alert = Style::new(Color::Red).bold().underline();
214    /// let painted = Paint::red("hi").bold().underline();
215    ///
216    /// assert_eq!(alert, painted.style());
217    /// ```
218    #[inline]
219    pub fn style(&self) -> Style {
220        self.style
221    }
222
223    /// Retrieves a borrow to the inner item.
224    ///
225    /// ```rust
226    /// use yansi::Paint;
227    ///
228    /// let x = Paint::red("Hello, world!");
229    /// assert_eq!(*x.inner(), "Hello, world!");
230    /// ```
231    #[inline]
232    pub fn inner(&self) -> &T {
233        &self.item
234    }
235
236    /// Sets the style of `self` to `style`.
237    ///
238    /// Any styling currently set on `self` is lost. Prefer to use the
239    /// [`style.paint()`](Style::paint()) method to create a `Paint` struct from
240    /// `Style`.
241    ///
242    /// ```rust
243    /// use yansi::{Paint, Color, Style};
244    ///
245    /// let s = Style::new(Color::Red).bold().underline();
246    ///
247    /// // Using this method.
248    /// println!("Alert: {}", Paint::new("This thing happened!").with_style(s));
249    ///
250    /// // Using the `style.paint()` method.
251    /// println!("Alert: {}", s.paint("This thing happened!"));
252    /// ```
253    #[inline]
254    pub fn with_style(mut self, style: Style) -> Paint<T> {
255        self.style = style;
256        self
257    }
258
259    /// Masks `self`.
260    ///
261    /// A masked `Paint` is not written out when painting is disabled during
262    /// `Display` or `Debug` invocations. When painting is enabled, masking has
263    /// no effect.
264    ///
265    /// ```rust
266    /// use yansi::Paint;
267    ///
268    /// // "Whoops! " will only print when coloring is enabled.
269    /// println!("{}Something happened.", Paint::red("Whoops! ").mask());
270    /// ```
271    #[inline]
272    pub fn mask(mut self) -> Paint<T> {
273        self.style.masked = true;
274        self
275    }
276
277    /// Makes `self` a _wrapping_ `Paint`.
278    ///
279    /// A wrapping `Paint` converts all color resets written out by the internal
280    /// value to the styling of itself. This allows for seamless color wrapping
281    /// of other colored text.
282    ///
283    /// # Performance
284    ///
285    /// In order to wrap an internal value, the internal value must first be
286    /// written out to a local buffer and examined. As a result, displaying a
287    /// wrapped value is likely to result in a heap allocation and copy.
288    ///
289    /// # Example
290    ///
291    /// ```rust
292    /// use yansi::{Paint, Color};
293    ///
294    /// let inner = format!("{} and {}", Paint::red("Stop"), Paint::green("Go"));
295    ///
296    /// // 'Hey!' will be unstyled, "Stop" will be red, "and" will be blue, and
297    /// // "Go" will be green. Without a wrapping `Paint`, "and" would be
298    /// // unstyled.
299    /// println!("Hey! {}", Paint::blue(inner).wrap());
300    /// ```
301    #[inline]
302    pub fn wrap(mut self) -> Paint<T> {
303        self.style.wrap = true;
304        self
305    }
306
307    /// Sets the foreground to `color`.
308    ///
309    /// ```rust
310    /// use yansi::Paint;
311    /// use yansi::Color::Red;
312    ///
313    /// println!("Red foreground: {}", Paint::new("hi!").fg(Red));
314    /// ```
315    #[inline]
316    pub fn fg(mut self, color: Color) -> Paint<T> {
317        self.style.foreground = color;
318        self
319    }
320
321    /// Sets the background to `color`.
322    ///
323    /// ```rust
324    /// use yansi::Paint;
325    /// use yansi::Color::Yellow;
326    ///
327    /// println!("Yellow background: {}", Paint::new("hi!").bg(Yellow));
328    /// ```
329    #[inline]
330    pub fn bg(mut self, color: Color) -> Paint<T> {
331        self.style.background = color;
332        self
333    }
334
335    style_builder_for!(Paint<T>, |paint| paint.style.properties,
336                       bold: BOLD, dimmed: DIMMED, italic: ITALIC,
337                       underline: UNDERLINE, blink: BLINK, invert: INVERT,
338                       hidden: HIDDEN, strikethrough: STRIKETHROUGH);
339}
340
341macro_rules! impl_fmt_trait {
342    ($trait:ident, $fmt:expr) => (
343        impl<T: fmt::$trait> fmt::$trait for Paint<T> {
344            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
345                if Paint::is_enabled() && self.style.wrap {
346                    let mut prefix = String::new();
347                    prefix.push_str("\x1B[0m");
348                    self.style.fmt_prefix(&mut prefix)?;
349
350                    self.style.fmt_prefix(f)?;
351                    let item = format!($fmt, self.item).replace("\x1B[0m", &prefix);
352                    fmt::$trait::fmt(&item, f)?;
353                    self.style.fmt_suffix(f)
354                } else if Paint::is_enabled() {
355                    self.style.fmt_prefix(f)?;
356                    fmt::$trait::fmt(&self.item, f)?;
357                    self.style.fmt_suffix(f)
358                } else if !self.style.masked {
359                    fmt::$trait::fmt(&self.item, f)
360                } else {
361                    Ok(())
362                }
363            }
364        }
365    )
366}
367
368impl_fmt_trait!(Display, "{}");
369impl_fmt_trait!(Debug, "{:?}");
370
371use std::sync::atomic::AtomicBool;
372use std::sync::atomic::Ordering;
373
374static ENABLED: AtomicBool = AtomicBool::new(true);
375
376impl Paint<()> {
377    /// Disables coloring globally.
378    ///
379    /// # Example
380    ///
381    /// ```rust
382    /// use yansi::Paint;
383    ///
384    /// // With coloring enabled, ANSI color codes are emitted.
385    /// assert_ne!(Paint::green("go").to_string(), "go".to_string());
386    ///
387    /// // With coloring disabled, ANSI color codes are _not_ emitted.
388    /// Paint::disable();
389    /// assert_eq!(Paint::green("go").to_string(), "go".to_string());
390    /// ```
391    pub fn disable() {
392        ENABLED.store(false, Ordering::Release);
393    }
394
395    /// Enables coloring globally. Coloring is enabled by default, so this
396    /// method should only be called to _re_ enable coloring.
397    ///
398    /// # Example
399    ///
400    /// ```rust
401    /// use yansi::Paint;
402    ///
403    /// // With coloring disabled, ANSI color codes are _not_ emitted.
404    /// Paint::disable();
405    /// assert_eq!(Paint::green("go").to_string(), "go".to_string());
406    ///
407    /// // Reenabling causes color code to be emitted.
408    /// Paint::enable();
409    /// assert_ne!(Paint::green("go").to_string(), "go".to_string());
410    /// ```
411    pub fn enable() {
412        ENABLED.store(true, Ordering::Release);
413    }
414
415    /// Returns `true` if coloring is enabled and `false` otherwise. Coloring is
416    /// enabled by default but can be enabled and disabled on-the-fly with the
417    /// [`Paint::enable()`] and [`Paint::disable()`] methods.
418    ///
419    /// [`Paint::disable()`]: struct.Paint.html#method.disable
420    /// [`Paint::enable()`]: struct.Paint.html#method.disable
421    ///
422    /// # Example
423    ///
424    /// ```rust
425    /// use yansi::Paint;
426    ///
427    /// // Coloring is enabled by default.
428    /// assert!(Paint::is_enabled());
429    ///
430    /// // Disable it with `Paint::disable()`.
431    /// Paint::disable();
432    /// assert!(!Paint::is_enabled());
433    ///
434    /// // Reenable with `Paint::enable()`.
435    /// Paint::enable();
436    /// assert!(Paint::is_enabled());
437    /// ```
438    pub fn is_enabled() -> bool {
439        ENABLED.load(Ordering::Acquire)
440    }
441
442    /// Enables ASCII terminal escape sequences on Windows consoles when
443    /// possible. Returns `true` if escape sequence support was successfully
444    /// enabled and `false` otherwise. On non-Windows targets, this method
445    /// always returns `true`.
446    ///
447    /// Support for escape sequences in Windows consoles was added in the
448    /// Windows 10 anniversary update. For targets with older Windows
449    /// installations, this method is expected to return `false`.
450    ///
451    /// # Example
452    ///
453    /// ```rust
454    /// use yansi::Paint;
455    ///
456    /// // A best-effort Windows ASCII terminal support enabling.
457    /// Paint::enable_windows_ascii();
458    /// ```
459    #[inline]
460    pub fn enable_windows_ascii() -> bool {
461        ::windows::enable_ascii_colors()
462    }
463}