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