diff options
Diffstat (limited to 'src/display.rs')
-rw-r--r-- | src/display.rs | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..17c54f0 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,296 @@ +use std::borrow::Cow; +use std::fmt; +use std::io; +use std::ops::Deref; + +use ansi::RESET; +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 <S as ToOwned>::Owned: fmt::Debug { + style: Style, + string: Cow<'a, S>, +} + + +/// Cloning an `ANSIGenericString` will clone its underlying string. +/// +/// # Examples +/// +/// ``` +/// use ansi_term::ANSIString; +/// +/// 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 <S as ToOwned>::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 +// <S as ToOwned>::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<ANSIString<'static>>); +// ^^^^^^^^^^^^^^^^^^^^^^^^^ +// 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. +/// +/// # Examples +/// +/// ``` +/// use ansi_term::ANSIString; +/// use ansi_term::Colour::Red; +/// +/// let red_string = Red.paint("a red string"); +/// println!("{}", red_string); +/// ``` +/// +/// ``` +/// use ansi_term::ANSIString; +/// +/// let plain_string = ANSIString::from("a plain string"); +/// assert_eq!(&*plain_string, "a plain string"); +/// ``` +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<I> for ANSIGenericString<'a, S> +where I: Into<Cow<'a, S>>, + <S as ToOwned>::Owned: fmt::Debug { + fn from(input: I) -> ANSIGenericString<'a, S> { + ANSIGenericString { + string: input.into(), + style: Style::default(), + } + } +} + +impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> + where <S as ToOwned>::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: 'a + ToOwned + ?Sized> Deref for ANSIGenericString<'a, S> +where <S as ToOwned>::Owned: fmt::Debug { + type Target = S; + + fn deref(&self) -> &S { + self.string.deref() + } +} + + +/// A set of `ANSIGenericString`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 <S as ToOwned>::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) +} + +/// 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) +} + + +// ---- paint functions ---- + +impl Style { + + /// 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<Cow<'a, S>>, + <S as ToOwned>::Owned: fmt::Debug { + ANSIGenericString { + string: input.into(), + style: self, + } + } +} + + +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. + /// + /// ``` + /// use ansi_term::Colour::Blue; + /// 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<Cow<'a, S>>, + <S as ToOwned>::Owned: fmt::Debug { + ANSIGenericString { + string: input.into(), + style: self.normal(), + } + } +} + + +// ---- writers for individual ANSI strings ---- + +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) + } +} + +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<W: io::Write>(&self, w: &mut W) -> io::Result<()> { + let w: &mut io::Write = w; + self.write_to_any(w) + } +} + +impl<'a, S: 'a + ToOwned + ?Sized> ANSIGenericString<'a, S> +where <S as ToOwned>::Owned: fmt::Debug, &'a S: AsRef<[u8]> { + fn write_to_any<W: AnyWrite<wstr=S> + ?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()) + } +} + + +// ---- writers for combined ANSI 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<'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. + pub fn write_to<W: io::Write>(&self, w: &mut W) -> io::Result<()> { + let w: &mut io::Write = w; + self.write_to_any(w) + } +} + +impl<'a, S: 'a + ToOwned + ?Sized + PartialEq> ANSIGenericStrings<'a, S> +where <S as ToOwned>::Owned: fmt::Debug, &'a S: AsRef<[u8]> { + fn write_to_any<W: AnyWrite<wstr=S> + ?Sized>(&self, w: &mut W) -> Result<(), W::Error> { + use self::Difference::*; + + let first = match self.0.first() { + None => return Ok(()), + Some(f) => f, + }; + + write!(w, "{}", first.style.prefix())?; + w.write_str(first.string.as_ref())?; + + 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)?; + } + + // 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)?; + } + } + + Ok(()) + } +} + + +// ---- tests ---- + +#[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"); + } +} |