From e5706ee88315efe0874ae01e29461b3c927b7e1d Mon Sep 17 00:00:00 2001 From: Michal Nazarewicz Date: Tue, 3 Jan 2023 15:24:48 +0100 Subject: [PATCH] Rewrite to support generic Display types inside of ANSIString MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Motivation here was to make ANSIString work with arbitrary Display types such that values don’t need to be first converted into a String. For example, in the past one would have to write something along the lines of: let (red, green, blue) = (255, 248, 231); let red = Red.paint(format!("{red:02x}"); let green = Green.paint(format!("{green:02x}"); let blue = Blue.paint(format!("{blue:02x}"); let latte = format!("#{red}{green}{blue}"); This of course works but results in three String allocations. Those can now be avoided since ANSIString can take any Display type and postpone formatting to when the entire string is formatted: let (red, green, blue) = (255, 248, 231); let red = Red.paint(red); let green = Green.paint(green); let blue = Blue.paint(blue); let latte = format!("#{red:02x}{green:02x}{blue:02x}"); Adding this feature lead to a rabbit hole of changing a lot of other interfaces around ANSIString type. Most notably, ANSIGenericString and ANSIByteString types no longer exists. ANSIString is now the only type. Implementation of Display trait and write_to method are now limited by the bounds on the generic argument rather than on the type being ANSIString or ANSIByteString. Similarly, there’s now just one ANSIStrings type which points at a slice of strings. Furthermore, util::substring now works on generic types and doesn’t perform allocations on its own. E.g. when doing a substring over Strings or Cows, the resulting substring borrows from the underlying strings. Lastly, how strings and bytes are written out has been completely refactored. This is just an internal change though not observable by the user. --- Cargo.toml | 2 +- README.md | 152 ++++++++++-------- src/ansi.rs | 208 ++++++++++++++---------- src/display.rs | 404 +++++++++++++++++++++++------------------------ src/lib.rs | 183 ++++++++++----------- src/substring.rs | 289 +++++++++++++++++++++++++++++++++ src/util.rs | 81 ---------- src/write.rs | 40 ----- 8 files changed, 785 insertions(+), 574 deletions(-) create mode 100644 src/substring.rs delete mode 100644 src/util.rs delete mode 100644 src/write.rs diff --git a/Cargo.toml b/Cargo.toml index def34af..7a216df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,7 @@ documentation = "https://docs.rs/ansi_term" homepage = "https://github.com/ogham/rust-ansi-term" license = "MIT" readme = "README.md" -version = "0.12.1" +version = "0.13.0" repository = "https://github.com/ogham/rust-ansi-term" [lib] diff --git a/README.md b/README.md index 30d52ab..a6d6255 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,19 @@ ansi_term = "0.12" ## Basic usage -There are three main types in this crate that you need to be concerned with: `ANSIString`, `Style`, and `Colour`. +There are three main types in this crate that you need to be +concerned with: `ANSIString`, `Style`, and `Colour`. -A `Style` holds stylistic information: foreground and background colours, whether the text should be bold, or blinking, or other properties. -The `Colour` enum represents the available colours. -And an `ANSIString` is a string paired with a `Style`. +A `Style` holds stylistic information: foreground and background colours, +whether the text should be bold, or blinking, or other properties. The +`Colour` enum represents the available colours. And an `ANSIString` is +a string paired with a `Style`. `Color` is also available as an alias to `Colour`. -To format a string, call the `paint` method on a `Style` or a `Colour`, passing in the string you want to format as the argument. -For example, here’s how to get some red text: +To format a string, call the `Style::paint` or `Colour::paint` method, +passing in the string you want to format as the argument. For example, +here’s how to get some red text: ```rust use ansi_term::Colour::Red; @@ -34,30 +37,49 @@ use ansi_term::Colour::Red; println!("This is in red: {}", Red.paint("a red string")); ``` -It’s important to note that the `paint` method does *not* actually return a string with the ANSI control characters surrounding it. -Instead, it returns an `ANSIString` value that has a `Display` implementation that, when formatted, returns the characters. -This allows strings to be printed with a minimum of `String` allocations being performed behind the scenes. +Note that the `paint` method doesn’t return a string with the ANSI control +sequence surrounding it. Instead, it returns an `ANSIString` value which +has a `Display` implementation that outputs the sequence. This allows +strings to be printed without additional `String` allocations. -If you *do* want to get at the escape codes, then you can convert the `ANSIString` to a string as you would any other `Display` value: +In fact, `ANSIString` is a generic type which doesn’t require the element +to be a `String` at all. Any type which implements `Display` can be +painted. Other related traits (such as `LowerHex`) are supported as well. +For example: ```rust -use ansi_term::Colour::Red; +use ansi_term::Colour::{Red, Green, Blue}; -let red_string = Red.paint("a red string").to_string(); +let red = Red.paint(255); +let green = Green.paint(248); +let blue = Blue.paint(231); + +let latte = format!("rgb({red}, {green}, {blue})"); +assert_eq!("rgb(\u{1b}[31m255\u{1b}[0m, \ + \u{1b}[32m248\u{1b}[0m, \ + \u{1b}[34m231\u{1b}[0m)", latte); + +let latte = format!("#{red:02x}{green:02x}{blue:02x}"); +assert_eq!("#\u{1b}[31mff\u{1b}[0m\ + \u{1b}[32mf8\u{1b}[0m\ + \u{1b}[34me7\u{1b}[0m", latte); ``` -**Note for Windows 10 users:** On Windows 10, the application must enable ANSI support first: +If you want to get at the escape codes, you can convert an `ANSIString` to +a string with `to_string` method as you would any other `Display` value: -```rust,ignore -let enabled = ansi_term::enable_ansi_support(); +```rustrust +use ansi_term::Colour::Red; + +let red_string = Red.paint("a red string").to_string(); ``` ## Bold, underline, background, and other styles -For anything more complex than plain foreground colour changes, you need to construct `Style` values themselves, rather than beginning with a `Colour`. -You can do this by chaining methods based on a new `Style`, created with `Style::new()`. -Each method creates a new style that has that specific property set. -For example: +For anything more complex than plain foreground colour changes, you need to +construct `Style` values. You can do this by chaining methods based on +a object created with `Style::new`. Each method creates a new style that +has that specific property set. For example: ```rust use ansi_term::Style; @@ -67,7 +89,9 @@ println!("How about some {} and {}?", Style::new().underline().paint("underline")); ``` -For brevity, these methods have also been implemented for `Colour` values, so you can give your styles a foreground colour without having to begin with an empty `Style` value: +For brevity, these methods have also been implemented for `Colour` values, +so you can give your styles a foreground colour without having to begin with +an empty `Style` value: ```rust use ansi_term::Colour::{Blue, Yellow}; @@ -79,10 +103,12 @@ println!("Demonstrating {} and {}!", println!("Yellow on blue: {}", Yellow.on(Blue).paint("wow!")); ``` -The complete list of styles you can use are: -`bold`, `dimmed`, `italic`, `underline`, `blink`, `reverse`, `hidden`, and `on` for background colours. +The complete list of styles you can use are: `bold`, `dimmed`, `italic`, +`underline`, `blink`, `reverse`, `hidden`, `strikethrough`, and `on` for +background colours. -In some cases, you may find it easier to change the foreground on an existing `Style` rather than starting from the appropriate `Colour`. +In some cases, you may find it easier to change the foreground on an +existing `Style` rather than starting from the appropriate `Colour`. You can do this using the `fg` method: ```rust @@ -93,8 +119,10 @@ println!("Yellow on blue: {}", Style::new().on(Blue).fg(Yellow).paint("yow!")); println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!")); ``` -You can turn a `Colour` into a `Style` with the `normal` method. -This will produce the exact same `ANSIString` as if you just used the `paint` method on the `Colour` directly, but it’s useful in certain cases: for example, you may have a method that returns `Styles`, and need to represent both the “red bold” and “red, but not bold” styles with values of the same type. The `Style` struct also has a `Default` implementation if you want to have a style with *nothing* set. +You can turn a `Colour` into a `Style` with the `normal` method. This +produces the exact same `ANSIString` as if you just used the +`Colour::paint` method directly, but it’s useful if you need to represent +both the “red bold” and “red, but not bold” with values of the same type. ```rust use ansi_term::Style; @@ -104,11 +132,11 @@ Red.normal().paint("yet another red string"); Style::default().paint("a completely regular string"); ``` - ## Extended colours -You can access the extended range of 256 colours by using the `Colour::Fixed` variant, which takes an argument of the colour number to use. -This can be included wherever you would use a `Colour`: +You can access the 256-colour palette by using the `Colour::Fixed` +variant. It takes an argument of the colour number to use. This can be +included wherever you would use a `Colour`: ```rust use ansi_term::Colour::Fixed; @@ -117,10 +145,8 @@ Fixed(134).paint("A sort of light purple"); Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup"); ``` -The first sixteen of these values are the same as the normal and bold standard colour variants. -There’s nothing stopping you from using these as `Fixed` colours instead, but there’s nothing to be gained by doing so either. - -You can also access full 24-bit colour by using the `Colour::RGB` variant, which takes separate `u8` arguments for red, green, and blue: +You can also access full 24-bit colour by using the `Colour::RGB` variant, +which takes separate red, green, and blue arguments: ```rust use ansi_term::Colour::RGB; @@ -130,54 +156,56 @@ RGB(70, 130, 180).paint("Steel blue"); ## Combining successive coloured strings -The benefit of writing ANSI escape codes to the terminal is that they *stack*: you do not need to end every coloured string with a reset code if the text that follows it is of a similar style. -For example, if you want to have some blue text followed by some blue bold text, it’s possible to send the ANSI code for blue, followed by the ANSI code for bold, and finishing with a reset code without having to have an extra one between the two strings. +The benefit of writing ANSI escape codes to the terminal is that they +*stack*: you do not need to end every coloured string with a reset code if +the text that follows it is of a similar style. For example, if you want to +have some blue text followed by some blue bold text, it’s possible to send +the ANSI code for blue, followed by the ANSI code for bold, and finishing +with a reset code without having to have an extra one between the two +strings. -This crate can optimise the ANSI codes that get printed in situations like this, making life easier for your terminal renderer. -The `ANSIStrings` struct takes a slice of several `ANSIString` values, and will iterate over each of them, printing only the codes for the styles that need to be updated as part of its formatting routine. +This crate can optimise the ANSI codes that get printed in situations like +this, making life easier for your terminal renderer. The `ANSIStrings` +type takes a slice of several `ANSIString` values, and will iterate over +each of them, printing only the codes for the styles that need to be updated +as part of its formatting routine. -The following code snippet uses this to enclose a binary number displayed in red bold text inside some red, but not bold, brackets: +The following code snippet uses this to enclose a binary number displayed in +red bold text inside some red, but not bold, brackets: ```rust use ansi_term::Colour::Red; use ansi_term::{ANSIString, ANSIStrings}; let some_value = format!("{:b}", 42); -let strings: &[ANSIString<'static>] = &[ - Red.paint("["), - Red.bold().paint(some_value), - Red.paint("]"), +let strings: &[ANSIString<_>] = &[ + Red.paint_cow("["), + Red.bold().paint_cow(some_value), + Red.paint_cow("]"), ]; println!("Value: {}", ANSIStrings(strings)); ``` -There are several things to note here. -Firstly, the `paint` method can take *either* an owned `String` or a borrowed `&str`. -Internally, an `ANSIString` holds a copy-on-write (`Cow`) string value to deal with both owned and borrowed strings at the same time. -This is used here to display a `String`, the result of the `format!` call, using the same mechanism as some statically-available `&str` slices. -Secondly, that the `ANSIStrings` value works in the same way as its singular counterpart, with a `Display` implementation that only performs the formatting when required. +In this example, the `paint_cow` method can take *either* an owned `String` +or a borrowed `&str` value. It converts the argument into a copy-on-write +string (`Cow`) and wraps that inside of an `ANSIString`. -## Byte strings +The `ANSIStrings` value works in the same way as its singular counterpart, +with a `Display` implementation that only performs the formatting when +required. -This library also supports formatting `[u8]` byte strings; this supports applications working with text in an unknown encoding. -`Style` and `Colour` support painting `[u8]` values, resulting in an `ANSIByteString`. -This type does not implement `Display`, as it may not contain UTF-8, but it does provide a method `write_to` to write the result to any value that implements `Write`: - -```rust -use ansi_term::Colour::Green; - -Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap(); -``` +## Byte strings -Similarly, the type `ANSIByteStrings` supports writing a list of `ANSIByteString` values with minimal escape sequences: +This library also handles formatting `[u8]` byte strings. This supports +applications working with text in an unknown encoding. More specifically, +any type which implements `AsRef<[u8]>` can be painted. For such types +`ANSIString::write_to` method is provided to write the value to any object +that implements `Write`: ```rust use ansi_term::Colour::Green; -use ansi_term::ANSIByteStrings; -ANSIByteStrings(&[ - Green.paint("user data 1\n".as_bytes()), - Green.bold().paint("user data 2\n".as_bytes()), -]).write_to(&mut std::io::stdout()).unwrap(); +Green.paint("user data".as_bytes()) + .write_to(&mut std::io::stdout()).unwrap(); ``` diff --git a/src/ansi.rs b/src/ansi.rs index aaf2152..ed705ac 100644 --- a/src/ansi.rs +++ b/src/ansi.rs @@ -1,74 +1,135 @@ use style::{Colour, Style}; use std::fmt; - -use write::AnyWrite; +use std::str; // ---- generating ANSI codes ---- -impl Style { +/// A buffer to write prefix ANSI code into. This allows the entire prefix code +/// to be formatted and then sent to Formatter or Write all at once. +// The length 54 corresponds to maximum number of bytes write_impl might +// write. It is 2 bytes for `\x1B[` prefix, 9*2 bytes for all possible +// single-digit codes and 2*17 for foreground and background. +pub(super) struct PrefixBuffer([u8; 54]); + +enum ColourCategory { + Simple(u8), + Fixed(u8), + RGB(u8, u8, u8) +} + +impl Default for PrefixBuffer { + fn default() -> Self { + PrefixBuffer([0; 54]) + } +} + +impl PrefixBuffer { + /// Returns ANSI code for given style. + pub fn write(&'_ mut self, style: &Style) -> &'_ str { + self.write_impl(style, false) + } - /// Write any bytes that go *before* a piece of text to the given writer. - fn write_prefix(&self, f: &mut W) -> Result<(), W::Error> { + /// Returns ANSI code for given style including a reset sequence. + pub fn write_with_reset(&'_ mut self, style: &Style) -> &'_ str { + self.write_impl(style, true) + } + /// Returns ANSI code for given style optionally including a reset sequence. + fn write_impl(&'_ mut self, style: &Style, with_reset: bool) -> &'_ str { // 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(()); + if style.is_plain() { + return if with_reset { RESET } else { "" }; } // 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; + self.0[..2].copy_from_slice(b"\x1B["); + let mut idx = 2; { - let mut write_char = |c| { - if written_anything { write!(f, ";")?; } - written_anything = true; - write!(f, "{}", c)?; - Ok(()) + let mut write_char = |byte: u8| { + self.0[idx] = byte; + self.0[idx + 1] = b';'; + idx += 2; }; - 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')? } + if with_reset { write_char(b'0'); } + if style.is_bold { write_char(b'1'); } + if style.is_dimmed { write_char(b'2'); } + if style.is_italic { write_char(b'3'); } + if style.is_underline { write_char(b'4'); } + if style.is_blink { write_char(b'5'); } + if style.is_reverse { write_char(b'7'); } + if style.is_hidden { write_char(b'8'); } + if style.is_strikethrough { write_char(b'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)?; + // (see `write_colour_category`) + if let Some(bg) = style.background { + idx = self.write_colour_category(idx, b'4', bg.colour_category()); } - - if let Some(fg) = self.foreground { - if written_anything { write!(f, ";")?; } - fg.write_foreground_code(f)?; + if let Some(fg) = style.foreground { + idx = self.write_colour_category(idx, b'3', fg.colour_category()); } - // All the codes end with an `m`, because reasons. - write!(f, "m")?; + // Replace final `;` with a `m` which indicates end of the ANSI code. + self.0[idx - 1] = b'm'; - Ok(()) + // SAFETY: We’ve only ever written bytes <128 so everything written is + // ASCII and thus valid UTF-8. + unsafe { str::from_utf8_unchecked(&self.0[..idx]) } } - /// Write any bytes that go *after* a piece of text to the given writer. - fn write_suffix(&self, f: &mut W) -> Result<(), W::Error> { - if self.is_plain() { - Ok(()) + /// Writes colour code at given position in the buffer. Ends the sequence + /// with a semicolon. Returns index past the last written byte. + /// + /// May write up to 17 bytes. + fn write_colour_category( + &mut self, + idx: usize, + typ: u8, + category: ColourCategory, + ) -> usize { + use std::io::Write; + + self.0[idx] = typ; + match category { + ColourCategory::Simple(digit) => { + self.0[idx + 1] = digit; + self.0[idx + 2] = b';'; + idx + 3 + }, + ColourCategory::Fixed(num) => { + self.0.len() - { + let mut wr = &mut self.0[idx+1..]; + write!(wr, "8;5;{};", num).unwrap(); + wr.len() + } + } + ColourCategory::RGB(r, g, b) => { + self.0.len() - { + let mut wr = &mut self.0[idx+1..]; + write!(wr, "8;2;{};{};{};", r, g, b).unwrap(); + wr.len() + } + } } - else { - write!(f, "{}", RESET) + } +} + +impl Style { + /// Returns any bytes that go *after* a piece of text. + pub(super) fn suffix_str(&self) -> &'static str { + if self.is_plain() { + "" + } else { + RESET } } } @@ -78,35 +139,19 @@ impl Style { pub static RESET: &str = "\x1B[0m"; - impl Colour { - fn write_foreground_code(&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(&self, f: &mut W) -> Result<(), W::Error> { + fn colour_category(&self) -> ColourCategory { 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), + Colour::Black => ColourCategory::Simple(b'0'), + Colour::Red => ColourCategory::Simple(b'1'), + Colour::Green => ColourCategory::Simple(b'2'), + Colour::Yellow => ColourCategory::Simple(b'3'), + Colour::Blue => ColourCategory::Simple(b'4'), + Colour::Purple => ColourCategory::Simple(b'5'), + Colour::Cyan => ColourCategory::Simple(b'6'), + Colour::White => ColourCategory::Simple(b'7'), + Colour::Fixed(num) => ColourCategory::Fixed(num), + Colour::RGB(r,g,b) => ColourCategory::RGB(r, g, b), } } } @@ -276,8 +321,7 @@ impl Colour { 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) + f.write_str(PrefixBuffer::default().write(&self.0)) } } @@ -285,28 +329,20 @@ impl fmt::Display for Prefix { 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.1.prefix()) - }, - Difference::NoDifference => { - Ok(()) // nothing to write - }, - } + let mut buf = PrefixBuffer::default(); + let prefix = match Difference::between(&self.0, &self.1) { + Difference::ExtraStyles(style) => buf.write(&style), + Difference::Reset => buf.write_with_reset(&self.1), + Difference::NoDifference => return Ok(()), + }; + f.write_str(prefix) } } 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) + f.write_str(self.0.suffix_str()) } } @@ -366,7 +402,7 @@ mod test { #[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.dimmed().infix(White.normal()).to_string(), "\x1B[0;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(), ""); diff --git a/src/display.rs b/src/display.rs index 17c54f0..833214d 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,171 +1,126 @@ use std::borrow::Cow; use std::fmt; use std::io; -use std::ops::Deref; -use ansi::RESET; +use ansi::PrefixBuffer; use difference::Difference; use style::{Style, Colour}; -use write::AnyWrite; -/// An `ANSIGenericString` includes a generic string type and a `Style` to -/// display that string. `ANSIString` and `ANSIByteString` are aliases for -/// this type on `str` and `\[u8]`, respectively. -#[derive(PartialEq, Debug)] -pub struct ANSIGenericString<'a, S: 'a + ToOwned + ?Sized> -where ::Owned: fmt::Debug { - style: Style, - string: Cow<'a, S>, -} - - -/// Cloning an `ANSIGenericString` will clone its underlying string. -/// -/// # Examples -/// -/// ``` -/// use ansi_term::ANSIString; +/// An `ANSIString` includes a generic string type and a `Style` to display that +/// string. /// -/// let plain_string = ANSIString::from("a plain string"); -/// let clone_string = plain_string.clone(); -/// assert_eq!(clone_string, plain_string); -/// ``` -impl<'a, S: 'a + ToOwned + ?Sized> Clone for ANSIGenericString<'a, S> -where ::Owned: fmt::Debug { - fn clone(&self) -> ANSIGenericString<'a, S> { - ANSIGenericString { - style: self.style, - string: self.string.clone(), - } - } -} - -// You might think that the hand-written Clone impl above is the same as the -// one that gets generated with #[derive]. But it’s not *quite* the same! -// -// `str` is not Clone, and the derived Clone implementation puts a Clone -// constraint on the S type parameter (generated using --pretty=expanded): -// -// ↓_________________↓ -// impl <'a, S: ::std::clone::Clone + 'a + ToOwned + ?Sized> ::std::clone::Clone -// for ANSIGenericString<'a, S> where -// ::Owned: fmt::Debug { ... } -// -// This resulted in compile errors when you tried to derive Clone on a type -// that used it: -// -// #[derive(PartialEq, Debug, Clone, Default)] -// pub struct TextCellContents(Vec>); -// ^^^^^^^^^^^^^^^^^^^^^^^^^ -// error[E0277]: the trait `std::clone::Clone` is not implemented for `str` -// -// The hand-written impl above can ignore that constraint and still compile. - - - -/// An ANSI String is a string coupled with the `Style` to display it -/// in a terminal. -/// -/// Although not technically a string itself, it can be turned into -/// one with the `to_string` method. +/// If the generic type implements `Display`, this value can be displayed or +/// turned into a string using `to_string` method. Similarly, if the generic +/// type implements `AsRef<[u8]>`, this value can be written to arbitrary byte +/// stream with ANSI codes surrounding it. /// /// # Examples /// /// ``` -/// use ansi_term::ANSIString; -/// use ansi_term::Colour::Red; +/// use ansi_term::{ANSIString, Colour}; +/// +/// let red: ANSIString<_> = Colour::Red.paint("red"); +/// println!("A {red} string"); /// -/// let red_string = Red.paint("a red string"); -/// println!("{}", red_string); +/// let red = red.to_string(); +/// let message = ["A ", &red, " string"].concat(); +/// assert_eq!("A \x1b[31mred\x1B[0m string", message); /// ``` /// /// ``` -/// use ansi_term::ANSIString; +/// use ansi_term::{ANSIString, Colour}; /// -/// let plain_string = ANSIString::from("a plain string"); -/// assert_eq!(&*plain_string, "a plain string"); +/// let green = Colour::Green.paint("A green string".as_bytes()); +/// green.write_to(&mut std::io::stdout()).unwrap(); +/// let mut buf = [0; 23]; +/// green.write_to(&mut &mut buf[..]).unwrap(); +/// assert_eq!(b"\x1b[32mA green string\x1b[0m", &buf[..]); /// ``` -pub type ANSIString<'a> = ANSIGenericString<'a, str>; - -/// An `ANSIByteString` represents a formatted series of bytes. Use -/// `ANSIByteString` when styling text with an unknown encoding. -pub type ANSIByteString<'a> = ANSIGenericString<'a, [u8]>; - -impl<'a, I, S: 'a + ToOwned + ?Sized> From for ANSIGenericString<'a, S> -where I: Into>, - ::Owned: fmt::Debug { - fn from(input: I) -> ANSIGenericString<'a, S> { - ANSIGenericString { - string: input.into(), - style: Style::default(), - } - } +#[derive(Clone, Default, Debug, PartialEq)] +pub struct ANSIString { + /// Style of the value. + pub style: Style, + /// Value of the string. + pub value: S, } -impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> - where ::Owned: fmt::Debug { - - /// Directly access the style - pub fn style_ref(&self) -> &Style { - &self.style - } - - /// Directly access the style mutably - pub fn style_ref_mut(&mut self) -> &mut Style { - &mut self.style +impl<'a, S, I> From for ANSIString> +where S: 'a + ToOwned + ?Sized, + I: Into> { + fn from(input: I) -> Self { + ANSIString { + style: Style::default(), + value: input.into(), + } } } -impl<'a, S: 'a + ToOwned + ?Sized> Deref for ANSIGenericString<'a, S> -where ::Owned: fmt::Debug { - type Target = S; - - fn deref(&self) -> &S { - self.string.deref() +impl ANSIString { + /// Creates a new object with default style. + pub fn new>(value: T) -> Self { + Self { + style: Style::default(), + value: value.into(), + } } } -/// A set of `ANSIGenericString`s collected together, in order to be +/// A set of `ANSIString`s collected together, in order to be /// written with a minimum of control characters. #[derive(Debug, PartialEq)] -pub struct ANSIGenericStrings<'a, S: 'a + ToOwned + ?Sized> - (pub &'a [ANSIGenericString<'a, S>]) - where ::Owned: fmt::Debug, S: PartialEq; - -/// A set of `ANSIString`s collected together, in order to be written with a -/// minimum of control characters. -pub type ANSIStrings<'a> = ANSIGenericStrings<'a, str>; - -/// A function to construct an `ANSIStrings` instance. -#[allow(non_snake_case)] -pub fn ANSIStrings<'a>(arg: &'a [ANSIString<'a>]) -> ANSIStrings<'a> { - ANSIGenericStrings(arg) -} +pub struct ANSIStrings<'a, S: 'a>(pub &'a [ANSIString]); -/// A set of `ANSIByteString`s collected together, in order to be -/// written with a minimum of control characters. -pub type ANSIByteStrings<'a> = ANSIGenericStrings<'a, [u8]>; - -/// A function to construct an `ANSIByteStrings` instance. -#[allow(non_snake_case)] -pub fn ANSIByteStrings<'a>(arg: &'a [ANSIByteString<'a>]) -> ANSIByteStrings<'a> { - ANSIGenericStrings(arg) +impl<'a, S: 'a> ANSIStrings<'a, S> { + /// Returns iterator over all string values stored. + /// + /// # Examples + /// + /// ``` + /// use ansi_term::{ANSIString, ANSIStrings, Colour, Style}; + /// + /// let strings = [ + /// Colour::Red.paint("Red"), + /// Style::default().paint(" "), + /// Colour::Green.paint("Green"), + /// Style::default().paint(" "), + /// Colour::Blue.paint("Blue"), + /// ]; + /// let strings = ANSIStrings(&strings); + /// + /// let unstyled_len = strings.values().map(|val| val.len()).sum::(); + /// assert_eq!(14, unstyled_len); + /// + /// let unstyled = strings.values().map(|&value| value).collect::(); + /// assert_eq!("Red Green Blue", unstyled); + /// ``` + pub fn values(&self) -> impl Iterator { + self.0.iter().map(|string| &string.value) + } } - // ---- paint functions ---- impl Style { + /// Paints the given text with this colour, returning an ANSI string. + #[must_use] + #[inline] + pub fn paint(self, input: S) -> ANSIString { + ANSIString { + value: input, + style: self, + } + } /// Paints the given text with this colour, returning an ANSI string. #[must_use] - pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S> - where I: Into>, - ::Owned: fmt::Debug { - ANSIGenericString { - string: input.into(), + #[inline] + pub fn paint_cow<'a, S, I>(self, input: I) -> ANSIString> + where S: 'a + ToOwned + ?Sized, + I: Into> { + ANSIString { + value: input.into(), style: self, } } @@ -173,7 +128,6 @@ impl Style { impl Colour { - /// Paints the given text with this colour, returning an ANSI string. /// This is a short-cut so you don’t have to use `Blue.normal()` just /// to get blue text. @@ -183,114 +137,156 @@ impl Colour { /// println!("{}", Blue.paint("da ba dee")); /// ``` #[must_use] - pub fn paint<'a, I, S: 'a + ToOwned + ?Sized>(self, input: I) -> ANSIGenericString<'a, S> - where I: Into>, - ::Owned: fmt::Debug { - ANSIGenericString { - string: input.into(), - style: self.normal(), - } + #[inline] + pub fn paint(self, input: S) -> ANSIString { + self.normal().paint(input) + } + + /// Paints the given text with this colour, returning an ANSI string. + /// This is a short-cut so you don’t have to use `Blue.normal()` just + /// to get blue text. + /// + /// ``` + /// use ansi_term::Colour::Blue; + /// println!("{}", Blue.paint_cow("da ba dee")); + /// ``` + #[must_use] + #[inline] + pub fn paint_cow<'a, S, I>(self, input: I) -> ANSIString> + where S: 'a + ToOwned + ?Sized, + I: Into> { + self.normal().paint_cow(input) } } -// ---- writers for individual ANSI strings ---- +// ---- Display et al ---- -impl<'a> fmt::Display for ANSIString<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let w: &mut fmt::Write = f; - self.write_to_any(w) - } -} +macro_rules! display_impl { + ($trait:ident, $write:ident) => { + impl fmt::$trait for ANSIString { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.write_str(PrefixBuffer::default().write(&self.style))?; + self.value.fmt(fmt)?; + fmt.write_str(self.style.suffix_str()) + } + } -impl<'a> ANSIByteString<'a> { - /// Write an `ANSIByteString` to an `io::Write`. This writes the escape - /// sequences for the associated `Style` around the bytes. - pub fn write_to(&self, w: &mut W) -> io::Result<()> { - let w: &mut io::Write = w; - self.write_to_any(w) - } -} + struct $write<'a, 'b: 'a>(pub &'a mut fmt::Formatter<'b>); + + impl<'a, 'b, V: fmt::$trait> AnyWrite for $write<'a, 'b> { + type Error = fmt::Error; -impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> -where ::Owned: fmt::Debug, &'a S: AsRef<[u8]> { - fn write_to_any + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { - write!(w, "{}", self.style.prefix())?; - w.write_str(self.string.as_ref())?; - write!(w, "{}", self.style.suffix()) + fn write(&mut self, code: &str, value: &V) -> Result<(), Self::Error> { + self.0.write_str(code)?; + value.fmt(self.0) + } + + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + self.0.write_str(s) + } + } + + impl<'a, S: fmt::$trait> fmt::$trait for ANSIStrings<'a, S> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.write_to_any($write(f)) + } + } } } +display_impl!(Binary, BinaryWrite); +display_impl!(Display, DisplayWrite); +display_impl!(LowerExp, LowerExpWrite); +display_impl!(LowerHex, LowerHexWrite); +display_impl!(Octal, OctalWrite); +display_impl!(Pointer, PointerWrite); +display_impl!(UpperExp, UpperExpWrite); +display_impl!(UpperHex, UpperHexWrite); + -// ---- writers for combined ANSI strings ---- +// ---- Writes for binary strings ---- -impl<'a> fmt::Display for ANSIStrings<'a> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let f: &mut fmt::Write = f; - self.write_to_any(f) +impl> ANSIString { + /// Write an `ANSIString` to an `io::Write`. This writes the escape + /// sequences for the associated `Style` around the bytes. + pub fn write_to(&self, w: &mut W) -> io::Result<()> { + w.write_all(PrefixBuffer::default().write(&self.style).as_bytes())?; + w.write_all(self.value.as_ref())?; + w.write_all(self.style.suffix_str().as_bytes())?; + Ok(()) } } -impl<'a> ANSIByteStrings<'a> { - /// Write `ANSIByteStrings` to an `io::Write`. This writes the minimal - /// escape sequences for the associated `Style`s around each set of - /// bytes. +impl<'a, S: AsRef<[u8]>> ANSIStrings<'a, S> { + /// Write `ANSIStrings` to an `io::Write`. This writes the minimal + /// escape sequences for the associated `Style`s around each set of bytes. pub fn write_to(&self, w: &mut W) -> io::Result<()> { - let w: &mut io::Write = w; - self.write_to_any(w) + self.write_to_any(IOWrite(w)) } } -impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> ANSIGenericStrings<'a, S> -where ::Owned: fmt::Debug, &'a S: AsRef<[u8]> { - fn write_to_any + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { + +// ---- writer for combined ANSI strings ---- + +impl<'a, S> ANSIStrings<'a, S> { + fn write_to_any>(&self, mut wr: W) -> Result<(), W::Error> { use self::Difference::*; - let first = match self.0.first() { + let mut buf = PrefixBuffer::default(); + match self.0.first() { None => return Ok(()), - Some(f) => f, - }; - - write!(w, "{}", first.style.prefix())?; - w.write_str(first.string.as_ref())?; + Some(first) => wr.write(buf.write(&first.style), &first.value)?, + } for window in self.0.windows(2) { - match Difference::between(&window[0].style, &window[1].style) { - ExtraStyles(style) => write!(w, "{}", style.prefix())?, - Reset => write!(w, "{}{}", RESET, window[1].style.prefix())?, - NoDifference => {/* Do nothing! */}, - } - - w.write_str(&window[1].string)?; + let code = match Difference::between(&window[0].style, &window[1].style) { + ExtraStyles(style) => buf.write(&style), + Reset => buf.write_with_reset(&window[1].style), + NoDifference => "", + }; + wr.write(code, &window[1].value)?; } - // Write the final reset string after all of the ANSIStrings have been - // written, *except* if the last one has no styles, because it would - // have already been written by this point. if let Some(last) = self.0.last() { - if !last.style.is_plain() { - write!(w, "{}", RESET)?; - } + wr.write_str(last.style.suffix_str())?; } Ok(()) } } +trait AnyWrite { + type Error; -// ---- tests ---- + fn write(&mut self, code: &str, value: &V) -> Result<(), Self::Error>; + fn write_str(&mut self, s: &str) -> Result<(), Self::Error>; +} + +struct IOWrite<'a, W: 'a>(pub &'a mut W); + +impl<'a, W: io::Write, V: AsRef<[u8]>> AnyWrite for IOWrite<'a, W> { + type Error = io::Error; -#[cfg(test)] -mod tests { - pub use super::super::ANSIStrings; - pub use style::Style; - pub use style::Colour::*; - - #[test] - fn no_control_codes_for_plain() { - let one = Style::default().paint("one"); - let two = Style::default().paint("two"); - let output = format!("{}", ANSIStrings( &[ one, two ] )); - assert_eq!(&*output, "onetwo"); + fn write(&mut self, code: &str, value: &V) -> Result<(), Self::Error> { + self.0.write_all(code.as_bytes())?; + self.0.write_all(value.as_ref()) } + + fn write_str(&mut self, s: &str) -> Result<(), Self::Error> { + self.0.write_all(s.as_bytes()) + } +} + + +// ---- tests ---- + +#[test] +fn no_control_codes_for_plain() { + use std::borrow::Cow; + + let one = Style::default().paint(Cow::Borrowed("one")); + let two = Style::default().paint(Cow::Borrowed("two")); + let output = format!("{}", ANSIStrings( &[ one, two ] )); + assert_eq!(&*output, "onetwo"); } diff --git a/src/lib.rs b/src/lib.rs index 2d2f83a..a56382e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,20 @@ //! This is a library for controlling colours and formatting, such as //! red bold text or blue underlined text, on ANSI terminals. //! -//! //! ## Basic usage //! //! There are three main types in this crate that you need to be //! concerned with: [`ANSIString`], [`Style`], and [`Colour`]. //! -//! A `Style` holds stylistic information: foreground and background colours, +//! A [`Style`] holds stylistic information: foreground and background colours, //! whether the text should be bold, or blinking, or other properties. The -//! [`Colour`] enum represents the available colours. And an [`ANSIString`] is a -//! string paired with a [`Style`]. +//! [`Colour`] enum represents the available colours. And an [`ANSIString`] is +//! a string paired with a [`Style`]. //! -//! [`Color`] is also available as an alias to `Colour`. +//! [`Color`] is also available as an alias to [`Colour`]. //! -//! To format a string, call the `paint` method on a `Style` or a `Colour`, -//! passing in the string you want to format as the argument. For example, +//! To format a string, call the [`Style::paint`] or [`Colour::paint`] method, +//! passing in the string you want to format as the argument. For example, //! here’s how to get some red text: //! //! ``` @@ -24,29 +23,50 @@ //! println!("This is in red: {}", Red.paint("a red string")); //! ``` //! -//! It’s important to note that the `paint` method does *not* actually return a -//! string with the ANSI control characters surrounding it. Instead, it returns -//! an [`ANSIString`] value that has a [`Display`] implementation that, when -//! formatted, returns the characters. This allows strings to be printed with a -//! minimum of [`String`] allocations being performed behind the scenes. +//! Note that the `paint` method doesn’t return a string with the ANSI control +//! sequence surrounding it. Instead, it returns an [`ANSIString`] value which +//! has a [`Display`] implementation that outputs the sequence. This allows +//! strings to be printed without additional [`String`] allocations. +//! +//! In fact, [`ANSIString`] is a generic type which doesn’t require the element +//! to be a [`String`] at all. Any type which implements [`Display`] can be +//! painted. Other related traits (such as [`LowerHex`]) are supported as well. +//! For example: +//! +//! ``` +//! use ansi_term::Colour::{Red, Green, Blue}; +//! +//! let red = Red.paint(255); +//! let green = Green.paint(248); +//! let blue = Blue.paint(231); //! -//! If you *do* want to get at the escape codes, then you can convert the -//! [`ANSIString`] to a string as you would any other `Display` value: +//! let latte = format!("rgb({red}, {green}, {blue})"); +//! assert_eq!("rgb(\u{1b}[31m255\u{1b}[0m, \ +//! \u{1b}[32m248\u{1b}[0m, \ +//! \u{1b}[34m231\u{1b}[0m)", latte); //! +//! let latte = format!("#{red:02x}{green:02x}{blue:02x}"); +//! assert_eq!("#\u{1b}[31mff\u{1b}[0m\ +//! \u{1b}[32mf8\u{1b}[0m\ +//! \u{1b}[34me7\u{1b}[0m", latte); //! ``` +//! +//! If you want to get at the escape codes, you can convert an [`ANSIString`] to +//! a string with [`to_string`](ToString::to_string) method as you would any +//! other [`Display`] value: +//! +//! ```rust //! use ansi_term::Colour::Red; //! //! let red_string = Red.paint("a red string").to_string(); //! ``` //! -//! //! ## Bold, underline, background, and other styles //! //! For anything more complex than plain foreground colour changes, you need to -//! construct `Style` values themselves, rather than beginning with a `Colour`. -//! You can do this by chaining methods based on a new `Style`, created with -//! [`Style::new()`]. Each method creates a new style that has that specific -//! property set. For example: +//! construct [`Style`] values. You can do this by chaining methods based on +//! a object created with [`Style::new`]. Each method creates a new style that +//! has that specific property set. For example: //! //! ``` //! use ansi_term::Style; @@ -75,7 +95,7 @@ //! background colours. //! //! In some cases, you may find it easier to change the foreground on an -//! existing `Style` rather than starting from the appropriate `Colour`. +//! existing [`Style`] rather than starting from the appropriate [`Colour`]. //! You can do this using the [`fg`] method: //! //! ``` @@ -86,13 +106,10 @@ //! println!("Also yellow on blue: {}", Cyan.on(Blue).fg(Yellow).paint("zow!")); //! ``` //! -//! You can turn a `Colour` into a `Style` with the [`normal`] method. -//! This will produce the exact same `ANSIString` as if you just used the -//! `paint` method on the `Colour` directly, but it’s useful in certain cases: -//! for example, you may have a method that returns `Styles`, and need to -//! represent both the “red bold” and “red, but not bold” styles with values of -//! the same type. The `Style` struct also has a [`Default`] implementation if you -//! want to have a style with *nothing* set. +//! You can turn a [`Colour`] into a [`Style`] with the [`normal`] method. This +//! produces the exact same [`ANSIString`] as if you just used the +//! [`Colour::paint`] method directly, but it’s useful if you need to represent +//! both the “red bold” and “red, but not bold” with values of the same type. //! //! ``` //! use ansi_term::Style; @@ -102,12 +119,11 @@ //! Style::default().paint("a completely regular string"); //! ``` //! -//! //! ## Extended colours //! -//! You can access the extended range of 256 colours by using the `Colour::Fixed` -//! variant, which takes an argument of the colour number to use. This can be -//! included wherever you would use a `Colour`: +//! You can access the 256-colour palette by using the [`Colour::Fixed`] +//! variant. It takes an argument of the colour number to use. This can be +//! included wherever you would use a [`Colour`]: //! //! ``` //! use ansi_term::Colour::Fixed; @@ -116,13 +132,8 @@ //! Fixed(221).on(Fixed(124)).paint("Mustard in the ketchup"); //! ``` //! -//! The first sixteen of these values are the same as the normal and bold -//! standard colour variants. There’s nothing stopping you from using these as -//! `Fixed` colours instead, but there’s nothing to be gained by doing so -//! either. -//! -//! You can also access full 24-bit colour by using the `Colour::RGB` variant, -//! which takes separate `u8` arguments for red, green, and blue: +//! You can also access full 24-bit colour by using the [`Colour::RGB`] variant, +//! which takes separate red, green, and blue arguments: //! //! ``` //! use ansi_term::Colour::RGB; @@ -154,80 +165,55 @@ //! use ansi_term::{ANSIString, ANSIStrings}; //! //! let some_value = format!("{:b}", 42); -//! let strings: &[ANSIString<'static>] = &[ -//! Red.paint("["), -//! Red.bold().paint(some_value), -//! Red.paint("]"), +//! let strings: &[ANSIString<_>] = &[ +//! Red.paint_cow("["), +//! Red.bold().paint_cow(some_value), +//! Red.paint_cow("]"), //! ]; //! //! println!("Value: {}", ANSIStrings(strings)); //! ``` //! -//! There are several things to note here. Firstly, the [`paint`] method can take -//! *either* an owned [`String`] or a borrowed [`&str`]. Internally, an [`ANSIString`] -//! holds a copy-on-write ([`Cow`]) string value to deal with both owned and -//! borrowed strings at the same time. This is used here to display a `String`, -//! the result of the `format!` call, using the same mechanism as some -//! statically-available `&str` slices. Secondly, that the [`ANSIStrings`] value -//! works in the same way as its singular counterpart, with a [`Display`] -//! implementation that only performs the formatting when required. +//! In this example, the [`paint_cow`](Style::paint_cow) method can take +//! *either* an owned [`String`] or a borrowed [`&str`] value. It converts the +//! argument into a copy-on-write string ([`Cow`]) and wraps that inside of an +//! [`ANSIString`]. +//! +//! The [`ANSIStrings`] value works in the same way as its singular counterpart, +//! with a [`Display`] implementation that only performs the formatting when +//! required. //! //! ## Byte strings //! -//! This library also supports formatting `\[u8]` byte strings; this supports -//! applications working with text in an unknown encoding. [`Style`] and -//! [`Colour`] support painting `\[u8]` values, resulting in an [`ANSIByteString`]. -//! This type does not implement [`Display`], as it may not contain UTF-8, but -//! it does provide a method [`write_to`] to write the result to any value that -//! implements [`Write`]: +//! This library also handles formatting `[u8]` byte strings. This supports +//! applications working with text in an unknown encoding. More specifically, +//! any type which implements `AsRef<[u8]>` can be painted. For such types +//! [`ANSIString::write_to`] method is provided to write the value to any object +//! that implements [`Write`](std::io::Write): //! //! ``` //! use ansi_term::Colour::Green; //! -//! Green.paint("user data".as_bytes()).write_to(&mut std::io::stdout()).unwrap(); +//! Green.paint("user data".as_bytes()) +//! .write_to(&mut std::io::stdout()).unwrap(); //! ``` //! -//! Similarly, the type [`ANSIByteStrings`] supports writing a list of -//! [`ANSIByteString`] values with minimal escape sequences: -//! -//! ``` -//! use ansi_term::Colour::Green; -//! use ansi_term::ANSIByteStrings; +//! [`Cow`]: std::borrow::Cow +//! [`Display`]: core::fmt::Display +//! [`LowerHex`]: core::fmt::LowerHex //! -//! ANSIByteStrings(&[ -//! Green.paint("user data 1\n".as_bytes()), -//! Green.bold().paint("user data 2\n".as_bytes()), -//! ]).write_to(&mut std::io::stdout()).unwrap(); -//! ``` +//! [`normal`]: Colour::normal //! -//! [`Cow`]: https://doc.rust-lang.org/std/borrow/enum.Cow.html -//! [`Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html -//! [`Default`]: https://doc.rust-lang.org/std/default/trait.Default.html -//! [`String`]: https://doc.rust-lang.org/std/string/struct.String.html -//! [`&str`]: https://doc.rust-lang.org/std/primitive.str.html -//! [`Write`]: https://doc.rust-lang.org/std/io/trait.Write.html -//! [`Style`]: struct.Style.html -//! [`Style::new()`]: struct.Style.html#method.new -//! [`Color`]: enum.Color.html -//! [`Colour`]: enum.Colour.html -//! [`ANSIString`]: type.ANSIString.html -//! [`ANSIStrings`]: type.ANSIStrings.html -//! [`ANSIByteString`]: type.ANSIByteString.html -//! [`ANSIByteStrings`]: type.ANSIByteStrings.html -//! [`write_to`]: type.ANSIByteString.html#method.write_to -//! [`paint`]: type.ANSIByteString.html#method.write_to -//! [`normal`]: enum.Colour.html#method.normal -//! -//! [`bold`]: struct.Style.html#method.bold -//! [`dimmed`]: struct.Style.html#method.dimmed -//! [`italic`]: struct.Style.html#method.italic -//! [`underline`]: struct.Style.html#method.underline -//! [`blink`]: struct.Style.html#method.blink -//! [`reverse`]: struct.Style.html#method.reverse -//! [`hidden`]: struct.Style.html#method.hidden -//! [`strikethrough`]: struct.Style.html#method.strikethrough -//! [`fg`]: struct.Style.html#method.fg -//! [`on`]: struct.Style.html#method.on +//! [`bold`]: Style::bold +//! [`dimmed`]: Style::dimmed +//! [`italic`]: Style::italic +//! [`underline`]: Style::underline +//! [`blink`]: Style::blink +//! [`reverse`]: Style::reverse +//! [`hidden`]: Style::hidden +//! [`strikethrough`]: Style::strikethrough +//! [`fg`]: Style::fg +//! [`on`]: Style::on #![crate_name = "ansi_term"] #![crate_type = "rlib"] @@ -260,12 +246,9 @@ mod difference; mod display; pub use display::*; -mod write; - mod windows; pub use windows::*; -mod util; -pub use util::*; +pub mod substring; mod debug; diff --git a/src/substring.rs b/src/substring.rs new file mode 100644 index 0000000..912dbdc --- /dev/null +++ b/src/substring.rs @@ -0,0 +1,289 @@ +//! Implementation of the substring operation on [`ANSIStrings`]. +//! +//! See [`ANSIStrings::substring`] method. + +use std::borrow::Cow; +use std::ops::Range; +use std::iter; + +use display::{ANSIString, ANSIStrings}; + + +/// Iterator over a substring of an [`ANSIStrings`]. +/// +/// Created by [`ANSIStrings::substring`]. +/// +/// `S` generic argument correspond to generic argument of `ANSIStrings`. For +/// this type to be an iterator it `S` must implement [`Substringable`] trait. +pub struct Substring<'a, S: 'a> { + strings: &'a [ANSIString], + start: usize, + end: usize, +} + + +/// A value which implements substring operation. +pub trait Substringable { + /// Type returned when creating a substring of the value. + type Output : ?Sized; + + /// Returns length of the value. + fn len(&self) -> usize; + + /// Returns substring of the given value. + /// + /// **Panics** if range is out of bounds. + fn substr<'a>(&'a self, range: Range) -> &'a Self::Output; +} + + +// ---- Substringable ---- + +impl<'a> Substringable for &'a str { + type Output = str; + fn len(&self) -> usize { ::len(self) } + fn substr(&self, r: Range) -> &str { self.get(r).unwrap() } +} + +impl Substringable for String { + type Output = str; + fn len(&self) -> usize { String::len(self) } + fn substr(&self, r: Range) -> &str { self.get(r).unwrap() } +} + +impl<'a> Substringable for &'a [u8] { + type Output = [u8]; + fn len(&self) -> usize { <[u8]>::len(self) } + fn substr(&self, r: Range) -> &[u8] { self.get(r).unwrap() } +} + +impl Substringable for Vec { + type Output = [u8]; + fn len(&self) -> usize { Vec::len(self) } + fn substr(&self, r: Range) -> &[u8] { self.get(r).unwrap() } +} + +impl<'a, S> Substringable for Cow<'a, S> +where S: 'a + ToOwned + ?Sized + Substringable, + ::Owned: Substringable, +{ + type Output = S; + fn len(&self) -> usize { self.as_ref().len() } + fn substr(&self, range: Range) -> &S { + self.as_ref().substr(range) + } +} + + +// ---- Substring ---- + +impl<'a, S: Substringable> ANSIStrings<'a, S> { + /// Returns a substring with styles properly applied to remaining fragments. + /// + /// The substring is indexed on the positions within the strings excluding + /// the ANSI control sequences. That means a slice will never happen in the + /// middle of an ANSI code. Furthermore, leading and trailing strings will + /// be styled properly even if they are sliced. + /// + /// The substring is returned as an iterator with items borrowing from this + /// [`ANSIStrings`]. This means that creating a substring performs no + /// allocations but on the other hand it means that this objects cannot be + /// modified until the [`Substring`] iterator and all items it had returned + /// are dropped. + /// + /// Unlike [`str::get`] and other indexing methods, this method doesn’t + /// panic if `start` or `end` are out of bounds. Instead, if `start` is out + /// of bounds the resulting iterator will be empty and if `end` is out of + /// bounds, the iterator behaves as if it was unbounded. As such, to + /// simulate `start..` range, an end bound of `usize::MAX` can be used. + /// + /// Note however, that slicing of each leading or trailing fragment may + /// result in a panic. For example, if the slice happens in the middle of + /// an UTF-8 sequence on a `str`. + /// + /// # Examples + /// + /// ``` + /// use ansi_term::{ANSIStrings, Colour, Style}; + /// + /// let strings = [ + /// Style::new().paint("The quick "), + /// Colour::Yellow.paint("brown"), + /// Style::new().paint(" fox jumped over the lazy dog") + /// ]; + /// let strings = ANSIStrings(&strings[..]); + /// let fox = strings.substring(4..19).collect::>(); + /// let fox = ANSIStrings(fox.as_slice()); + /// assert_eq!("quick \u{1b}[33mbrown\u{1b}[0m fox", fox.to_string()); + /// ``` + pub fn substring( + &self, + range: Range, + ) -> Substring { + if range.end <= range.start { + Substring { strings: &[], start: 0, end: 0 } + } else { + let (idx, offset) = self.substring_start(range.start); + Substring { + strings: &self.0[idx..], + start: range.start - offset, + end: range.end - offset, + } + } + } + + /// Finds index of the fragment which contains `start` character. + /// + /// Returns (index, offset) tuple where index is the fragment that includes + /// character at `start` position and offset is total length of fragments + /// prior to fragment at given index. If `start` is out of bounds, returns + /// index one-past-the-last fragment. + fn substring_start( + &self, + start: usize + ) -> (usize, usize) { + let mut idx = 0; + let mut offset = 0; + while offset < start && idx < self.0.len() { + let len = self.0[idx].value.len(); + if start < offset + len { + break; + } + offset += len; + idx += 1; + } + (idx, offset) + } +} + +impl<'a, S: Substringable> Iterator for Substring<'a, S> { + type Item = ANSIString<&'a ::Output>; + + fn next(&mut self) -> Option { + let string = self.strings.get(0)?; + let len = string.value.len().min(self.end); + let string = ANSIString { + style: string.style, + value: string.value.substr(self.start..len), + }; + + self.start = 0; + self.end -= len; + // size_hint uses strings.is_empty to check if this is an empty iterator + // so update it to one if we’ve reached end bound. Otherwise, advance + // by one fragment. + self.strings = if self.end == 0 { + &[] + } else { + &self.strings[1..] + }; + + Some(string) + } + + fn size_hint(&self) -> (usize, Option) { + if self.strings.is_empty() { + (0, Some(0)) + } else { + (1, Some(self.strings.len())) + } + } +} + +impl<'a, S: Substringable> iter::FusedIterator for Substring<'a, S> {} + + +#[test] +fn test_substring() { + use crate::Colour; + + let strings = [ + Colour::Black.paint("foo"), + Colour::Red.paint("bar"), + Colour::White.paint("baz"), + ]; + let strings = ANSIStrings(&strings[..]); + let mut results = vec![]; + for start in 0..10 { + for end in start..11 { + let substring = strings + .substring(start..end) + .map(|fragment| fragment.to_string()) + .collect::(); + results.push(format!("{start}..{end:2} ‘{substring}’\n")); + } + } + let got = results + .into_iter() + .collect::() + .replace("\x1b[0m", ">") + .replace("\x1b[30m", "B<") + .replace("\x1b[31m", "R<") + .replace("\x1b[37m", "W<"); + assert_eq!("0.. 0 ‘’\n\ + 0.. 1 ‘B’\n\ + 0.. 2 ‘B’\n\ + 0.. 3 ‘B’\n\ + 0.. 4 ‘BR’\n\ + 0.. 5 ‘BR’\n\ + 0.. 6 ‘BR’\n\ + 0.. 7 ‘BRW’\n\ + 0.. 8 ‘BRW’\n\ + 0.. 9 ‘BRW’\n\ + 0..10 ‘BRW’\n\ + 1.. 1 ‘’\n\ + 1.. 2 ‘B’\n\ + 1.. 3 ‘B’\n\ + 1.. 4 ‘BR’\n\ + 1.. 5 ‘BR’\n\ + 1.. 6 ‘BR’\n\ + 1.. 7 ‘BRW’\n\ + 1.. 8 ‘BRW’\n\ + 1.. 9 ‘BRW’\n\ + 1..10 ‘BRW’\n\ + 2.. 2 ‘’\n\ + 2.. 3 ‘B’\n\ + 2.. 4 ‘BR’\n\ + 2.. 5 ‘BR’\n\ + 2.. 6 ‘BR’\n\ + 2.. 7 ‘BRW’\n\ + 2.. 8 ‘BRW’\n\ + 2.. 9 ‘BRW’\n\ + 2..10 ‘BRW’\n\ + 3.. 3 ‘’\n\ + 3.. 4 ‘R’\n\ + 3.. 5 ‘R’\n\ + 3.. 6 ‘R’\n\ + 3.. 7 ‘RW’\n\ + 3.. 8 ‘RW’\n\ + 3.. 9 ‘RW’\n\ + 3..10 ‘RW’\n\ + 4.. 4 ‘’\n\ + 4.. 5 ‘R’\n\ + 4.. 6 ‘R’\n\ + 4.. 7 ‘RW’\n\ + 4.. 8 ‘RW’\n\ + 4.. 9 ‘RW’\n\ + 4..10 ‘RW’\n\ + 5.. 5 ‘’\n\ + 5.. 6 ‘R’\n\ + 5.. 7 ‘RW’\n\ + 5.. 8 ‘RW’\n\ + 5.. 9 ‘RW’\n\ + 5..10 ‘RW’\n\ + 6.. 6 ‘’\n\ + 6.. 7 ‘W’\n\ + 6.. 8 ‘W’\n\ + 6.. 9 ‘W’\n\ + 6..10 ‘W’\n\ + 7.. 7 ‘’\n\ + 7.. 8 ‘W’\n\ + 7.. 9 ‘W’\n\ + 7..10 ‘W’\n\ + 8.. 8 ‘’\n\ + 8.. 9 ‘W’\n\ + 8..10 ‘W’\n\ + 9.. 9 ‘’\n\ + 9..10 ‘’\n\ + ", got); +} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index ba0f12a..0000000 --- a/src/util.rs +++ /dev/null @@ -1,81 +0,0 @@ -use display::*; -use std::ops::Deref; - -/// Return a substring of the given ANSIStrings sequence, while keeping the formatting. -pub fn sub_string<'a>(start: usize, len: usize, strs: &ANSIStrings<'a>) -> Vec> { - let mut vec = Vec::new(); - let mut pos = start; - let mut len_rem = len; - - for i in strs.0.iter() { - let fragment = i.deref(); - let frag_len = fragment.len(); - if pos >= frag_len { - pos -= frag_len; - continue; - } - if len_rem <= 0 { - break; - } - - let end = pos + len_rem; - let pos_end = if end >= frag_len { frag_len } else { end }; - - vec.push(i.style_ref().paint(String::from(&fragment[pos..pos_end]))); - - if end <= frag_len { - break; - } - - len_rem -= pos_end - pos; - pos = 0; - } - - vec -} - -/// Return a concatenated copy of `strs` without the formatting, as an allocated `String`. -pub fn unstyle(strs: &ANSIStrings) -> String { - let mut s = String::new(); - - for i in strs.0.iter() { - s += &i.deref(); - } - - s -} - -/// Return the unstyled length of ANSIStrings. This is equaivalent to `unstyle(strs).len()`. -pub fn unstyled_len(strs: &ANSIStrings) -> usize { - let mut l = 0; - for i in strs.0.iter() { - l += i.deref().len(); - } - l -} - -#[cfg(test)] -mod test { - use Colour::*; - use display::*; - use super::*; - - #[test] - fn test() { - let l = [ - Black.paint("first"), - Red.paint("-second"), - White.paint("-third"), - ]; - let a = ANSIStrings(&l); - assert_eq!(unstyle(&a), "first-second-third"); - assert_eq!(unstyled_len(&a), 18); - - let l2 = [ - Black.paint("st"), - Red.paint("-second"), - White.paint("-t"), - ]; - assert_eq!(sub_string(3, 11, &a).as_slice(), &l2); - } -} diff --git a/src/write.rs b/src/write.rs deleted file mode 100644 index 65a64fe..0000000 --- a/src/write.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::fmt; -use std::io; - - -pub trait AnyWrite { - type wstr: ?Sized; - type Error; - - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error>; - - fn write_str(&mut self, s: &Self::wstr) -> Result<(), Self::Error>; -} - - -impl<'a> AnyWrite for fmt::Write + 'a { - type wstr = str; - type Error = fmt::Error; - - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> { - fmt::Write::write_fmt(self, fmt) - } - - fn write_str(&mut self, s: &Self::wstr) -> Result<(), Self::Error> { - fmt::Write::write_str(self, s) - } -} - - -impl<'a> AnyWrite for io::Write + 'a { - type wstr = [u8]; - type Error = io::Error; - - fn write_fmt(&mut self, fmt: fmt::Arguments) -> Result<(), Self::Error> { - io::Write::write_fmt(self, fmt) - } - - fn write_str(&mut self, s: &Self::wstr) -> Result<(), Self::Error> { - io::Write::write_all(self, s) - } -}