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>> {
116 fn from(string: S) -> StyledString<'a> {
118 string: string.into(),
119 style: Style::default(),
124 impl<'a> Deref for StyledString<'a> {
127 fn deref(&self) -> &str {
132 /// A `Color` is a specific ANSI colour name which can refer to either the
133 /// foreground or background.
135 /// It can also be a custom value from 0 to 255 via the `Fixed(u8)` variant for
136 /// terminals that have 256-colour. True-colour is also supported via the
137 /// `Rgb(u8, u8, u8)` variant.
139 /// For further reading visit this [Wikipedia page][wp].
141 /// [wp]: https://en.wikipedia.org/wiki/ANSI_escape_code#Colors
142 #[derive(Debug, Clone, Copy, PartialEq)]
144 /// Foreground code `30`, background code `40`.
147 /// Foreground code `31`, background code `41`.
150 /// Foreground code `32`, background code `42`.
153 /// Foreground code `33`, background code `43`.
156 /// Foreground code `34`, background code `44`.
159 /// Foreground code `35`, background code `45`.
162 /// Foreground code `36`, background code `46`.
165 /// Foreground code `37`, background code `47`.
168 /// A value from 0 to 255 for use on terminals that have 256-colour support.
170 /// * 0 to 7 are the `Black` through `White` variants. These colours can
171 /// usually be changed in the terminal emulator.
172 /// * 8 to 15 are brighter versions of the aforementioned colours. These
173 /// colours can also usually be changed in the terminal emulator,
174 /// however, it also could be configured to use the original colours and
175 /// show the text in bold.
176 /// * 16 to 231 contain several palettes of bright colours, arranged in six
177 /// squares measuring six by six each.
178 /// * 232 to 255 are shades of grey from black to white.
180 /// You can view this [colour chart][cc] for a visual representation.
181 /// [cc]: https://upload.wikimedia.org/wikipedia/en/1/15/Xterm_256color_chart.svg
184 /// A red value, green value and blue value, each between 0 to 255 (the RGB
185 /// colour model) for use on terminals that have true-colour support.
187 /// Please note that true-colour support on many terminal emulators is
188 /// patchy, however a fair share of the most common terminal emulators
189 /// support it, such as:
191 /// * [Gnome Terminal][gterminal]
192 /// * [Konsole][konsole]
193 /// * [Terminator][terminator]
194 /// * [iTerm 2][iterm2]
196 /// For an up-to-date list of true-colour support visit:
197 /// https://gist.github.com/XVilka/8346728.
199 /// [gterminal]: https://wiki.gnome.org/Apps/Terminal
200 /// [konsole]: https://konsole.kde.org/
201 /// [terminator]: http://gnometerminator.blogspot.co.nz
202 /// [iterm2]: https://www.iterm2.com/
207 /// Convenience method for creating a `StyledString` with the foreground set
208 /// without having to manually create a `Style` or use `<color>.normal().paint()`.
209 pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
210 where S: Into<Cow<'a, str>> {
212 string: string.into(),
213 style: self.normal(),
217 /// Returns a `Style` with the foreground colour set to this colour.
218 pub fn normal(self) -> Style {
219 Style { foreground: Some(self), .. Style::default() }
222 /// The same as `Color::normal()`, but also sets the background colour.
223 pub fn on(self, background: Color) -> Style {
224 Style { foreground: Some(self), background: Some(background), .. Style::default() }
227 /// Returns a `Style` with the 'bold' property set and the foreground colour
228 /// set to this colour.
229 pub fn bold(self) -> Style {
230 Style { foreground: Some(self), bold: true, .. Style::default() }
233 /// Returns a `Style` with the 'dimmed' property set and the foreground
234 /// colour set to this colour.
235 pub fn dimmed(self) -> Style {
236 Style { foreground: Some(self), dimmed: true, .. Style::default() }
239 /// Returns a `Style` with the 'italic' property set and the foreground
240 /// colour set to this colour.
241 pub fn italic(self) -> Style {
242 Style { foreground: Some(self), italic: true, .. Style::default() }
245 /// Returns a `Style` with the 'underline' property set and the foreground
246 /// colour set to this colour.
247 pub fn underline(self) -> Style {
248 Style { foreground: Some(self), underline: true, .. Style::default() }
251 /// Returns a `Style` with the 'blink' property set and the foreground
252 /// colour set to this colour.
253 pub fn blink(self) -> Style {
254 Style { foreground: Some(self), blink: true, .. Style::default() }
257 /// Returns a `Style` with the 'reverse' property set and the foreground
258 /// colour set to this colour.
259 pub fn reverse(self) -> Style {
260 Style { foreground: Some(self), reverse: true, .. Style::default() }
263 /// Returns a `Style` with the 'hidden' property set and the foreground
264 /// colour set to this colour.
265 pub fn hidden(self) -> Style {
266 Style { foreground: Some(self), hidden: true, .. Style::default() }
269 fn write_foreground_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
271 Black => write!(f, "30"),
272 Red => write!(f, "31"),
273 Green => write!(f, "32"),
274 Yellow => write!(f, "33"),
275 Blue => write!(f, "34"),
276 Purple => write!(f, "35"),
277 Cyan => write!(f, "36"),
278 White => write!(f, "37"),
279 Fixed(n) => write!(f, "38;5;{}", &n),
280 Rgb(r, g, b) => write!(f, "38;2;{};{};{}", &r, &g, &b),
284 fn write_background_code(&self, f: &mut fmt::Formatter) -> fmt::Result {
286 Black => write!(f, "40"),
287 Red => write!(f, "41"),
288 Green => write!(f, "42"),
289 Yellow => write!(f, "43"),
290 Blue => write!(f, "44"),
291 Purple => write!(f, "45"),
292 Cyan => write!(f, "46"),
293 White => write!(f, "47"),
294 Fixed(n) => write!(f, "48;5;{}", &n),
295 Rgb(r, g, b) => write!(f, "48;2;{};{};{}", &r, &g, &b),
300 /// A collection of properties that are used to format a string.
301 #[derive(Debug, Clone, Copy, PartialEq)]
303 foreground: Option<Color>,
304 background: Option<Color>,
315 /// Creates a new `Style` without any formatting.
316 pub fn new() -> Style {
320 /// Applies the `Style` to a string, yielding a `StyledString`.
321 pub fn paint<'a, S>(self, string: S) -> StyledString<'a>
322 where S: Into<Cow<'a, str>> {
323 StyledString { string: string.into(), style: self }
326 /// Sets the foreground to the given colour.
327 pub fn foreground(&self, color: Color) -> Style {
328 Style { foreground: Some(color), .. *self }
331 /// Sets the background to the given colour.
332 pub fn background(&self, color: Color) -> Style {
333 Style { background: Some(color), .. *self }
336 /// Applies the 'bold' property.
337 pub fn bold(&self) -> Style {
338 Style { bold: true, .. *self }
341 /// Applies the 'dimmed' property.
342 pub fn dimmed(&self) -> Style {
343 Style { dimmed: true, .. *self }
346 /// Applies the 'italic' property.
347 pub fn italic(&self) -> Style {
348 Style { italic: true, .. *self }
351 /// Applies the 'underline' property.
352 pub fn underline(&self) -> Style {
353 Style { underline: true, .. *self }
356 /// Applies the 'blink' property.
357 pub fn blink(&self) -> Style {
358 Style { blink: true, .. *self }
361 /// Applies the 'reverse' property.
362 pub fn reverse(&self) -> Style {
363 Style { reverse: true, .. *self }
366 /// Applies the 'hidden' property.
367 pub fn hidden(&self) -> Style {
368 Style { hidden: true, .. *self }
371 /// Returns true if this `Style` has no colours or properties set.
372 fn is_plain(self) -> bool {
373 self == Style::default()
376 /// Write any ANSI escape codes that go before the given text, such as
377 /// colour or style codes.
378 fn write_prefix(&self, f: &mut fmt::Formatter) -> fmt::Result {
381 if self.is_plain() { return Ok(()); }
383 try!(write!(f, "\x1b["));
384 let mut written_anything = false;
387 let mut write_char = |c| {
388 if written_anything { try!(f.write_char(';')); }
389 written_anything = true;
390 try!(f.write_char(c));
394 if self.bold { try!(write_char('1')); }
395 if self.dimmed { try!(write_char('2')); }
396 if self.italic { try!(write_char('3')); }
397 if self.underline { try!(write_char('4')); }
398 if self.blink { try!(write_char('5')); }
399 if self.reverse { try!(write_char('6')); }
400 if self.hidden { try!(write_char('7')); }
403 if let Some(fg) = self.foreground {
404 if written_anything { try!(f.write_char(';')); }
405 written_anything = true;
407 try!(fg.write_foreground_code(f));
410 if let Some(bg) = self.background {
411 if written_anything { try!(f.write_char(';')); }
413 try!(bg.write_background_code(f));
416 try!(f.write_char('m'));
420 /// Write any ANSI escape codes that go after the given text, typically the
422 fn write_suffix(&self, f: &mut fmt::Formatter) -> fmt::Result {
431 impl Default for Style {
432 fn default() -> Style {
452 // Convenience macro for creating test cases.
454 ($name: ident: $style: expr; $input: expr => $result: expr) => {
457 assert_eq!($style.paint($input).to_string(), $result.to_string())
462 test!(plain: Style::default(); "TEST" => "TEST");
463 test!(black: Black; "TEST" => "\x1b[30mTEST\x1b[0m");
464 test!(black_background: White.on(Black); "TEST" => "\x1b[37;40mTEST\x1b[0m");
465 test!(red: Red; "TEST" => "\x1b[31mTEST\x1b[0m");
466 test!(red_background: Black.on(Red); "TEST" => "\x1b[30;41mTEST\x1b[0m");
467 test!(green: Green; "TEST" => "\x1b[32mTEST\x1b[0m");
468 test!(green_background: Black.on(Green); "TEST" => "\x1b[30;42mTEST\x1b[0m");
469 test!(yellow: Yellow; "TEST" => "\x1b[33mTEST\x1b[0m");
470 test!(yellow_background: Black.on(Yellow); "TEST" => "\x1b[30;43mTEST\x1b[0m");
471 test!(blue: Blue; "TEST" => "\x1b[34mTEST\x1b[0m");
472 test!(blue_background: Black.on(Blue); "TEST" => "\x1b[30;44mTEST\x1b[0m");
473 test!(purple: Purple; "TEST" => "\x1b[35mTEST\x1b[0m");
474 test!(purple_background: Black.on(Purple); "TEST" => "\x1b[30;45mTEST\x1b[0m");
475 test!(cyan: Cyan; "TEST" => "\x1b[36mTEST\x1b[0m");
476 test!(cyan_background: Black.on(Cyan); "TEST" => "\x1b[30;46mTEST\x1b[0m");
477 test!(white: White; "TEST" => "\x1b[37mTEST\x1b[0m");
478 test!(white_background: Black.on(White); "TEST" => "\x1b[30;47mTEST\x1b[0m");
479 test!(fixed: Fixed(220); "TEST" => "\x1b[38;5;220mTEST\x1b[0m");
480 test!(fixed_background: Fixed(220).on(Fixed(245));
481 "TEST" => "\x1b[38;5;220;48;5;245mTEST\x1b[0m");
482 test!(rgb: Rgb(105, 245, 238);
483 "TEST" => "\x1b[38;2;105;245;238mTEST\x1b[0m");
484 test!(rgb_background: Black.on(Rgb(100, 245, 238));
485 "TEST" => "\x1b[30;48;2;100;245;238mTEST\x1b[0m");
487 test!(bold: Style::new().bold(); "TEST" => "\x1b[1mTEST\x1b[0m");
488 test!(dimmed: Style::new().dimmed(); "TEST" => "\x1b[2mTEST\x1b[0m");
489 test!(italic: Style::new().italic(); "TEST" => "\x1b[3mTEST\x1b[0m");
490 test!(underline: Style::new().underline(); "TEST" => "\x1b[4mTEST\x1b[0m");
491 test!(blink: Style::new().blink(); "TEST" => "\x1b[5mTEST\x1b[0m");
492 test!(reverse: Style::new().reverse(); "TEST" => "\x1b[6mTEST\x1b[0m");
493 test!(hidden: Style::new().hidden(); "TEST" => "\x1b[7mTEST\x1b[0m");