mirror of
https://github.com/nushell/nushell.git
synced 2024-12-11 09:43:24 +01:00
406 lines
15 KiB
Rust
406 lines
15 KiB
Rust
|
#![allow(missing_docs)]
|
|||
|
use crate::style::{Color, Style};
|
|||
|
use crate::write::AnyWrite;
|
|||
|
use std::fmt;
|
|||
|
|
|||
|
impl Style {
|
|||
|
/// Write any bytes that go *before* a piece of text to the given writer.
|
|||
|
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 colors, 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 bytes that go *after* a piece of text to the given writer.
|
|||
|
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 Color {
|
|||
|
fn write_foreground_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
|||
|
match self {
|
|||
|
Color::Black => write!(f, "30"),
|
|||
|
Color::Red => write!(f, "31"),
|
|||
|
Color::Green => write!(f, "32"),
|
|||
|
Color::Yellow => write!(f, "33"),
|
|||
|
Color::Blue => write!(f, "34"),
|
|||
|
Color::Purple => write!(f, "35"),
|
|||
|
Color::Magenta => write!(f, "35"),
|
|||
|
Color::Cyan => write!(f, "36"),
|
|||
|
Color::White => write!(f, "37"),
|
|||
|
Color::Fixed(num) => write!(f, "38;5;{}", num),
|
|||
|
Color::Rgb(r, g, b) => write!(f, "38;2;{};{};{}", r, g, b),
|
|||
|
Color::DarkGray => write!(f, "90"),
|
|||
|
Color::LightRed => write!(f, "91"),
|
|||
|
Color::LightGreen => write!(f, "92"),
|
|||
|
Color::LightYellow => write!(f, "93"),
|
|||
|
Color::LightBlue => write!(f, "94"),
|
|||
|
Color::LightPurple => write!(f, "95"),
|
|||
|
Color::LightMagenta => write!(f, "95"),
|
|||
|
Color::LightCyan => write!(f, "96"),
|
|||
|
Color::LightGray => write!(f, "97"),
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fn write_background_code<W: AnyWrite + ?Sized>(&self, f: &mut W) -> Result<(), W::Error> {
|
|||
|
match self {
|
|||
|
Color::Black => write!(f, "40"),
|
|||
|
Color::Red => write!(f, "41"),
|
|||
|
Color::Green => write!(f, "42"),
|
|||
|
Color::Yellow => write!(f, "43"),
|
|||
|
Color::Blue => write!(f, "44"),
|
|||
|
Color::Purple => write!(f, "45"),
|
|||
|
Color::Magenta => write!(f, "45"),
|
|||
|
Color::Cyan => write!(f, "46"),
|
|||
|
Color::White => write!(f, "47"),
|
|||
|
Color::Fixed(num) => write!(f, "48;5;{}", num),
|
|||
|
Color::Rgb(r, g, b) => write!(f, "48;2;{};{};{}", r, g, b),
|
|||
|
Color::DarkGray => write!(f, "100"),
|
|||
|
Color::LightRed => write!(f, "101"),
|
|||
|
Color::LightGreen => write!(f, "102"),
|
|||
|
Color::LightYellow => write!(f, "103"),
|
|||
|
Color::LightBlue => write!(f, "104"),
|
|||
|
Color::LightPurple => write!(f, "105"),
|
|||
|
Color::LightMagenta => write!(f, "105"),
|
|||
|
Color::LightCyan => write!(f, "106"),
|
|||
|
Color::LightGray => write!(f, "107"),
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// Like `ANSIString`, but only displays the style prefix.
|
|||
|
///
|
|||
|
/// This type implements the `Display` trait, meaning it can be written to a
|
|||
|
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
|||
|
/// string with the `.to_string()` method. For examples, see
|
|||
|
/// [`Style::prefix`](struct.Style.html#method.prefix).
|
|||
|
#[derive(Clone, Copy, Debug)]
|
|||
|
pub struct Prefix(Style);
|
|||
|
|
|||
|
/// Like `ANSIString`, but only displays the difference between two
|
|||
|
/// styles.
|
|||
|
///
|
|||
|
/// This type implements the `Display` trait, meaning it can be written to a
|
|||
|
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
|||
|
/// string with the `.to_string()` method. For examples, see
|
|||
|
/// [`Style::infix`](struct.Style.html#method.infix).
|
|||
|
#[derive(Clone, Copy, Debug)]
|
|||
|
pub struct Infix(Style, Style);
|
|||
|
|
|||
|
/// Like `ANSIString`, but only displays the style suffix.
|
|||
|
///
|
|||
|
/// This type implements the `Display` trait, meaning it can be written to a
|
|||
|
/// `std::fmt` formatting without doing any extra allocation, and written to a
|
|||
|
/// string with the `.to_string()` method. For examples, see
|
|||
|
/// [`Style::suffix`](struct.Style.html#method.suffix).
|
|||
|
#[derive(Clone, Copy, Debug)]
|
|||
|
pub struct Suffix(Style);
|
|||
|
|
|||
|
impl Style {
|
|||
|
/// The prefix bytes for this style. These are the bytes that tell the
|
|||
|
/// terminal to use a different color or font style.
|
|||
|
///
|
|||
|
/// # Examples
|
|||
|
///
|
|||
|
/// ```
|
|||
|
/// use nu_ansi_term::{Style, Color::Blue};
|
|||
|
///
|
|||
|
/// let style = Style::default().bold();
|
|||
|
/// assert_eq!("\x1b[1m",
|
|||
|
/// style.prefix().to_string());
|
|||
|
///
|
|||
|
/// let style = Blue.bold();
|
|||
|
/// assert_eq!("\x1b[1;34m",
|
|||
|
/// style.prefix().to_string());
|
|||
|
///
|
|||
|
/// let style = Style::default();
|
|||
|
/// assert_eq!("",
|
|||
|
/// style.prefix().to_string());
|
|||
|
/// ```
|
|||
|
pub fn prefix(self) -> Prefix {
|
|||
|
Prefix(self)
|
|||
|
}
|
|||
|
|
|||
|
/// The infix bytes between this style and `next` style. These are the bytes
|
|||
|
/// that tell the terminal to change the style to `next`. These may include
|
|||
|
/// a reset followed by the next color and style, depending on the two styles.
|
|||
|
///
|
|||
|
/// # Examples
|
|||
|
///
|
|||
|
/// ```
|
|||
|
/// use nu_ansi_term::{Style, Color::Green};
|
|||
|
///
|
|||
|
/// let style = Style::default().bold();
|
|||
|
/// assert_eq!("\x1b[32m",
|
|||
|
/// style.infix(Green.bold()).to_string());
|
|||
|
///
|
|||
|
/// let style = Green.normal();
|
|||
|
/// assert_eq!("\x1b[1m",
|
|||
|
/// style.infix(Green.bold()).to_string());
|
|||
|
///
|
|||
|
/// let style = Style::default();
|
|||
|
/// assert_eq!("",
|
|||
|
/// style.infix(style).to_string());
|
|||
|
/// ```
|
|||
|
pub fn infix(self, next: Style) -> Infix {
|
|||
|
Infix(self, next)
|
|||
|
}
|
|||
|
|
|||
|
/// The suffix for this style. These are the bytes that tell the terminal
|
|||
|
/// to reset back to its normal color and font style.
|
|||
|
///
|
|||
|
/// # Examples
|
|||
|
///
|
|||
|
/// ```
|
|||
|
/// use nu_ansi_term::{Style, Color::Green};
|
|||
|
///
|
|||
|
/// let style = Style::default().bold();
|
|||
|
/// assert_eq!("\x1b[0m",
|
|||
|
/// style.suffix().to_string());
|
|||
|
///
|
|||
|
/// let style = Green.normal().bold();
|
|||
|
/// assert_eq!("\x1b[0m",
|
|||
|
/// style.suffix().to_string());
|
|||
|
///
|
|||
|
/// let style = Style::default();
|
|||
|
/// assert_eq!("",
|
|||
|
/// style.suffix().to_string());
|
|||
|
/// ```
|
|||
|
pub fn suffix(self) -> Suffix {
|
|||
|
Suffix(self)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
impl Color {
|
|||
|
/// The prefix bytes for this color as a `Style`. These are the bytes
|
|||
|
/// that tell the terminal to use a different color or font style.
|
|||
|
///
|
|||
|
/// See also [`Style::prefix`](struct.Style.html#method.prefix).
|
|||
|
///
|
|||
|
/// # Examples
|
|||
|
///
|
|||
|
/// ```
|
|||
|
/// use nu_ansi_term::Color::Green;
|
|||
|
///
|
|||
|
/// assert_eq!("\x1b[0m",
|
|||
|
/// Green.suffix().to_string());
|
|||
|
/// ```
|
|||
|
pub fn prefix(self) -> Prefix {
|
|||
|
Prefix(self.normal())
|
|||
|
}
|
|||
|
|
|||
|
/// The infix bytes between this color and `next` color. These are the bytes
|
|||
|
/// that tell the terminal to use the `next` color, or to do nothing if
|
|||
|
/// the two colors are equal.
|
|||
|
///
|
|||
|
/// See also [`Style::infix`](struct.Style.html#method.infix).
|
|||
|
///
|
|||
|
/// # Examples
|
|||
|
///
|
|||
|
/// ```
|
|||
|
/// use nu_ansi_term::Color::{Red, Yellow};
|
|||
|
///
|
|||
|
/// assert_eq!("\x1b[33m",
|
|||
|
/// Red.infix(Yellow).to_string());
|
|||
|
/// ```
|
|||
|
pub fn infix(self, next: Color) -> Infix {
|
|||
|
Infix(self.normal(), next.normal())
|
|||
|
}
|
|||
|
|
|||
|
/// The suffix for this color as a `Style`. These are the bytes that
|
|||
|
/// tell the terminal to reset back to its normal color and font style.
|
|||
|
///
|
|||
|
/// See also [`Style::suffix`](struct.Style.html#method.suffix).
|
|||
|
///
|
|||
|
/// # Examples
|
|||
|
///
|
|||
|
/// ```
|
|||
|
/// use nu_ansi_term::Color::Purple;
|
|||
|
///
|
|||
|
/// assert_eq!("\x1b[0m",
|
|||
|
/// Purple.suffix().to_string());
|
|||
|
/// ```
|
|||
|
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 dyn fmt::Write = f;
|
|||
|
self.0.write_prefix(f)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
impl fmt::Display for Infix {
|
|||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
|
use crate::difference::Difference;
|
|||
|
|
|||
|
match Difference::between(&self.0, &self.1) {
|
|||
|
Difference::ExtraStyles(style) => {
|
|||
|
let f: &mut dyn fmt::Write = f;
|
|||
|
style.write_prefix(f)
|
|||
|
}
|
|||
|
Difference::Reset => {
|
|||
|
let f: &mut dyn fmt::Write = f;
|
|||
|
write!(f, "{}{}", RESET, self.1.prefix())
|
|||
|
}
|
|||
|
Difference::Empty => {
|
|||
|
Ok(()) // nothing to write
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
impl fmt::Display for Suffix {
|
|||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|||
|
let f: &mut dyn fmt::Write = f;
|
|||
|
self.0.write_suffix(f)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
#[cfg(test)]
|
|||
|
mod test {
|
|||
|
use crate::style::Color::*;
|
|||
|
use crate::style::Style;
|
|||
|
|
|||
|
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!(magenta_on_white: Magenta.on(White); "hi" => "\x1B[47;35mhi\x1B[0m");
|
|||
|
test!(magenta_on_white_2: Magenta.normal().on(White); "hi" => "\x1B[47;35mhi\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");
|
|||
|
test!(lr_on_lr: LightRed.on(LightRed); "hi" => "\x1B[101;91mhi\x1B[0m");
|
|||
|
|
|||
|
#[test]
|
|||
|
fn test_infix() {
|
|||
|
assert_eq!(
|
|||
|
Style::new().dimmed().infix(Style::new()).to_string(),
|
|||
|
"\x1B[0m"
|
|||
|
);
|
|||
|
assert_eq!(
|
|||
|
White.dimmed().infix(White.normal()).to_string(),
|
|||
|
"\x1B[0m\x1B[37m"
|
|||
|
);
|
|||
|
assert_eq!(White.normal().infix(White.bold()).to_string(), "\x1B[1m");
|
|||
|
assert_eq!(White.normal().infix(Blue.normal()).to_string(), "\x1B[34m");
|
|||
|
assert_eq!(Blue.bold().infix(Blue.bold()).to_string(), "");
|
|||
|
}
|
|||
|
}
|