yansi_term/
style.rs

1/// A style is a collection of properties that can format a string
2/// using ANSI escape codes.
3///
4/// # Examples
5///
6/// ```
7/// use yansi_term::{Style, Colour};
8///
9/// let style = Style::new().bold().on(Colour::Black);
10/// println!("{}", style.paint("Bold on black"));
11/// ```
12#[derive(PartialEq, Clone, Copy, Default, Debug)]
13#[cfg_attr(
14    feature = "derive_serde_style",
15    derive(serde::Deserialize, serde::Serialize)
16)]
17pub struct Style {
18    /// The style's foreground colour, if it has one.
19    pub foreground: Option<Colour>,
20
21    /// The style's background colour, if it has one.
22    pub background: Option<Colour>,
23
24    /// Whether this style is bold.
25    pub is_bold: bool,
26
27    /// Whether this style is dimmed.
28    pub is_dimmed: bool,
29
30    /// Whether this style is italic.
31    pub is_italic: bool,
32
33    /// Whether this style is underlined.
34    pub is_underline: bool,
35
36    /// Whether this style is blinking.
37    pub is_blink: bool,
38
39    /// Whether this style has reverse colours.
40    pub is_reverse: bool,
41
42    /// Whether this style is hidden.
43    pub is_hidden: bool,
44
45    /// Whether this style is struckthrough.
46    pub is_strikethrough: bool,
47}
48
49impl Style {
50    /// Creates a new Style with no properties set.
51    ///
52    /// # Examples
53    ///
54    /// ```
55    /// use yansi_term::Style;
56    ///
57    /// let style = Style::new();
58    /// println!("{}", style.paint("hi"));
59    /// ```
60    pub fn new() -> Style {
61        Style::default()
62    }
63
64    /// Returns a `Style` with the bold property set.
65    ///
66    /// # Examples
67    ///
68    /// ```
69    /// use yansi_term::Style;
70    ///
71    /// let style = Style::new().bold();
72    /// println!("{}", style.paint("hey"));
73    /// ```
74    pub fn bold(mut self) -> Self {
75        self.is_bold = true;
76        self
77    }
78
79    /// Returns a `Style` with the dimmed property set.
80    ///
81    /// # Examples
82    ///
83    /// ```
84    /// use yansi_term::Style;
85    ///
86    /// let style = Style::new().dimmed();
87    /// println!("{}", style.paint("sup"));
88    /// ```
89    pub fn dimmed(mut self) -> Self {
90        self.is_dimmed = true;
91        self
92    }
93
94    /// Returns a `Style` with the italic property set.
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// use yansi_term::Style;
100    ///
101    /// let style = Style::new().italic();
102    /// println!("{}", style.paint("greetings"));
103    /// ```
104    pub fn italic(mut self) -> Self {
105        self.is_italic = true;
106        self
107    }
108
109    /// Returns a `Style` with the underline property set.
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use yansi_term::Style;
115    ///
116    /// let style = Style::new().underline();
117    /// println!("{}", style.paint("salutations"));
118    /// ```
119    pub fn underline(mut self) -> Self {
120        self.is_underline = true;
121        self
122    }
123
124    /// Returns a `Style` with the blink property set.
125    /// # Examples
126    ///
127    /// ```
128    /// use yansi_term::Style;
129    ///
130    /// let style = Style::new().blink();
131    /// println!("{}", style.paint("wazzup"));
132    /// ```
133    pub fn blink(mut self) -> Self {
134        self.is_blink = true;
135        self
136    }
137
138    /// Returns a `Style` with the reverse property set.
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use yansi_term::Style;
144    ///
145    /// let style = Style::new().reverse();
146    /// println!("{}", style.paint("aloha"));
147    /// ```
148    pub fn reverse(mut self) -> Self {
149        self.is_reverse = true;
150        self
151    }
152
153    /// Returns a `Style` with the hidden property set.
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use yansi_term::Style;
159    ///
160    /// let style = Style::new().hidden();
161    /// println!("{}", style.paint("ahoy"));
162    /// ```
163    pub fn hidden(mut self) -> Self {
164        self.is_hidden = true;
165        self
166    }
167
168    /// Returns a `Style` with the strikethrough property set.
169    ///
170    /// # Examples
171    ///
172    /// ```
173    /// use yansi_term::Style;
174    ///
175    /// let style = Style::new().strikethrough();
176    /// println!("{}", style.paint("yo"));
177    /// ```
178    pub fn strikethrough(mut self) -> Self {
179        self.is_strikethrough = true;
180        self
181    }
182
183    /// Returns a `Style` with the foreground colour property set.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use yansi_term::{Style, Colour};
189    ///
190    /// let style = Style::new().fg(Colour::Yellow);
191    /// println!("{}", style.paint("hi"));
192    /// ```
193    pub fn fg(mut self, foreground: Colour) -> Self {
194        self.foreground = Some(foreground);
195        self
196    }
197
198    /// Returns a `Style` with the background colour property set.
199    ///
200    /// # Examples
201    ///
202    /// ```
203    /// use yansi_term::{Style, Colour};
204    ///
205    /// let style = Style::new().on(Colour::Blue);
206    /// println!("{}", style.paint("eyyyy"));
207    /// ```
208    pub fn on(mut self, background: Colour) -> Self {
209        self.background = Some(background);
210        self
211    }
212
213    /// Return true if this `Style` has no actual styles, and can be written
214    /// without any control characters.
215    ///
216    /// # Examples
217    ///
218    /// ```
219    /// use yansi_term::Style;
220    ///
221    /// assert_eq!(true,  Style::default().is_plain());
222    /// assert_eq!(false, Style::default().bold().is_plain());
223    /// ```
224    pub fn is_plain(&self) -> bool {
225        *self == Style::default()
226    }
227}
228
229/// A colour is one specific type of ANSI escape code, and can refer
230/// to either the foreground or background colour.
231///
232/// These use the standard numeric sequences.
233/// See <http://invisible-island.net/xterm/ctlseqs/ctlseqs.html>
234#[derive(PartialEq, Clone, Copy, Debug)]
235#[cfg_attr(
236    feature = "derive_serde_style",
237    derive(serde::Deserialize, serde::Serialize)
238)]
239pub enum Colour {
240    /// Colour #0 (foreground code `30`, background code `40`).
241    ///
242    /// This is not necessarily the background colour, and using it as one may
243    /// render the text hard to read on terminals with dark backgrounds.
244    Black,
245
246    /// Colour #1 (foreground code `31`, background code `41`).
247    Red,
248
249    /// Colour #2 (foreground code `32`, background code `42`).
250    Green,
251
252    /// Colour #3 (foreground code `33`, background code `43`).
253    Yellow,
254
255    /// Colour #4 (foreground code `34`, background code `44`).
256    Blue,
257
258    /// Colour #5 (foreground code `35`, background code `45`).
259    Purple,
260
261    /// Colour #6 (foreground code `36`, background code `46`).
262    Cyan,
263
264    /// Colour #7 (foreground code `37`, background code `47`).
265    ///
266    /// As above, this is not necessarily the foreground colour, and may be
267    /// hard to read on terminals with light backgrounds.
268    White,
269
270    /// A colour number from 0 to 255, for use in 256-colour terminal
271    /// environments.
272    ///
273    /// - Colours 0 to 7 are the `Black` to `White` variants respectively.
274    ///   These colours can usually be changed in the terminal emulator.
275    /// - Colours 8 to 15 are brighter versions of the eight colours above.
276    ///   These can also usually be changed in the terminal emulator, or it
277    ///   could be configured to use the original colours and show the text in
278    ///   bold instead. It varies depending on the program.
279    /// - Colours 16 to 231 contain several palettes of bright colours,
280    ///   arranged in six squares measuring six by six each.
281    /// - Colours 232 to 255 are shades of grey from black to white.
282    ///
283    /// It might make more sense to look at a [colour chart][cc].
284    ///
285    /// [cc]: https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg
286    Fixed(u8),
287
288    /// A 24-bit RGB color, as specified by ISO-8613-3.
289    RGB(u8, u8, u8),
290}
291
292impl Colour {
293    /// Returns a `Style` with the foreground colour set to this colour.
294    ///
295    /// # Examples
296    ///
297    /// ```
298    /// use yansi_term::Colour;
299    ///
300    /// let style = Colour::Red.normal();
301    /// println!("{}", style.paint("hi"));
302    /// ```
303    pub fn normal(self) -> Style {
304        Style {
305            foreground: Some(self),
306            ..Style::default()
307        }
308    }
309
310    /// Returns a `Style` with the foreground colour set to this colour and the
311    /// bold property set.
312    ///
313    /// # Examples
314    ///
315    /// ```
316    /// use yansi_term::Colour;
317    ///
318    /// let style = Colour::Green.bold();
319    /// println!("{}", style.paint("hey"));
320    /// ```
321    pub fn bold(self) -> Style {
322        Style {
323            foreground: Some(self),
324            is_bold: true,
325            ..Style::default()
326        }
327    }
328
329    /// Returns a `Style` with the foreground colour set to this colour and the
330    /// dimmed property set.
331    ///
332    /// # Examples
333    ///
334    /// ```
335    /// use yansi_term::Colour;
336    ///
337    /// let style = Colour::Yellow.dimmed();
338    /// println!("{}", style.paint("sup"));
339    /// ```
340    pub fn dimmed(self) -> Style {
341        Style {
342            foreground: Some(self),
343            is_dimmed: true,
344            ..Style::default()
345        }
346    }
347
348    /// Returns a `Style` with the foreground colour set to this colour and the
349    /// italic property set.
350    ///
351    /// # Examples
352    ///
353    /// ```
354    /// use yansi_term::Colour;
355    ///
356    /// let style = Colour::Blue.italic();
357    /// println!("{}", style.paint("greetings"));
358    /// ```
359    pub fn italic(self) -> Style {
360        Style {
361            foreground: Some(self),
362            is_italic: true,
363            ..Style::default()
364        }
365    }
366
367    /// Returns a `Style` with the foreground colour set to this colour and the
368    /// underline property set.
369    ///
370    /// # Examples
371    ///
372    /// ```
373    /// use yansi_term::Colour;
374    ///
375    /// let style = Colour::Purple.underline();
376    /// println!("{}", style.paint("salutations"));
377    /// ```
378    pub fn underline(self) -> Style {
379        Style {
380            foreground: Some(self),
381            is_underline: true,
382            ..Style::default()
383        }
384    }
385
386    /// Returns a `Style` with the foreground colour set to this colour and the
387    /// blink property set.
388    ///
389    /// # Examples
390    ///
391    /// ```
392    /// use yansi_term::Colour;
393    ///
394    /// let style = Colour::Cyan.blink();
395    /// println!("{}", style.paint("wazzup"));
396    /// ```
397    pub fn blink(self) -> Style {
398        Style {
399            foreground: Some(self),
400            is_blink: true,
401            ..Style::default()
402        }
403    }
404
405    /// Returns a `Style` with the foreground colour set to this colour and the
406    /// reverse property set.
407    ///
408    /// # Examples
409    ///
410    /// ```
411    /// use yansi_term::Colour;
412    ///
413    /// let style = Colour::Black.reverse();
414    /// println!("{}", style.paint("aloha"));
415    /// ```
416    pub fn reverse(self) -> Style {
417        Style {
418            foreground: Some(self),
419            is_reverse: true,
420            ..Style::default()
421        }
422    }
423
424    /// Returns a `Style` with the foreground colour set to this colour and the
425    /// hidden property set.
426    ///
427    /// # Examples
428    ///
429    /// ```
430    /// use yansi_term::Colour;
431    ///
432    /// let style = Colour::White.hidden();
433    /// println!("{}", style.paint("ahoy"));
434    /// ```
435    pub fn hidden(self) -> Style {
436        Style {
437            foreground: Some(self),
438            is_hidden: true,
439            ..Style::default()
440        }
441    }
442
443    /// Returns a `Style` with the foreground colour set to this colour and the
444    /// strikethrough property set.
445    ///
446    /// # Examples
447    ///
448    /// ```
449    /// use yansi_term::Colour;
450    ///
451    /// let style = Colour::Fixed(244).strikethrough();
452    /// println!("{}", style.paint("yo"));
453    /// ```
454    pub fn strikethrough(self) -> Style {
455        Style {
456            foreground: Some(self),
457            is_strikethrough: true,
458            ..Style::default()
459        }
460    }
461
462    /// Returns a `Style` with the foreground colour set to this colour and the
463    /// background colour property set to the given colour.
464    ///
465    /// # Examples
466    ///
467    /// ```
468    /// use yansi_term::Colour;
469    ///
470    /// let style = Colour::RGB(31, 31, 31).on(Colour::White);
471    /// println!("{}", style.paint("eyyyy"));
472    /// ```
473    pub fn on(self, background: Colour) -> Style {
474        Style {
475            foreground: Some(self),
476            background: Some(background),
477            ..Style::default()
478        }
479    }
480}
481
482impl From<Colour> for Style {
483    /// You can turn a `Colour` into a `Style` with the foreground colour set
484    /// with the `From` trait.
485    ///
486    /// ```
487    /// use yansi_term::{Style, Colour};
488    /// let green_foreground = Style::default().fg(Colour::Green);
489    /// assert_eq!(green_foreground, Colour::Green.normal());
490    /// assert_eq!(green_foreground, Colour::Green.into());
491    /// assert_eq!(green_foreground, Style::from(Colour::Green));
492    /// ```
493    fn from(colour: Colour) -> Style {
494        colour.normal()
495    }
496}
497
498#[cfg(test)]
499#[cfg(feature = "derive_serde_style")]
500mod serde_json_tests {
501    use super::{Colour, Style};
502
503    #[test]
504    fn colour_serialization() {
505        let colours = &[
506            Colour::Red,
507            Colour::Blue,
508            Colour::RGB(123, 123, 123),
509            Colour::Fixed(255),
510        ];
511
512        assert_eq!(
513            serde_json::to_string(&colours).unwrap(),
514            String::from("[\"Red\",\"Blue\",{\"RGB\":[123,123,123]},{\"Fixed\":255}]")
515        );
516    }
517
518    #[test]
519    fn colour_deserialization() {
520        let colours = &[
521            Colour::Red,
522            Colour::Blue,
523            Colour::RGB(123, 123, 123),
524            Colour::Fixed(255),
525        ];
526
527        for colour in colours {
528            let serialized = serde_json::to_string(&colour).unwrap();
529            let deserialized: Colour = serde_json::from_str(&serialized).unwrap();
530
531            assert_eq!(colour, &deserialized);
532        }
533    }
534
535    #[test]
536    fn style_serialization() {
537        let style = Style::default();
538
539        assert_eq!(serde_json::to_string(&style).unwrap(), "{\"foreground\":null,\"background\":null,\"is_bold\":false,\"is_dimmed\":false,\"is_italic\":false,\"is_underline\":false,\"is_blink\":false,\"is_reverse\":false,\"is_hidden\":false,\"is_strikethrough\":false}".to_string());
540    }
541}