1 // This Source Code Form is subject to the terms of the Mozilla Public
2 // License, v. 2.0. If a copy of the MPL was not distributed with this
3 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
5 //! A module for handling terminal output styling.
7 //! It would be fair to say that this module ~~is a rip off of~~ is *heavily*
8 //! influenced by [ogham][ogham]'s [ansi_term][ansiterm] crate and
9 //! [peter-murach][peter]'s [Pastel][pastel] library. However if there is
10 //! something original of mine in here it would be true-colour support at the
19 //! Most other POSIX/*nix systems will probably work as well.
21 //! Windows support is planned.
25 //! The two main data structures in this module are `Style`, which holds
26 //! stylistic information such as the foreground colour, the background colour
27 //! and other properties such as if the text should be bold, blinking, etc. and
28 //! `ANSIString`, which is string paired with a `Style`.
30 //! In order to format a string, call the `paint()` method on a `Style` or
31 //! `Color`. For example, here is how you get red text and blue text:
34 //! use tutil::crayon::Color::{Red, Blue};
36 //! println!("{}", Red.paint("Hello world in red!"));
37 //! println!("{}", Blue.paint("Hello world in blue!"));
40 //! It is worth noting that `paint()` does not actually return a string with the
41 //! escape codes surrounding it, but instead returns a `StyledString` that has
42 //! an implementation of `Display` that will return the escape codes as well as
43 //! the string when formatted.
45 //! In the case that you *do* want the escape codes, you can convert the
46 //! `StyledString` to a string:
49 //! use std::string::ToString;
50 //! use tutil::crayon::Color::Blue;
52 //! let string = Blue.paint("Hello!").to_string(); // => "\x1b[34mTEST\x1b[0m"
57 //! For complex styling you can construct a `Style` object rather than operating
58 //! directly on a `Color`:
61 //! use tutil::crayon::Style;
62 //! use tutil::crayon::Color::{Red, Blue};
64 //! // Red blinking text on a black background:
65 //! println!("This will be {} and this will be {}.",
66 //! Style::new().foreground(Red).bold().paint("red and bold"),
67 //! Style::new().foreground(Blue).italic().paint("blue and italic"));
70 //! The same styling methods that you can use on a `Style` have been implemented
71 //! on the `Color` struct, allowing you to skip creating an empty `Style` with
75 //! use tutil::crayon::Color::{Red, Blue};
77 //! // Red blinking text on a black background:
78 //! println!("This will be {} and this will be {}.",
79 //! Red.bold().paint("red and bold"),
80 //! Blue.italic().paint("blue and italic"));
83 //! [ogham]: https://github.com/ogham
84 //! [ansiterm]: https://github.com/ogham/rust-ansi-term
85 //! [peter]: https://github.com/peter-murach
86 //! [pastel]: https://github.com/peter-murach/pastel
91 use std::default::Default;
95 /// A string coupled with a `Style` in order to display it in a terminal.
97 /// It can be turned into a string with the `.to_string()` method.
98 #[derive(Debug, Clone, PartialEq)]
99 pub struct StyledString<'a> {
100 string: Cow<'a, str>,
104 impl<'a> fmt::Display for StyledString<'a> {
105 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
106 // TODO: Convert the `try!()` calls to the `?` operator once it is
108 try!(self.style.write_prefix(f));
109 try!(write!(f, "{}", self.string));
110 self.style.write_suffix(f)
114 impl<'a, S> From<S> for StyledString<'a>
115 where S: Into<Cow<'a, str>>
117 fn from(string: S) -> StyledString<'a> {
118 StyledString { string: string.into(), style: Style::default() }
122 impl<'a> Deref for StyledString<'a> {
125 fn deref(&self) -> &str {
130 /// A `Color` is a specific ANSI colour name which can refer to either the
131 /// foreground or background.
133 /// It can also be a custom value from 0 to 255 via the `Fixed(u8)` variant for
134 /// terminals that have 256-colour. True-colour is also supported via the
135 /// `Rgb(u8, u8, u8)` variant.
137 /// For further reading visit this [Wikipedia page][wp].
139 /// [wp]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
140 #[derive(Debug, Clone, Copy, PartialEq)]
142 /// Foreground code `30`, background code `40`.
145 /// Foreground code `31`, background code `41`.
148 /// Foreground code `32`, background code `42`.
151 /// Foreground code `33`, background code `43`.
154 /// Foreground code `34`, background code `44`.
157 /// Foreground code `35`, background code `45`.
160 /// Foreground code `36`, background code `46`.
163 /// Foreground code `37`, background code `47`.
166 /// A value from 0 to 255 for use on terminals that have 256-colour support.
168 /// * 0 to 7 are the `Black` through `White` variants. These colours can
169 /// usually be changed in the terminal emulator.
170 /// * 8 to 15 are brighter versions of the aforementioned colours. These
171 /// colours can also usually be changed in the terminal emulator,
172 /// however, it also could be configured to use the original colours and
173 /// show the text in bold.
174 /// * 16 to 231 contain several palettes of bright colours, arranged in six
175 /// squares measuring six by six each.
176 /// * 232 to 255 are shades of grey from black to white.
178 /// You can view this [colour chart][cc] for a visual representation.
179 /// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
182 /// A red value, green value and blue value, each between 0 to 255 (the RGB
183 /// colour model) for use on terminals that have true-colour support.
185 /// Please note that true-colour support on many terminal emulators is
186 /// patchy, however a fair share of the most common terminal emulators
187 /// support it, such as:
189 /// * [Gnome Terminal][gterminal]
190 /// * [Konsole][konsole]
191 /// * [Terminator][terminator]
192 /// * [iTerm 2][iterm2]
194 /// For an up-to-date list of true-colour support visit:
195 /// https://gist.github.com/XVilka/8346728.
197 /// [gterminal]: https://wiki.gnome.org/Apps/Terminal
198 /// [konsole]: https://konsole.kde.org/
199 /// [terminator]: http://gnometerminator.blogspot.co.nz
200 /// [iterm2]: https://www.iterm2.com/
205 /// Convenience method for creating a `StyledString` with the foreground set
206 /// without having to manually create a `Style` or use `<color>.normal().paint()`.
207 pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
208 where S: Into<Cow<'a, str>>
210 StyledString { string: string.into(), style: self.normal() }
213 /// Returns a `Style` with the foreground colour set to this colour.
214 pub fn normal(self) -> Style {
215 Style { foreground: Some(self), ..Style::default() }
218 /// The same as `Color::normal()`, but also sets the background colour.
219 pub fn on(self, background: Color) -> Style {
220 Style { foreground: Some(self), background: Some(background), ..Style::default() }
223 /// Returns a `Style` with the 'bold' property set and the foreground colour
224 /// set to this colour.
225 pub fn bold(self) -> Style {
226 Style { foreground: Some(self), bold: true, ..Style::default() }
229 /// Returns a `Style` with the 'dimmed' property set and the foreground
230 /// colour set to this colour.
231 pub fn dimmed(self) -> Style {
232 Style { foreground: Some(self), dimmed: true, ..Style::default() }
235 /// Returns a `Style` with the 'italic' property set and the foreground
236 /// colour set to this colour.
237 pub fn italic(self) -> Style {
238 Style { foreground: Some(self), italic: true, ..Style::default() }
241 /// Returns a `Style` with the 'underline' property set and the foreground
242 /// colour set to this colour.
243 pub fn underline(self) -> Style {
244 Style { foreground: Some(self), underline: true, ..Style::default() }
247 /// Returns a `Style` with the 'blink' property set and the foreground
248 /// colour set to this colour.
249 pub fn blink(self) -> Style {
250 Style { foreground: Some(self), blink: true, ..Style::default() }
253 /// Returns a `Style` with the 'reverse' property set and the foreground
254 /// colour set to this colour.
255 pub fn reverse(self) -> Style {
256 Style { foreground: Some(self), reverse: true, ..Style::default() }
259 /// Returns a `Style` with the 'hidden' property set and the foreground
260 /// colour set to this colour.
261 pub fn hidden(self) -> Style {
262 Style { foreground: Some(self), hidden: true, ..Style::default() }
265 fn write_foreground_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
267 Black => write!(f, "30"),
268 Red => write!(f, "31"),
269 Green => write!(f, "32"),
270 Yellow => write!(f, "33"),
271 Blue => write!(f, "34"),
272 Purple => write!(f, "35"),
273 Cyan => write!(f, "36"),
274 White => write!(f, "37"),
275 Fixed(n) => write!(f, "38;5;{}", &n),
276 Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
280 fn write_background_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
282 Black => write!(f, "40"),
283 Red => write!(f, "41"),
284 Green => write!(f, "42"),
285 Yellow => write!(f, "43"),
286 Blue => write!(f, "44"),
287 Purple => write!(f, "45"),
288 Cyan => write!(f, "46"),
289 White => write!(f, "47"),
290 Fixed(n) => write!(f, "48;5;{}", &n),
291 Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
296 /// A collection of properties that are used to format a string.
297 #[derive(Debug, Clone, Copy, PartialEq)]
299 foreground: Option<Color>,
300 background: Option<Color>,
311 /// Creates a new `Style` without any formatting.
312 pub fn new() -> Style {
316 /// Applies the `Style` to a string, yielding a `StyledString`.
317 pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
318 where S: Into<Cow<'a, str>>
320 StyledString { string: string.into(), style: self }
323 /// Sets the foreground to the given colour.
324 pub fn foreground(&self, color: Color) -> Style {
325 Style { foreground: Some(color), ..*self }
328 /// Sets the background to the given colour.
329 pub fn background(&self, color: Color) -> Style {
330 Style { background: Some(color), ..*self }
333 /// Applies the 'bold' property.
334 pub fn bold(&self) -> Style {
335 Style { bold: true, ..*self }
338 /// Applies the 'dimmed' property.
339 pub fn dimmed(&self) -> Style {
340 Style { dimmed: true, ..*self }
343 /// Applies the 'italic' property.
344 pub fn italic(&self) -> Style {
345 Style { italic: true, ..*self }
348 /// Applies the 'underline' property.
349 pub fn underline(&self) -> Style {
350 Style { underline: true, ..*self }
353 /// Applies the 'blink' property.
354 pub fn blink(&self) -> Style {
355 Style { blink: true, ..*self }
358 /// Applies the 'reverse' property.
359 pub fn reverse(&self) -> Style {
360 Style { reverse: true, ..*self }
363 /// Applies the 'hidden' property.
364 pub fn hidden(&self) -> Style {
365 Style { hidden: true, ..*self }
368 /// Returns true if this `Style` has no colours or properties set.
369 fn is_plain(self) -> bool {
370 self == Style::default()
373 /// Write any ANSI escape codes that go before the given text, such as
374 /// colour or style codes.
375 fn write_prefix(&self, f: &mut fmt::Formatter) -> fmt::Result {
382 try!(write!(f, "\x1b["));
383 let mut written_anything = false;
386 let mut write_char = |c| {
387 if written_anything {
388 try!(f.write_char(';'));
390 written_anything = true;
391 try!(f.write_char(c));
395 if self.bold { try!(write_char('1')); }
396 if self.dimmed { try!(write_char('2')); }
397 if self.italic { try!(write_char('3')); }
398 if self.underline { try!(write_char('4')); }
399 if self.blink { try!(write_char('5')); }
400 if self.reverse { try!(write_char('6')); }
401 if self.hidden { try!(write_char('7')); }
404 if let Some(fg) = self.foreground {
405 if written_anything { try!(f.write_char(';')); }
406 written_anything = true;
408 try!(fg.write_foreground_code(f));
411 if let Some(bg) = self.background {
412 if written_anything { try!(f.write_char(';')); }
414 try!(bg.write_background_code(f));
417 try!(f.write_char('m'));
421 /// Write any ANSI escape codes that go after the given text, typically the
423 fn write_suffix(&self, f: &mut fmt::Formatter) -> fmt::Result {
432 impl Default for Style {
433 fn default() -> Style {
453 // Convenience macro for creating test cases.
455 ($name: ident: $style: expr; $input: expr => $result: expr) => {
458 assert_eq!($style.paint($input).to_string(), $result.to_string())
463 test!(plain: Style::default(); "TEST" => "TEST");
464 test!(black: Black; "TEST" => "\x1b[30mTEST\x1b[0m");
465 test!(black_background: White.on(Black); "TEST" => "\x1b[37;40mTEST\x1b[0m");
466 test!(red: Red; "TEST" => "\x1b[31mTEST\x1b[0m");
467 test!(red_background: Black.on(Red); "TEST" => "\x1b[30;41mTEST\x1b[0m");
468 test!(green: Green; "TEST" => "\x1b[32mTEST\x1b[0m");
469 test!(green_background: Black.on(Green); "TEST" => "\x1b[30;42mTEST\x1b[0m");
470 test!(yellow: Yellow; "TEST" => "\x1b[33mTEST\x1b[0m");
471 test!(yellow_background: Black.on(Yellow); "TEST" => "\x1b[30;43mTEST\x1b[0m");
472 test!(blue: Blue; "TEST" => "\x1b[34mTEST\x1b[0m");
473 test!(blue_background: Black.on(Blue); "TEST" => "\x1b[30;44mTEST\x1b[0m");
474 test!(purple: Purple; "TEST" => "\x1b[35mTEST\x1b[0m");
475 test!(purple_background: Black.on(Purple); "TEST" => "\x1b[30;45mTEST\x1b[0m");
476 test!(cyan: Cyan; "TEST" => "\x1b[36mTEST\x1b[0m");
477 test!(cyan_background: Black.on(Cyan); "TEST" => "\x1b[30;46mTEST\x1b[0m");
478 test!(white: White; "TEST" => "\x1b[37mTEST\x1b[0m");
479 test!(white_background: Black.on(White); "TEST" => "\x1b[30;47mTEST\x1b[0m");
480 test!(fixed: Fixed(220); "TEST" => "\x1b[38;5;220mTEST\x1b[0m");
481 test!(fixed_background: Fixed(220).on(Fixed(245));
482 "TEST" => "\x1b[38;5;220;48;5;245mTEST\x1b[0m");
483 test!(rgb: Rgb(105, 245, 238);
484 "TEST" => "\x1b[38;2;105;245;238mTEST\x1b[0m");
485 test!(rgb_background: Black.on(Rgb(100, 245, 238));
486 "TEST" => "\x1b[30;48;2;100;245;238mTEST\x1b[0m");
488 test!(bold: Style::new().bold(); "TEST" => "\x1b[1mTEST\x1b[0m");
489 test!(dimmed: Style::new().dimmed(); "TEST" => "\x1b[2mTEST\x1b[0m");
490 test!(italic: Style::new().italic(); "TEST" => "\x1b[3mTEST\x1b[0m");
491 test!(underline: Style::new().underline(); "TEST" => "\x1b[4mTEST\x1b[0m");
492 test!(blink: Style::new().blink(); "TEST" => "\x1b[5mTEST\x1b[0m");
493 test!(reverse: Style::new().reverse(); "TEST" => "\x1b[6mTEST\x1b[0m");
494 test!(hidden: Style::new().hidden(); "TEST" => "\x1b[7mTEST\x1b[0m");