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}