use style::{Colour, Style}; | |
use std::fmt; | |
use write::AnyWrite; | |
// ---- generating ANSI codes ---- | |
impl Style { | |
/// Write any ANSI codes that go *before* a piece of text. These should be | |
/// the codes to set the terminal to a different colour or font style. | |
fn write_prefix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> { | |
// If there are actually no styles here, then don’t write *any* codes | |
// as the prefix. An empty ANSI code may not affect the terminal | |
// output at all, but a user may just want a code-free string. | |
if self.is_plain() { | |
return Ok(()); | |
} | |
// Write the codes’ prefix, then write numbers, separated by | |
// semicolons, for each text style we want to apply. | |
write!(f, "\x1B[")?; | |
let mut written_anything = false; | |
{ | |
let mut write_char = |c| { | |
if written_anything { write!(f, ";")?; } | |
written_anything = true; | |
write!(f, "{}", c)?; | |
Ok(()) | |
}; | |
if self.is_bold { write_char('1')? } | |
if self.is_dimmed { write_char('2')? } | |
if self.is_italic { write_char('3')? } | |
if self.is_underline { write_char('4')? } | |
if self.is_blink { write_char('5')? } | |
if self.is_reverse { write_char('7')? } | |
if self.is_hidden { write_char('8')? } | |
if self.is_strikethrough { write_char('9')? } | |
} | |
// The foreground and background colours, if specified, need to be | |
// handled specially because the number codes are more complicated. | |
// (see `write_background_code` and `write_foreground_code`) | |
if let Some(bg) = self.background { | |
if written_anything { write!(f, ";")?; } | |
written_anything = true; | |
bg.write_background_code(f)?; | |
} | |
if let Some(fg) = self.foreground { | |
if written_anything { write!(f, ";")?; } | |
fg.write_foreground_code(f)?; | |
} | |
// All the codes end with an `m`, because reasons. | |
write!(f, "m")?; | |
Ok(()) | |
} | |
/// Write any ANSI codes that go *after* a piece of text. These should be | |
/// the codes to *reset* the terminal back to its normal colour and style. | |
fn write_suffix<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> { | |
if self.is_plain() { | |
Ok(()) | |
} | |
else { | |
write!(f, "{}", RESET) | |
} | |
} | |
} | |
/// The code to send to reset all styles and return to `Style::default()`. | |
pub static RESET: &str = "\x1B[0m"; | |
impl Colour { | |
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> { | |
match *self { | |
Colour::Black => write!(f, "30"), | |
Colour::Red => write!(f, "31"), | |
Colour::Green => write!(f, "32"), | |
Colour::Yellow => write!(f, "33"), | |
Colour::Blue => write!(f, "34"), | |
Colour::Purple => write!(f, "35"), | |
Colour::Cyan => write!(f, "36"), | |
Colour::White => write!(f, "37"), | |
Colour::Fixed(num) => write!(f, "38;5;{}", &num), | |
Colour::RGB(r,g,b) => write!(f, "38;2;{};{};{}", &r, &g, &b), | |
} | |
} | |
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> { | |
match *self { | |
Colour::Black => write!(f, "40"), | |
Colour::Red => write!(f, "41"), | |
Colour::Green => write!(f, "42"), | |
Colour::Yellow => write!(f, "43"), | |
Colour::Blue => write!(f, "44"), | |
Colour::Purple => write!(f, "45"), | |
Colour::Cyan => write!(f, "46"), | |
Colour::White => write!(f, "47"), | |
Colour::Fixed(num) => write!(f, "48;5;{}", &num), | |
Colour::RGB(r,g,b) => write!(f, "48;2;{};{};{}", &r, &g, &b), | |
} | |
} | |
} | |
/// Like `ANSIString`, but only displays the style prefix. | |
#[derive(Clone, Copy, Debug)] | |
pub struct Prefix(Style); | |
/// Like `ANSIString`, but only displays the difference between two | |
/// styles. | |
#[derive(Clone, Copy, Debug)] | |
pub struct Infix(Style, Style); | |
/// Like `ANSIString`, but only displays the style suffix. | |
#[derive(Clone, Copy, Debug)] | |
pub struct Suffix(Style); | |
impl Style { | |
/// The prefix for this style. | |
pub fn prefix(self) -> Prefix { | |
Prefix(self) | |
} | |
/// The infix between this style and another. | |
pub fn infix(self, other: Style) -> Infix { | |
Infix(self, other) | |
} | |
/// The suffix for this style. | |
pub fn suffix(self) -> Suffix { | |
Suffix(self) | |
} | |
} | |
impl Colour { | |
/// The prefix for this colour. | |
pub fn prefix(self) -> Prefix { | |
Prefix(self.normal()) | |
} | |
/// The infix between this colour and another. | |
pub fn infix(self, other: Colour) -> Infix { | |
Infix(self.normal(), other.normal()) | |
} | |
/// The suffix for this colour. | |
pub fn suffix(self) -> Suffix { | |
Suffix(self.normal()) | |
} | |
} | |
impl fmt::Display for Prefix { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
let f: &mut fmt::Write = f; | |
self.0.write_prefix(f) | |
} | |
} | |
impl fmt::Display for Infix { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
use difference::Difference; | |
match Difference::between(&self.0, &self.1) { | |
Difference::ExtraStyles(style) => { | |
let f: &mut fmt::Write = f; | |
style.write_prefix(f) | |
}, | |
Difference::Reset => { | |
let f: &mut fmt::Write = f; | |
write!(f, "{}{}", RESET, self.0.prefix()) | |
}, | |
Difference::NoDifference => { | |
Ok(()) // nothing to write | |
}, | |
} | |
} | |
} | |
impl fmt::Display for Suffix { | |
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
let f: &mut fmt::Write = f; | |
self.0.write_suffix(f) | |
} | |
} | |
#[cfg(test)] | |
mod test { | |
use style::Style; | |
use style::Colour::*; | |
macro_rules! test { | |
($name: ident: $style: expr; $input: expr => $result: expr) => { | |
#[test] | |
fn $name() { | |
assert_eq!($style.paint($input).to_string(), $result.to_string()); | |
let mut v = Vec::new(); | |
$style.paint($input.as_bytes()).write_to(&mut v).unwrap(); | |
assert_eq!(v.as_slice(), $result.as_bytes()); | |
} | |
}; | |
} | |
test!(plain: Style::default(); "text/plain" => "text/plain"); | |
test!(red: Red; "hi" => "\x1B[31mhi\x1B[0m"); | |
test!(black: Black.normal(); "hi" => "\x1B[30mhi\x1B[0m"); | |
test!(yellow_bold: Yellow.bold(); "hi" => "\x1B[1;33mhi\x1B[0m"); | |
test!(yellow_bold_2: Yellow.normal().bold(); "hi" => "\x1B[1;33mhi\x1B[0m"); | |
test!(blue_underline: Blue.underline(); "hi" => "\x1B[4;34mhi\x1B[0m"); | |
test!(green_bold_ul: Green.bold().underline(); "hi" => "\x1B[1;4;32mhi\x1B[0m"); | |
test!(green_bold_ul_2: Green.underline().bold(); "hi" => "\x1B[1;4;32mhi\x1B[0m"); | |
test!(purple_on_white: Purple.on(White); "hi" => "\x1B[47;35mhi\x1B[0m"); | |
test!(purple_on_white_2: Purple.normal().on(White); "hi" => "\x1B[47;35mhi\x1B[0m"); | |
test!(yellow_on_blue: Style::new().on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m"); | |
test!(yellow_on_blue_2: Cyan.on(Blue).fg(Yellow); "hi" => "\x1B[44;33mhi\x1B[0m"); | |
test!(cyan_bold_on_white: Cyan.bold().on(White); "hi" => "\x1B[1;47;36mhi\x1B[0m"); | |
test!(cyan_ul_on_white: Cyan.underline().on(White); "hi" => "\x1B[4;47;36mhi\x1B[0m"); | |
test!(cyan_bold_ul_on_white: Cyan.bold().underline().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m"); | |
test!(cyan_ul_bold_on_white: Cyan.underline().bold().on(White); "hi" => "\x1B[1;4;47;36mhi\x1B[0m"); | |
test!(fixed: Fixed(100); "hi" => "\x1B[38;5;100mhi\x1B[0m"); | |
test!(fixed_on_purple: Fixed(100).on(Purple); "hi" => "\x1B[45;38;5;100mhi\x1B[0m"); | |
test!(fixed_on_fixed: Fixed(100).on(Fixed(200)); "hi" => "\x1B[48;5;200;38;5;100mhi\x1B[0m"); | |
test!(rgb: RGB(70,130,180); "hi" => "\x1B[38;2;70;130;180mhi\x1B[0m"); | |
test!(rgb_on_blue: RGB(70,130,180).on(Blue); "hi" => "\x1B[44;38;2;70;130;180mhi\x1B[0m"); | |
test!(blue_on_rgb: Blue.on(RGB(70,130,180)); "hi" => "\x1B[48;2;70;130;180;34mhi\x1B[0m"); | |
test!(rgb_on_rgb: RGB(70,130,180).on(RGB(5,10,15)); "hi" => "\x1B[48;2;5;10;15;38;2;70;130;180mhi\x1B[0m"); | |
test!(bold: Style::new().bold(); "hi" => "\x1B[1mhi\x1B[0m"); | |
test!(underline: Style::new().underline(); "hi" => "\x1B[4mhi\x1B[0m"); | |
test!(bunderline: Style::new().bold().underline(); "hi" => "\x1B[1;4mhi\x1B[0m"); | |
test!(dimmed: Style::new().dimmed(); "hi" => "\x1B[2mhi\x1B[0m"); | |
test!(italic: Style::new().italic(); "hi" => "\x1B[3mhi\x1B[0m"); | |
test!(blink: Style::new().blink(); "hi" => "\x1B[5mhi\x1B[0m"); | |
test!(reverse: Style::new().reverse(); "hi" => "\x1B[7mhi\x1B[0m"); | |
test!(hidden: Style::new().hidden(); "hi" => "\x1B[8mhi\x1B[0m"); | |
test!(stricken: Style::new().strikethrough(); "hi" => "\x1B[9mhi\x1B[0m"); | |
} |