| /*! |
| This crate provides a cross platform abstraction for writing colored text to |
| a terminal. Colors are written using either ANSI escape sequences or by |
| communicating with a Windows console. Much of this API was motivated by use |
| inside command line applications, where colors or styles can be configured |
| by the end user and/or the environment. |
| |
| This crate also provides platform independent support for writing colored text |
| to an in memory buffer. While this is easy to do with ANSI escape sequences |
| (because they are in the buffer themselves), it is trickier to do with the |
| Windows console API, which requires synchronous communication. |
| |
| # Organization |
| |
| The `WriteColor` trait extends the `io::Write` trait with methods for setting |
| colors or resetting them. |
| |
| `StandardStream` and `StandardStreamLock` both satisfy `WriteColor` and are |
| analogous to `std::io::Stdout` and `std::io::StdoutLock`, or `std::io::Stderr` |
| and `std::io::StderrLock`. |
| |
| `Buffer` is an in memory buffer that supports colored text. In a parallel |
| program, each thread might write to its own buffer. A buffer can be printed to |
| using a `BufferWriter`. The advantage of this design is that each thread can |
| work in parallel on a buffer without having to synchronize access to global |
| resources such as the Windows console. Moreover, this design also prevents |
| interleaving of buffer output. |
| |
| `Ansi` and `NoColor` both satisfy `WriteColor` for arbitrary implementors of |
| `io::Write`. These types are useful when you know exactly what you need. An |
| analogous type for the Windows console is not provided since it cannot exist. |
| |
| # Example: using `StandardStream` |
| |
| The `StandardStream` type in this crate works similarly to `std::io::Stdout`, |
| except it is augmented with methods for coloring by the `WriteColor` trait. |
| For example, to write some green text: |
| |
| ```rust,no_run |
| # fn test() -> Result<(), Box<::std::error::Error>> { |
| use std::io::Write; |
| use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; |
| |
| let mut stdout = StandardStream::stdout(ColorChoice::Always); |
| stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; |
| writeln!(&mut stdout, "green text!")?; |
| # Ok(()) } |
| ``` |
| |
| Note that any text written to the terminal now will be colored |
| green when using ANSI escape sequences, even if it is written via |
| stderr, and even if stderr had previously been set to `Color::Red`. |
| Users will need to manage any color changes themselves by calling |
| [`WriteColor::set_color`](trait.WriteColor.html#tymethod.set_color), and this |
| may include calling [`WriteColor::reset`](trait.WriteColor.html#tymethod.reset) |
| before the program exits to a shell. |
| |
| # Example: using `BufferWriter` |
| |
| A `BufferWriter` can create buffers and write buffers to stdout or stderr. It |
| does *not* implement `io::Write` or `WriteColor` itself. Instead, `Buffer` |
| implements `io::Write` and `io::WriteColor`. |
| |
| This example shows how to print some green text to stderr. |
| |
| ```rust,no_run |
| # fn test() -> Result<(), Box<::std::error::Error>> { |
| use std::io::Write; |
| use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; |
| |
| let mut bufwtr = BufferWriter::stderr(ColorChoice::Always); |
| let mut buffer = bufwtr.buffer(); |
| buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; |
| writeln!(&mut buffer, "green text!")?; |
| bufwtr.print(&buffer)?; |
| # Ok(()) } |
| ``` |
| |
| # Detecting presence of a terminal |
| |
| In many scenarios when using color, one often wants to enable colors |
| automatically when writing to a terminal and disable colors automatically when |
| writing to anything else. The typical way to achieve this in Unix environments |
| is via libc's |
| [`isatty`](http://man7.org/linux/man-pages/man3/isatty.3.html) |
| function. |
| Unfortunately, this notoriously does not work well in Windows environments. To |
| work around that, the currently recommended solution is to use the |
| [`atty`](https://crates.io/crates/atty) |
| crate, which goes out of its way to get this as right as possible in Windows |
| environments. |
| |
| For example, in a command line application that exposes a `--color` flag, |
| your logic for how to enable colors might look like this: |
| |
| ```rust,ignore |
| use atty; |
| use termcolor::{ColorChoice, StandardStream}; |
| |
| let preference = argv.get_flag("color").unwrap_or("auto"); |
| let choice = match preference { |
| "always" => ColorChoice::Always, |
| "ansi" => ColorChoice::AlwaysAnsi, |
| "auto" => { |
| if atty::is(atty::Stream::Stdout) { |
| ColorChoice::Auto |
| } else { |
| ColorChoice::Never |
| } |
| } |
| _ => ColorChoice::Never, |
| }; |
| let stdout = StandardStream::stdout(choice); |
| // ... write to stdout |
| ``` |
| |
| Currently, `termcolor` does not provide anything to do this for you. |
| */ |
| |
| #![deny(missing_docs)] |
| |
| #[cfg(test)] |
| use doc_comment::doctest; |
| #[cfg(test)] |
| doctest!("../README.md"); |
| |
| use std::env; |
| use std::error; |
| use std::fmt; |
| use std::io::{self, Write}; |
| use std::str::FromStr; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| #[cfg(windows)] |
| use std::sync::{Mutex, MutexGuard}; |
| |
| #[cfg(windows)] |
| use winapi_util::console as wincon; |
| |
| /// This trait describes the behavior of writers that support colored output. |
| pub trait WriteColor: io::Write { |
| /// Returns true if and only if the underlying writer supports colors. |
| fn supports_color(&self) -> bool; |
| |
| /// Set the color settings of the writer. |
| /// |
| /// Subsequent writes to this writer will use these settings until either |
| /// `reset` is called or new color settings are set. |
| /// |
| /// If there was a problem setting the color settings, then an error is |
| /// returned. |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()>; |
| |
| /// Reset the current color settings to their original settings. |
| /// |
| /// If there was a problem resetting the color settings, then an error is |
| /// returned. |
| fn reset(&mut self) -> io::Result<()>; |
| |
| /// Returns true if and only if the underlying writer must synchronously |
| /// interact with an end user's device in order to control colors. By |
| /// default, this always returns `false`. |
| /// |
| /// In practice, this should return `true` if the underlying writer is |
| /// manipulating colors using the Windows console APIs. |
| /// |
| /// This is useful for writing generic code (such as a buffered writer) |
| /// that can perform certain optimizations when the underlying writer |
| /// doesn't rely on synchronous APIs. For example, ANSI escape sequences |
| /// can be passed through to the end user's device as is. |
| fn is_synchronous(&self) -> bool { |
| false |
| } |
| } |
| |
| impl<'a, T: ?Sized + WriteColor> WriteColor for &'a mut T { |
| fn supports_color(&self) -> bool { |
| (&**self).supports_color() |
| } |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| (&mut **self).set_color(spec) |
| } |
| fn reset(&mut self) -> io::Result<()> { |
| (&mut **self).reset() |
| } |
| fn is_synchronous(&self) -> bool { |
| (&**self).is_synchronous() |
| } |
| } |
| |
| impl<T: ?Sized + WriteColor> WriteColor for Box<T> { |
| fn supports_color(&self) -> bool { |
| (&**self).supports_color() |
| } |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| (&mut **self).set_color(spec) |
| } |
| fn reset(&mut self) -> io::Result<()> { |
| (&mut **self).reset() |
| } |
| fn is_synchronous(&self) -> bool { |
| (&**self).is_synchronous() |
| } |
| } |
| |
| /// ColorChoice represents the color preferences of an end user. |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub enum ColorChoice { |
| /// Try very hard to emit colors. This includes emitting ANSI colors |
| /// on Windows if the console API is unavailable. |
| Always, |
| /// AlwaysAnsi is like Always, except it never tries to use anything other |
| /// than emitting ANSI color codes. |
| AlwaysAnsi, |
| /// Try to use colors, but don't force the issue. If the console isn't |
| /// available on Windows, or if TERM=dumb, or if `NO_COLOR` is defined, for |
| /// example, then don't use colors. |
| Auto, |
| /// Never emit colors. |
| Never, |
| } |
| |
| impl ColorChoice { |
| /// Returns true if we should attempt to write colored output. |
| fn should_attempt_color(&self) -> bool { |
| match *self { |
| ColorChoice::Always => true, |
| ColorChoice::AlwaysAnsi => true, |
| ColorChoice::Never => false, |
| ColorChoice::Auto => self.env_allows_color(), |
| } |
| } |
| |
| #[cfg(not(windows))] |
| fn env_allows_color(&self) -> bool { |
| match env::var_os("TERM") { |
| // If TERM isn't set, then we are in a weird environment that |
| // probably doesn't support colors. |
| None => return false, |
| Some(k) => { |
| if k == "dumb" { |
| return false; |
| } |
| } |
| } |
| // If TERM != dumb, then the only way we don't allow colors at this |
| // point is if NO_COLOR is set. |
| if env::var_os("NO_COLOR").is_some() { |
| return false; |
| } |
| true |
| } |
| |
| #[cfg(windows)] |
| fn env_allows_color(&self) -> bool { |
| // On Windows, if TERM isn't set, then we shouldn't automatically |
| // assume that colors aren't allowed. This is unlike Unix environments |
| // where TERM is more rigorously set. |
| if let Some(k) = env::var_os("TERM") { |
| if k == "dumb" { |
| return false; |
| } |
| } |
| // If TERM != dumb, then the only way we don't allow colors at this |
| // point is if NO_COLOR is set. |
| if env::var_os("NO_COLOR").is_some() { |
| return false; |
| } |
| true |
| } |
| |
| /// Returns true if this choice should forcefully use ANSI color codes. |
| /// |
| /// It's possible that ANSI is still the correct choice even if this |
| /// returns false. |
| #[cfg(windows)] |
| fn should_ansi(&self) -> bool { |
| match *self { |
| ColorChoice::Always => false, |
| ColorChoice::AlwaysAnsi => true, |
| ColorChoice::Never => false, |
| ColorChoice::Auto => { |
| match env::var("TERM") { |
| Err(_) => false, |
| // cygwin doesn't seem to support ANSI escape sequences |
| // and instead has its own variety. However, the Windows |
| // console API may be available. |
| Ok(k) => k != "dumb" && k != "cygwin", |
| } |
| } |
| } |
| } |
| } |
| |
| /// `std::io` implements `Stdout` and `Stderr` (and their `Lock` variants) as |
| /// separate types, which makes it difficult to abstract over them. We use |
| /// some simple internal enum types to work around this. |
| |
| enum StandardStreamType { |
| Stdout, |
| Stderr, |
| StdoutBuffered, |
| StderrBuffered, |
| } |
| |
| enum IoStandardStream { |
| Stdout(io::Stdout), |
| Stderr(io::Stderr), |
| StdoutBuffered(io::BufWriter<io::Stdout>), |
| StderrBuffered(io::BufWriter<io::Stderr>), |
| } |
| |
| impl IoStandardStream { |
| fn new(sty: StandardStreamType) -> IoStandardStream { |
| match sty { |
| StandardStreamType::Stdout => { |
| IoStandardStream::Stdout(io::stdout()) |
| } |
| StandardStreamType::Stderr => { |
| IoStandardStream::Stderr(io::stderr()) |
| } |
| StandardStreamType::StdoutBuffered => { |
| let wtr = io::BufWriter::new(io::stdout()); |
| IoStandardStream::StdoutBuffered(wtr) |
| } |
| StandardStreamType::StderrBuffered => { |
| let wtr = io::BufWriter::new(io::stderr()); |
| IoStandardStream::StderrBuffered(wtr) |
| } |
| } |
| } |
| |
| fn lock(&self) -> IoStandardStreamLock<'_> { |
| match *self { |
| IoStandardStream::Stdout(ref s) => { |
| IoStandardStreamLock::StdoutLock(s.lock()) |
| } |
| IoStandardStream::Stderr(ref s) => { |
| IoStandardStreamLock::StderrLock(s.lock()) |
| } |
| IoStandardStream::StdoutBuffered(_) |
| | IoStandardStream::StderrBuffered(_) => { |
| // We don't permit this case to ever occur in the public API, |
| // so it's OK to panic. |
| panic!("cannot lock a buffered standard stream") |
| } |
| } |
| } |
| } |
| |
| impl io::Write for IoStandardStream { |
| #[inline(always)] |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { |
| match *self { |
| IoStandardStream::Stdout(ref mut s) => s.write(b), |
| IoStandardStream::Stderr(ref mut s) => s.write(b), |
| IoStandardStream::StdoutBuffered(ref mut s) => s.write(b), |
| IoStandardStream::StderrBuffered(ref mut s) => s.write(b), |
| } |
| } |
| |
| #[inline(always)] |
| fn flush(&mut self) -> io::Result<()> { |
| match *self { |
| IoStandardStream::Stdout(ref mut s) => s.flush(), |
| IoStandardStream::Stderr(ref mut s) => s.flush(), |
| IoStandardStream::StdoutBuffered(ref mut s) => s.flush(), |
| IoStandardStream::StderrBuffered(ref mut s) => s.flush(), |
| } |
| } |
| } |
| |
| // Same rigmarole for the locked variants of the standard streams. |
| |
| enum IoStandardStreamLock<'a> { |
| StdoutLock(io::StdoutLock<'a>), |
| StderrLock(io::StderrLock<'a>), |
| } |
| |
| impl<'a> io::Write for IoStandardStreamLock<'a> { |
| #[inline(always)] |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { |
| match *self { |
| IoStandardStreamLock::StdoutLock(ref mut s) => s.write(b), |
| IoStandardStreamLock::StderrLock(ref mut s) => s.write(b), |
| } |
| } |
| |
| #[inline(always)] |
| fn flush(&mut self) -> io::Result<()> { |
| match *self { |
| IoStandardStreamLock::StdoutLock(ref mut s) => s.flush(), |
| IoStandardStreamLock::StderrLock(ref mut s) => s.flush(), |
| } |
| } |
| } |
| |
| /// Satisfies `io::Write` and `WriteColor`, and supports optional coloring |
| /// to either of the standard output streams, stdout and stderr. |
| pub struct StandardStream { |
| wtr: LossyStandardStream<WriterInner<IoStandardStream>>, |
| } |
| |
| /// `StandardStreamLock` is a locked reference to a `StandardStream`. |
| /// |
| /// This implements the `io::Write` and `WriteColor` traits, and is constructed |
| /// via the `Write::lock` method. |
| /// |
| /// The lifetime `'a` refers to the lifetime of the corresponding |
| /// `StandardStream`. |
| pub struct StandardStreamLock<'a> { |
| wtr: LossyStandardStream<WriterInnerLock<'a, IoStandardStreamLock<'a>>>, |
| } |
| |
| /// Like `StandardStream`, but does buffered writing. |
| pub struct BufferedStandardStream { |
| wtr: LossyStandardStream<WriterInner<IoStandardStream>>, |
| } |
| |
| /// WriterInner is a (limited) generic representation of a writer. It is |
| /// limited because W should only ever be stdout/stderr on Windows. |
| enum WriterInner<W> { |
| NoColor(NoColor<W>), |
| Ansi(Ansi<W>), |
| #[cfg(windows)] |
| Windows { |
| wtr: W, |
| console: Mutex<wincon::Console>, |
| }, |
| } |
| |
| /// WriterInnerLock is a (limited) generic representation of a writer. It is |
| /// limited because W should only ever be stdout/stderr on Windows. |
| enum WriterInnerLock<'a, W> { |
| NoColor(NoColor<W>), |
| Ansi(Ansi<W>), |
| /// What a gross hack. On Windows, we need to specify a lifetime for the |
| /// console when in a locked state, but obviously don't need to do that |
| /// on Unix, which makes the `'a` unused. To satisfy the compiler, we need |
| /// a PhantomData. |
| #[allow(dead_code)] |
| Unreachable(::std::marker::PhantomData<&'a ()>), |
| #[cfg(windows)] |
| Windows { |
| wtr: W, |
| console: MutexGuard<'a, wincon::Console>, |
| }, |
| } |
| |
| impl StandardStream { |
| /// Create a new `StandardStream` with the given color preferences that |
| /// writes to standard output. |
| /// |
| /// On Windows, if coloring is desired and a Windows console could not be |
| /// found, then ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing via |
| /// the `WriteColor` trait. |
| pub fn stdout(choice: ColorChoice) -> StandardStream { |
| let wtr = WriterInner::create(StandardStreamType::Stdout, choice); |
| StandardStream { wtr: LossyStandardStream::new(wtr) } |
| } |
| |
| /// Create a new `StandardStream` with the given color preferences that |
| /// writes to standard error. |
| /// |
| /// On Windows, if coloring is desired and a Windows console could not be |
| /// found, then ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing via |
| /// the `WriteColor` trait. |
| pub fn stderr(choice: ColorChoice) -> StandardStream { |
| let wtr = WriterInner::create(StandardStreamType::Stderr, choice); |
| StandardStream { wtr: LossyStandardStream::new(wtr) } |
| } |
| |
| /// Lock the underlying writer. |
| /// |
| /// The lock guard returned also satisfies `io::Write` and |
| /// `WriteColor`. |
| /// |
| /// This method is **not reentrant**. It may panic if `lock` is called |
| /// while a `StandardStreamLock` is still alive. |
| pub fn lock(&self) -> StandardStreamLock<'_> { |
| StandardStreamLock::from_stream(self) |
| } |
| } |
| |
| impl<'a> StandardStreamLock<'a> { |
| #[cfg(not(windows))] |
| fn from_stream(stream: &StandardStream) -> StandardStreamLock<'_> { |
| let locked = match *stream.wtr.get_ref() { |
| WriterInner::NoColor(ref w) => { |
| WriterInnerLock::NoColor(NoColor(w.0.lock())) |
| } |
| WriterInner::Ansi(ref w) => { |
| WriterInnerLock::Ansi(Ansi(w.0.lock())) |
| } |
| }; |
| StandardStreamLock { wtr: stream.wtr.wrap(locked) } |
| } |
| |
| #[cfg(windows)] |
| fn from_stream(stream: &StandardStream) -> StandardStreamLock { |
| let locked = match *stream.wtr.get_ref() { |
| WriterInner::NoColor(ref w) => { |
| WriterInnerLock::NoColor(NoColor(w.0.lock())) |
| } |
| WriterInner::Ansi(ref w) => { |
| WriterInnerLock::Ansi(Ansi(w.0.lock())) |
| } |
| #[cfg(windows)] |
| WriterInner::Windows { ref wtr, ref console } => { |
| WriterInnerLock::Windows { |
| wtr: wtr.lock(), |
| console: console.lock().unwrap(), |
| } |
| } |
| }; |
| StandardStreamLock { wtr: stream.wtr.wrap(locked) } |
| } |
| } |
| |
| impl BufferedStandardStream { |
| /// Create a new `BufferedStandardStream` with the given color preferences |
| /// that writes to standard output via a buffered writer. |
| /// |
| /// On Windows, if coloring is desired and a Windows console could not be |
| /// found, then ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing via |
| /// the `WriteColor` trait. |
| pub fn stdout(choice: ColorChoice) -> BufferedStandardStream { |
| let wtr = |
| WriterInner::create(StandardStreamType::StdoutBuffered, choice); |
| BufferedStandardStream { wtr: LossyStandardStream::new(wtr) } |
| } |
| |
| /// Create a new `BufferedStandardStream` with the given color preferences |
| /// that writes to standard error via a buffered writer. |
| /// |
| /// On Windows, if coloring is desired and a Windows console could not be |
| /// found, then ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing via |
| /// the `WriteColor` trait. |
| pub fn stderr(choice: ColorChoice) -> BufferedStandardStream { |
| let wtr = |
| WriterInner::create(StandardStreamType::StderrBuffered, choice); |
| BufferedStandardStream { wtr: LossyStandardStream::new(wtr) } |
| } |
| } |
| |
| impl WriterInner<IoStandardStream> { |
| /// Create a new inner writer for a standard stream with the given color |
| /// preferences. |
| #[cfg(not(windows))] |
| fn create( |
| sty: StandardStreamType, |
| choice: ColorChoice, |
| ) -> WriterInner<IoStandardStream> { |
| if choice.should_attempt_color() { |
| WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) |
| } else { |
| WriterInner::NoColor(NoColor(IoStandardStream::new(sty))) |
| } |
| } |
| |
| /// Create a new inner writer for a standard stream with the given color |
| /// preferences. |
| /// |
| /// If coloring is desired and a Windows console could not be found, then |
| /// ANSI escape sequences are used instead. |
| #[cfg(windows)] |
| fn create( |
| sty: StandardStreamType, |
| choice: ColorChoice, |
| ) -> WriterInner<IoStandardStream> { |
| let mut con = match sty { |
| StandardStreamType::Stdout => wincon::Console::stdout(), |
| StandardStreamType::Stderr => wincon::Console::stderr(), |
| StandardStreamType::StdoutBuffered => wincon::Console::stdout(), |
| StandardStreamType::StderrBuffered => wincon::Console::stderr(), |
| }; |
| let is_console_virtual = con |
| .as_mut() |
| .map(|con| con.set_virtual_terminal_processing(true).is_ok()) |
| .unwrap_or(false); |
| if choice.should_attempt_color() { |
| if choice.should_ansi() || is_console_virtual { |
| WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) |
| } else if let Ok(console) = con { |
| WriterInner::Windows { |
| wtr: IoStandardStream::new(sty), |
| console: Mutex::new(console), |
| } |
| } else { |
| WriterInner::Ansi(Ansi(IoStandardStream::new(sty))) |
| } |
| } else { |
| WriterInner::NoColor(NoColor(IoStandardStream::new(sty))) |
| } |
| } |
| } |
| |
| impl io::Write for StandardStream { |
| #[inline] |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { |
| self.wtr.write(b) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| self.wtr.flush() |
| } |
| } |
| |
| impl WriteColor for StandardStream { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| self.wtr.supports_color() |
| } |
| |
| #[inline] |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| self.wtr.set_color(spec) |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| self.wtr.reset() |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| self.wtr.is_synchronous() |
| } |
| } |
| |
| impl<'a> io::Write for StandardStreamLock<'a> { |
| #[inline] |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { |
| self.wtr.write(b) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| self.wtr.flush() |
| } |
| } |
| |
| impl<'a> WriteColor for StandardStreamLock<'a> { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| self.wtr.supports_color() |
| } |
| |
| #[inline] |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| self.wtr.set_color(spec) |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| self.wtr.reset() |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| self.wtr.is_synchronous() |
| } |
| } |
| |
| impl io::Write for BufferedStandardStream { |
| #[inline] |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { |
| self.wtr.write(b) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| self.wtr.flush() |
| } |
| } |
| |
| impl WriteColor for BufferedStandardStream { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| self.wtr.supports_color() |
| } |
| |
| #[inline] |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| if self.is_synchronous() { |
| self.wtr.flush()?; |
| } |
| self.wtr.set_color(spec) |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| self.wtr.reset() |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| self.wtr.is_synchronous() |
| } |
| } |
| |
| impl<W: io::Write> io::Write for WriterInner<W> { |
| #[inline(always)] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| match *self { |
| WriterInner::NoColor(ref mut wtr) => wtr.write(buf), |
| WriterInner::Ansi(ref mut wtr) => wtr.write(buf), |
| #[cfg(windows)] |
| WriterInner::Windows { ref mut wtr, .. } => wtr.write(buf), |
| } |
| } |
| |
| #[inline(always)] |
| fn flush(&mut self) -> io::Result<()> { |
| match *self { |
| WriterInner::NoColor(ref mut wtr) => wtr.flush(), |
| WriterInner::Ansi(ref mut wtr) => wtr.flush(), |
| #[cfg(windows)] |
| WriterInner::Windows { ref mut wtr, .. } => wtr.flush(), |
| } |
| } |
| } |
| |
| impl<W: io::Write> WriteColor for WriterInner<W> { |
| fn supports_color(&self) -> bool { |
| match *self { |
| WriterInner::NoColor(_) => false, |
| WriterInner::Ansi(_) => true, |
| #[cfg(windows)] |
| WriterInner::Windows { .. } => true, |
| } |
| } |
| |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| match *self { |
| WriterInner::NoColor(ref mut wtr) => wtr.set_color(spec), |
| WriterInner::Ansi(ref mut wtr) => wtr.set_color(spec), |
| #[cfg(windows)] |
| WriterInner::Windows { ref mut wtr, ref console } => { |
| wtr.flush()?; |
| let mut console = console.lock().unwrap(); |
| spec.write_console(&mut *console) |
| } |
| } |
| } |
| |
| fn reset(&mut self) -> io::Result<()> { |
| match *self { |
| WriterInner::NoColor(ref mut wtr) => wtr.reset(), |
| WriterInner::Ansi(ref mut wtr) => wtr.reset(), |
| #[cfg(windows)] |
| WriterInner::Windows { ref mut wtr, ref mut console } => { |
| wtr.flush()?; |
| console.lock().unwrap().reset()?; |
| Ok(()) |
| } |
| } |
| } |
| |
| fn is_synchronous(&self) -> bool { |
| match *self { |
| WriterInner::NoColor(_) => false, |
| WriterInner::Ansi(_) => false, |
| #[cfg(windows)] |
| WriterInner::Windows { .. } => true, |
| } |
| } |
| } |
| |
| impl<'a, W: io::Write> io::Write for WriterInnerLock<'a, W> { |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| match *self { |
| WriterInnerLock::Unreachable(_) => unreachable!(), |
| WriterInnerLock::NoColor(ref mut wtr) => wtr.write(buf), |
| WriterInnerLock::Ansi(ref mut wtr) => wtr.write(buf), |
| #[cfg(windows)] |
| WriterInnerLock::Windows { ref mut wtr, .. } => wtr.write(buf), |
| } |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| match *self { |
| WriterInnerLock::Unreachable(_) => unreachable!(), |
| WriterInnerLock::NoColor(ref mut wtr) => wtr.flush(), |
| WriterInnerLock::Ansi(ref mut wtr) => wtr.flush(), |
| #[cfg(windows)] |
| WriterInnerLock::Windows { ref mut wtr, .. } => wtr.flush(), |
| } |
| } |
| } |
| |
| impl<'a, W: io::Write> WriteColor for WriterInnerLock<'a, W> { |
| fn supports_color(&self) -> bool { |
| match *self { |
| WriterInnerLock::Unreachable(_) => unreachable!(), |
| WriterInnerLock::NoColor(_) => false, |
| WriterInnerLock::Ansi(_) => true, |
| #[cfg(windows)] |
| WriterInnerLock::Windows { .. } => true, |
| } |
| } |
| |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| match *self { |
| WriterInnerLock::Unreachable(_) => unreachable!(), |
| WriterInnerLock::NoColor(ref mut wtr) => wtr.set_color(spec), |
| WriterInnerLock::Ansi(ref mut wtr) => wtr.set_color(spec), |
| #[cfg(windows)] |
| WriterInnerLock::Windows { ref mut wtr, ref mut console } => { |
| wtr.flush()?; |
| spec.write_console(console) |
| } |
| } |
| } |
| |
| fn reset(&mut self) -> io::Result<()> { |
| match *self { |
| WriterInnerLock::Unreachable(_) => unreachable!(), |
| WriterInnerLock::NoColor(ref mut wtr) => wtr.reset(), |
| WriterInnerLock::Ansi(ref mut wtr) => wtr.reset(), |
| #[cfg(windows)] |
| WriterInnerLock::Windows { ref mut wtr, ref mut console } => { |
| wtr.flush()?; |
| console.reset()?; |
| Ok(()) |
| } |
| } |
| } |
| |
| fn is_synchronous(&self) -> bool { |
| match *self { |
| WriterInnerLock::Unreachable(_) => unreachable!(), |
| WriterInnerLock::NoColor(_) => false, |
| WriterInnerLock::Ansi(_) => false, |
| #[cfg(windows)] |
| WriterInnerLock::Windows { .. } => true, |
| } |
| } |
| } |
| |
| /// Writes colored buffers to stdout or stderr. |
| /// |
| /// Writable buffers can be obtained by calling `buffer` on a `BufferWriter`. |
| /// |
| /// This writer works with terminals that support ANSI escape sequences or |
| /// with a Windows console. |
| /// |
| /// It is intended for a `BufferWriter` to be put in an `Arc` and written to |
| /// from multiple threads simultaneously. |
| pub struct BufferWriter { |
| stream: LossyStandardStream<IoStandardStream>, |
| printed: AtomicBool, |
| separator: Option<Vec<u8>>, |
| color_choice: ColorChoice, |
| #[cfg(windows)] |
| console: Option<Mutex<wincon::Console>>, |
| } |
| |
| impl BufferWriter { |
| /// Create a new `BufferWriter` that writes to a standard stream with the |
| /// given color preferences. |
| /// |
| /// The specific color/style settings can be configured when writing to |
| /// the buffers themselves. |
| #[cfg(not(windows))] |
| fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter { |
| BufferWriter { |
| stream: LossyStandardStream::new(IoStandardStream::new(sty)), |
| printed: AtomicBool::new(false), |
| separator: None, |
| color_choice: choice, |
| } |
| } |
| |
| /// Create a new `BufferWriter` that writes to a standard stream with the |
| /// given color preferences. |
| /// |
| /// If coloring is desired and a Windows console could not be found, then |
| /// ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing to |
| /// the buffers themselves. |
| #[cfg(windows)] |
| fn create(sty: StandardStreamType, choice: ColorChoice) -> BufferWriter { |
| let mut con = match sty { |
| StandardStreamType::Stdout => wincon::Console::stdout(), |
| StandardStreamType::Stderr => wincon::Console::stderr(), |
| StandardStreamType::StdoutBuffered => wincon::Console::stdout(), |
| StandardStreamType::StderrBuffered => wincon::Console::stderr(), |
| } |
| .ok(); |
| let is_console_virtual = con |
| .as_mut() |
| .map(|con| con.set_virtual_terminal_processing(true).is_ok()) |
| .unwrap_or(false); |
| // If we can enable ANSI on Windows, then we don't need the console |
| // anymore. |
| if is_console_virtual { |
| con = None; |
| } |
| let stream = LossyStandardStream::new(IoStandardStream::new(sty)); |
| BufferWriter { |
| stream: stream, |
| printed: AtomicBool::new(false), |
| separator: None, |
| color_choice: choice, |
| console: con.map(Mutex::new), |
| } |
| } |
| |
| /// Create a new `BufferWriter` that writes to stdout with the given |
| /// color preferences. |
| /// |
| /// On Windows, if coloring is desired and a Windows console could not be |
| /// found, then ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing to |
| /// the buffers themselves. |
| pub fn stdout(choice: ColorChoice) -> BufferWriter { |
| BufferWriter::create(StandardStreamType::Stdout, choice) |
| } |
| |
| /// Create a new `BufferWriter` that writes to stderr with the given |
| /// color preferences. |
| /// |
| /// On Windows, if coloring is desired and a Windows console could not be |
| /// found, then ANSI escape sequences are used instead. |
| /// |
| /// The specific color/style settings can be configured when writing to |
| /// the buffers themselves. |
| pub fn stderr(choice: ColorChoice) -> BufferWriter { |
| BufferWriter::create(StandardStreamType::Stderr, choice) |
| } |
| |
| /// If set, the separator given is printed between buffers. By default, no |
| /// separator is printed. |
| /// |
| /// The default value is `None`. |
| pub fn separator(&mut self, sep: Option<Vec<u8>>) { |
| self.separator = sep; |
| } |
| |
| /// Creates a new `Buffer` with the current color preferences. |
| /// |
| /// A `Buffer` satisfies both `io::Write` and `WriteColor`. A `Buffer` can |
| /// be printed using the `print` method. |
| #[cfg(not(windows))] |
| pub fn buffer(&self) -> Buffer { |
| Buffer::new(self.color_choice) |
| } |
| |
| /// Creates a new `Buffer` with the current color preferences. |
| /// |
| /// A `Buffer` satisfies both `io::Write` and `WriteColor`. A `Buffer` can |
| /// be printed using the `print` method. |
| #[cfg(windows)] |
| pub fn buffer(&self) -> Buffer { |
| Buffer::new(self.color_choice, self.console.is_some()) |
| } |
| |
| /// Prints the contents of the given buffer. |
| /// |
| /// It is safe to call this from multiple threads simultaneously. In |
| /// particular, all buffers are written atomically. No interleaving will |
| /// occur. |
| pub fn print(&self, buf: &Buffer) -> io::Result<()> { |
| if buf.is_empty() { |
| return Ok(()); |
| } |
| let mut stream = self.stream.wrap(self.stream.get_ref().lock()); |
| if let Some(ref sep) = self.separator { |
| if self.printed.load(Ordering::SeqCst) { |
| stream.write_all(sep)?; |
| stream.write_all(b"\n")?; |
| } |
| } |
| match buf.0 { |
| BufferInner::NoColor(ref b) => stream.write_all(&b.0)?, |
| BufferInner::Ansi(ref b) => stream.write_all(&b.0)?, |
| #[cfg(windows)] |
| BufferInner::Windows(ref b) => { |
| // We guarantee by construction that we have a console here. |
| // Namely, a BufferWriter is the only way to produce a Buffer. |
| let console_mutex = self |
| .console |
| .as_ref() |
| .expect("got Windows buffer but have no Console"); |
| let mut console = console_mutex.lock().unwrap(); |
| b.print(&mut *console, &mut stream)?; |
| } |
| } |
| self.printed.store(true, Ordering::SeqCst); |
| Ok(()) |
| } |
| } |
| |
| /// Write colored text to memory. |
| /// |
| /// `Buffer` is a platform independent abstraction for printing colored text to |
| /// an in memory buffer. When the buffer is printed using a `BufferWriter`, the |
| /// color information will be applied to the output device (a tty on Unix and a |
| /// console on Windows). |
| /// |
| /// A `Buffer` is typically created by calling the `BufferWriter.buffer` |
| /// method, which will take color preferences and the environment into |
| /// account. However, buffers can also be manually created using `no_color`, |
| /// `ansi` or `console` (on Windows). |
| pub struct Buffer(BufferInner); |
| |
| /// BufferInner is an enumeration of different buffer types. |
| enum BufferInner { |
| /// No coloring information should be applied. This ignores all coloring |
| /// directives. |
| NoColor(NoColor<Vec<u8>>), |
| /// Apply coloring using ANSI escape sequences embedded into the buffer. |
| Ansi(Ansi<Vec<u8>>), |
| /// Apply coloring using the Windows console APIs. This buffer saves |
| /// color information in memory and only interacts with the console when |
| /// the buffer is printed. |
| #[cfg(windows)] |
| Windows(WindowsBuffer), |
| } |
| |
| impl Buffer { |
| /// Create a new buffer with the given color settings. |
| #[cfg(not(windows))] |
| fn new(choice: ColorChoice) -> Buffer { |
| if choice.should_attempt_color() { |
| Buffer::ansi() |
| } else { |
| Buffer::no_color() |
| } |
| } |
| |
| /// Create a new buffer with the given color settings. |
| /// |
| /// On Windows, one can elect to create a buffer capable of being written |
| /// to a console. Only enable it if a console is available. |
| /// |
| /// If coloring is desired and `console` is false, then ANSI escape |
| /// sequences are used instead. |
| #[cfg(windows)] |
| fn new(choice: ColorChoice, console: bool) -> Buffer { |
| if choice.should_attempt_color() { |
| if !console || choice.should_ansi() { |
| Buffer::ansi() |
| } else { |
| Buffer::console() |
| } |
| } else { |
| Buffer::no_color() |
| } |
| } |
| |
| /// Create a buffer that drops all color information. |
| pub fn no_color() -> Buffer { |
| Buffer(BufferInner::NoColor(NoColor(vec![]))) |
| } |
| |
| /// Create a buffer that uses ANSI escape sequences. |
| pub fn ansi() -> Buffer { |
| Buffer(BufferInner::Ansi(Ansi(vec![]))) |
| } |
| |
| /// Create a buffer that can be written to a Windows console. |
| #[cfg(windows)] |
| pub fn console() -> Buffer { |
| Buffer(BufferInner::Windows(WindowsBuffer::new())) |
| } |
| |
| /// Returns true if and only if this buffer is empty. |
| pub fn is_empty(&self) -> bool { |
| self.len() == 0 |
| } |
| |
| /// Returns the length of this buffer in bytes. |
| pub fn len(&self) -> usize { |
| match self.0 { |
| BufferInner::NoColor(ref b) => b.0.len(), |
| BufferInner::Ansi(ref b) => b.0.len(), |
| #[cfg(windows)] |
| BufferInner::Windows(ref b) => b.buf.len(), |
| } |
| } |
| |
| /// Clears this buffer. |
| pub fn clear(&mut self) { |
| match self.0 { |
| BufferInner::NoColor(ref mut b) => b.0.clear(), |
| BufferInner::Ansi(ref mut b) => b.0.clear(), |
| #[cfg(windows)] |
| BufferInner::Windows(ref mut b) => b.clear(), |
| } |
| } |
| |
| /// Consume this buffer and return the underlying raw data. |
| /// |
| /// On Windows, this unrecoverably drops all color information associated |
| /// with the buffer. |
| pub fn into_inner(self) -> Vec<u8> { |
| match self.0 { |
| BufferInner::NoColor(b) => b.0, |
| BufferInner::Ansi(b) => b.0, |
| #[cfg(windows)] |
| BufferInner::Windows(b) => b.buf, |
| } |
| } |
| |
| /// Return the underlying data of the buffer. |
| pub fn as_slice(&self) -> &[u8] { |
| match self.0 { |
| BufferInner::NoColor(ref b) => &b.0, |
| BufferInner::Ansi(ref b) => &b.0, |
| #[cfg(windows)] |
| BufferInner::Windows(ref b) => &b.buf, |
| } |
| } |
| |
| /// Return the underlying data of the buffer as a mutable slice. |
| pub fn as_mut_slice(&mut self) -> &mut [u8] { |
| match self.0 { |
| BufferInner::NoColor(ref mut b) => &mut b.0, |
| BufferInner::Ansi(ref mut b) => &mut b.0, |
| #[cfg(windows)] |
| BufferInner::Windows(ref mut b) => &mut b.buf, |
| } |
| } |
| } |
| |
| impl io::Write for Buffer { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| match self.0 { |
| BufferInner::NoColor(ref mut w) => w.write(buf), |
| BufferInner::Ansi(ref mut w) => w.write(buf), |
| #[cfg(windows)] |
| BufferInner::Windows(ref mut w) => w.write(buf), |
| } |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| match self.0 { |
| BufferInner::NoColor(ref mut w) => w.flush(), |
| BufferInner::Ansi(ref mut w) => w.flush(), |
| #[cfg(windows)] |
| BufferInner::Windows(ref mut w) => w.flush(), |
| } |
| } |
| } |
| |
| impl WriteColor for Buffer { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| match self.0 { |
| BufferInner::NoColor(_) => false, |
| BufferInner::Ansi(_) => true, |
| #[cfg(windows)] |
| BufferInner::Windows(_) => true, |
| } |
| } |
| |
| #[inline] |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| match self.0 { |
| BufferInner::NoColor(ref mut w) => w.set_color(spec), |
| BufferInner::Ansi(ref mut w) => w.set_color(spec), |
| #[cfg(windows)] |
| BufferInner::Windows(ref mut w) => w.set_color(spec), |
| } |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| match self.0 { |
| BufferInner::NoColor(ref mut w) => w.reset(), |
| BufferInner::Ansi(ref mut w) => w.reset(), |
| #[cfg(windows)] |
| BufferInner::Windows(ref mut w) => w.reset(), |
| } |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| false |
| } |
| } |
| |
| /// Satisfies `WriteColor` but ignores all color options. |
| pub struct NoColor<W>(W); |
| |
| impl<W: Write> NoColor<W> { |
| /// Create a new writer that satisfies `WriteColor` but drops all color |
| /// information. |
| pub fn new(wtr: W) -> NoColor<W> { |
| NoColor(wtr) |
| } |
| |
| /// Consume this `NoColor` value and return the inner writer. |
| pub fn into_inner(self) -> W { |
| self.0 |
| } |
| |
| /// Return a reference to the inner writer. |
| pub fn get_ref(&self) -> &W { |
| &self.0 |
| } |
| |
| /// Return a mutable reference to the inner writer. |
| pub fn get_mut(&mut self) -> &mut W { |
| &mut self.0 |
| } |
| } |
| |
| impl<W: io::Write> io::Write for NoColor<W> { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| self.0.write(buf) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| self.0.flush() |
| } |
| } |
| |
| impl<W: io::Write> WriteColor for NoColor<W> { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| false |
| } |
| |
| #[inline] |
| fn set_color(&mut self, _: &ColorSpec) -> io::Result<()> { |
| Ok(()) |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| Ok(()) |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| false |
| } |
| } |
| |
| /// Satisfies `WriteColor` using standard ANSI escape sequences. |
| pub struct Ansi<W>(W); |
| |
| impl<W: Write> Ansi<W> { |
| /// Create a new writer that satisfies `WriteColor` using standard ANSI |
| /// escape sequences. |
| pub fn new(wtr: W) -> Ansi<W> { |
| Ansi(wtr) |
| } |
| |
| /// Consume this `Ansi` value and return the inner writer. |
| pub fn into_inner(self) -> W { |
| self.0 |
| } |
| |
| /// Return a reference to the inner writer. |
| pub fn get_ref(&self) -> &W { |
| &self.0 |
| } |
| |
| /// Return a mutable reference to the inner writer. |
| pub fn get_mut(&mut self) -> &mut W { |
| &mut self.0 |
| } |
| } |
| |
| impl<W: io::Write> io::Write for Ansi<W> { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| self.0.write(buf) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| self.0.flush() |
| } |
| } |
| |
| impl<W: io::Write> WriteColor for Ansi<W> { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| true |
| } |
| |
| #[inline] |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| if spec.reset { |
| self.reset()?; |
| } |
| if spec.bold { |
| self.write_str("\x1B[1m")?; |
| } |
| if spec.italic { |
| self.write_str("\x1B[3m")?; |
| } |
| if spec.underline { |
| self.write_str("\x1B[4m")?; |
| } |
| if let Some(ref c) = spec.fg_color { |
| self.write_color(true, c, spec.intense)?; |
| } |
| if let Some(ref c) = spec.bg_color { |
| self.write_color(false, c, spec.intense)?; |
| } |
| Ok(()) |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| self.write_str("\x1B[0m") |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| false |
| } |
| } |
| |
| impl<W: io::Write> Ansi<W> { |
| fn write_str(&mut self, s: &str) -> io::Result<()> { |
| self.write_all(s.as_bytes()) |
| } |
| |
| fn write_color( |
| &mut self, |
| fg: bool, |
| c: &Color, |
| intense: bool, |
| ) -> io::Result<()> { |
| macro_rules! write_intense { |
| ($clr:expr) => { |
| if fg { |
| self.write_str(concat!("\x1B[38;5;", $clr, "m")) |
| } else { |
| self.write_str(concat!("\x1B[48;5;", $clr, "m")) |
| } |
| }; |
| } |
| macro_rules! write_normal { |
| ($clr:expr) => { |
| if fg { |
| self.write_str(concat!("\x1B[3", $clr, "m")) |
| } else { |
| self.write_str(concat!("\x1B[4", $clr, "m")) |
| } |
| }; |
| } |
| macro_rules! write_var_ansi_code { |
| ($pre:expr, $($code:expr),+) => {{ |
| // The loop generates at worst a literal of the form |
| // '255,255,255m' which is 12-bytes. |
| // The largest `pre` expression we currently use is 7 bytes. |
| // This gives us the maximum of 19-bytes for our work buffer. |
| let pre_len = $pre.len(); |
| assert!(pre_len <= 7); |
| let mut fmt = [0u8; 19]; |
| fmt[..pre_len].copy_from_slice($pre); |
| let mut i = pre_len - 1; |
| $( |
| let c1: u8 = ($code / 100) % 10; |
| let c2: u8 = ($code / 10) % 10; |
| let c3: u8 = $code % 10; |
| let mut printed = false; |
| |
| if c1 != 0 { |
| printed = true; |
| i += 1; |
| fmt[i] = b'0' + c1; |
| } |
| if c2 != 0 || printed { |
| i += 1; |
| fmt[i] = b'0' + c2; |
| } |
| // If we received a zero value we must still print a value. |
| i += 1; |
| fmt[i] = b'0' + c3; |
| i += 1; |
| fmt[i] = b';'; |
| )+ |
| |
| fmt[i] = b'm'; |
| self.write_all(&fmt[0..i+1]) |
| }} |
| } |
| macro_rules! write_custom { |
| ($ansi256:expr) => { |
| if fg { |
| write_var_ansi_code!(b"\x1B[38;5;", $ansi256) |
| } else { |
| write_var_ansi_code!(b"\x1B[48;5;", $ansi256) |
| } |
| }; |
| |
| ($r:expr, $g:expr, $b:expr) => {{ |
| if fg { |
| write_var_ansi_code!(b"\x1B[38;2;", $r, $g, $b) |
| } else { |
| write_var_ansi_code!(b"\x1B[48;2;", $r, $g, $b) |
| } |
| }}; |
| } |
| if intense { |
| match *c { |
| Color::Black => write_intense!("8"), |
| Color::Blue => write_intense!("12"), |
| Color::Green => write_intense!("10"), |
| Color::Red => write_intense!("9"), |
| Color::Cyan => write_intense!("14"), |
| Color::Magenta => write_intense!("13"), |
| Color::Yellow => write_intense!("11"), |
| Color::White => write_intense!("15"), |
| Color::Ansi256(c) => write_custom!(c), |
| Color::Rgb(r, g, b) => write_custom!(r, g, b), |
| Color::__Nonexhaustive => unreachable!(), |
| } |
| } else { |
| match *c { |
| Color::Black => write_normal!("0"), |
| Color::Blue => write_normal!("4"), |
| Color::Green => write_normal!("2"), |
| Color::Red => write_normal!("1"), |
| Color::Cyan => write_normal!("6"), |
| Color::Magenta => write_normal!("5"), |
| Color::Yellow => write_normal!("3"), |
| Color::White => write_normal!("7"), |
| Color::Ansi256(c) => write_custom!(c), |
| Color::Rgb(r, g, b) => write_custom!(r, g, b), |
| Color::__Nonexhaustive => unreachable!(), |
| } |
| } |
| } |
| } |
| |
| /// An in-memory buffer that provides Windows console coloring. |
| /// |
| /// This doesn't actually communicate with the Windows console. Instead, it |
| /// acts like a normal buffer but also saves the color information associated |
| /// with positions in the buffer. It is only when the buffer is written to the |
| /// console that coloring is actually applied. |
| /// |
| /// This is roughly isomorphic to the ANSI based approach (i.e., |
| /// `Ansi<Vec<u8>>`), except with ANSI, the color information is embedded |
| /// directly into the buffer. |
| /// |
| /// Note that there is no way to write something generic like |
| /// `WindowsConsole<W: io::Write>` since coloring on Windows is tied |
| /// specifically to the console APIs, and therefore can't work on arbitrary |
| /// writers. |
| #[cfg(windows)] |
| #[derive(Clone, Debug)] |
| struct WindowsBuffer { |
| /// The actual content that should be printed. |
| buf: Vec<u8>, |
| /// A sequence of position oriented color specifications. Namely, each |
| /// element is a position and a color spec, where the color spec should |
| /// be applied at the position inside of `buf`. |
| /// |
| /// A missing color spec implies the underlying console should be reset. |
| colors: Vec<(usize, Option<ColorSpec>)>, |
| } |
| |
| #[cfg(windows)] |
| impl WindowsBuffer { |
| /// Create a new empty buffer for Windows console coloring. |
| fn new() -> WindowsBuffer { |
| WindowsBuffer { buf: vec![], colors: vec![] } |
| } |
| |
| /// Push the given color specification into this buffer. |
| /// |
| /// This has the effect of setting the given color information at the |
| /// current position in the buffer. |
| fn push(&mut self, spec: Option<ColorSpec>) { |
| let pos = self.buf.len(); |
| self.colors.push((pos, spec)); |
| } |
| |
| /// Print the contents to the given stream handle, and use the console |
| /// for coloring. |
| fn print( |
| &self, |
| console: &mut wincon::Console, |
| stream: &mut LossyStandardStream<IoStandardStreamLock>, |
| ) -> io::Result<()> { |
| let mut last = 0; |
| for &(pos, ref spec) in &self.colors { |
| stream.write_all(&self.buf[last..pos])?; |
| stream.flush()?; |
| last = pos; |
| match *spec { |
| None => console.reset()?, |
| Some(ref spec) => spec.write_console(console)?, |
| } |
| } |
| stream.write_all(&self.buf[last..])?; |
| stream.flush() |
| } |
| |
| /// Clear the buffer. |
| fn clear(&mut self) { |
| self.buf.clear(); |
| self.colors.clear(); |
| } |
| } |
| |
| #[cfg(windows)] |
| impl io::Write for WindowsBuffer { |
| #[inline] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| self.buf.extend_from_slice(buf); |
| Ok(buf.len()) |
| } |
| |
| #[inline] |
| fn flush(&mut self) -> io::Result<()> { |
| Ok(()) |
| } |
| } |
| |
| #[cfg(windows)] |
| impl WriteColor for WindowsBuffer { |
| #[inline] |
| fn supports_color(&self) -> bool { |
| true |
| } |
| |
| #[inline] |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| self.push(Some(spec.clone())); |
| Ok(()) |
| } |
| |
| #[inline] |
| fn reset(&mut self) -> io::Result<()> { |
| self.push(None); |
| Ok(()) |
| } |
| |
| #[inline] |
| fn is_synchronous(&self) -> bool { |
| false |
| } |
| } |
| |
| /// A color specification. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct ColorSpec { |
| fg_color: Option<Color>, |
| bg_color: Option<Color>, |
| bold: bool, |
| intense: bool, |
| underline: bool, |
| italic: bool, |
| reset: bool, |
| } |
| |
| impl Default for ColorSpec { |
| fn default() -> ColorSpec { |
| ColorSpec { |
| fg_color: None, |
| bg_color: None, |
| bold: false, |
| intense: false, |
| underline: false, |
| italic: false, |
| reset: true, |
| } |
| } |
| } |
| |
| impl ColorSpec { |
| /// Create a new color specification that has no colors or styles. |
| pub fn new() -> ColorSpec { |
| ColorSpec::default() |
| } |
| |
| /// Get the foreground color. |
| pub fn fg(&self) -> Option<&Color> { |
| self.fg_color.as_ref() |
| } |
| |
| /// Set the foreground color. |
| pub fn set_fg(&mut self, color: Option<Color>) -> &mut ColorSpec { |
| self.fg_color = color; |
| self |
| } |
| |
| /// Get the background color. |
| pub fn bg(&self) -> Option<&Color> { |
| self.bg_color.as_ref() |
| } |
| |
| /// Set the background color. |
| pub fn set_bg(&mut self, color: Option<Color>) -> &mut ColorSpec { |
| self.bg_color = color; |
| self |
| } |
| |
| /// Get whether this is bold or not. |
| /// |
| /// Note that the bold setting has no effect in a Windows console. |
| pub fn bold(&self) -> bool { |
| self.bold |
| } |
| |
| /// Set whether the text is bolded or not. |
| /// |
| /// Note that the bold setting has no effect in a Windows console. |
| pub fn set_bold(&mut self, yes: bool) -> &mut ColorSpec { |
| self.bold = yes; |
| self |
| } |
| |
| /// Get whether this is italic or not. |
| /// |
| /// Note that the italic setting has no effect in a Windows console. |
| pub fn italic(&self) -> bool { |
| self.italic |
| } |
| |
| /// Set whether the text is italicized or not. |
| /// |
| /// Note that the italic setting has no effect in a Windows console. |
| pub fn set_italic(&mut self, yes: bool) -> &mut ColorSpec { |
| self.italic = yes; |
| self |
| } |
| |
| /// Get whether this is underline or not. |
| /// |
| /// Note that the underline setting has no effect in a Windows console. |
| pub fn underline(&self) -> bool { |
| self.underline |
| } |
| |
| /// Set whether the text is underlined or not. |
| /// |
| /// Note that the underline setting has no effect in a Windows console. |
| pub fn set_underline(&mut self, yes: bool) -> &mut ColorSpec { |
| self.underline = yes; |
| self |
| } |
| |
| /// Get whether reset is enabled or not. |
| /// |
| /// reset is enabled by default. When disabled and using ANSI escape |
| /// sequences, a "reset" code will be emitted every time a `ColorSpec`'s |
| /// settings are applied. |
| /// |
| /// Note that the reset setting has no effect in a Windows console. |
| pub fn reset(&self) -> bool { |
| self.reset |
| } |
| |
| /// Set whether to reset the terminal whenever color settings are applied. |
| /// |
| /// reset is enabled by default. When disabled and using ANSI escape |
| /// sequences, a "reset" code will be emitted every time a `ColorSpec`'s |
| /// settings are applied. |
| /// |
| /// Typically this is useful if callers have a requirement to more |
| /// scrupulously manage the exact sequence of escape codes that are emitted |
| /// when using ANSI for colors. |
| /// |
| /// Note that the reset setting has no effect in a Windows console. |
| pub fn set_reset(&mut self, yes: bool) -> &mut ColorSpec { |
| self.reset = yes; |
| self |
| } |
| |
| /// Get whether this is intense or not. |
| /// |
| /// On Unix-like systems, this will output the ANSI escape sequence |
| /// that will print a high-intensity version of the color |
| /// specified. |
| /// |
| /// On Windows systems, this will output the ANSI escape sequence |
| /// that will print a brighter version of the color specified. |
| pub fn intense(&self) -> bool { |
| self.intense |
| } |
| |
| /// Set whether the text is intense or not. |
| /// |
| /// On Unix-like systems, this will output the ANSI escape sequence |
| /// that will print a high-intensity version of the color |
| /// specified. |
| /// |
| /// On Windows systems, this will output the ANSI escape sequence |
| /// that will print a brighter version of the color specified. |
| pub fn set_intense(&mut self, yes: bool) -> &mut ColorSpec { |
| self.intense = yes; |
| self |
| } |
| |
| /// Returns true if this color specification has no colors or styles. |
| pub fn is_none(&self) -> bool { |
| self.fg_color.is_none() |
| && self.bg_color.is_none() |
| && !self.bold |
| && !self.underline |
| && !self.italic |
| && !self.intense |
| } |
| |
| /// Clears this color specification so that it has no color/style settings. |
| pub fn clear(&mut self) { |
| self.fg_color = None; |
| self.bg_color = None; |
| self.bold = false; |
| self.underline = false; |
| self.intense = false; |
| self.italic = false; |
| } |
| |
| /// Writes this color spec to the given Windows console. |
| #[cfg(windows)] |
| fn write_console(&self, console: &mut wincon::Console) -> io::Result<()> { |
| let fg_color = self.fg_color.and_then(|c| c.to_windows(self.intense)); |
| if let Some((intense, color)) = fg_color { |
| console.fg(intense, color)?; |
| } |
| let bg_color = self.bg_color.and_then(|c| c.to_windows(self.intense)); |
| if let Some((intense, color)) = bg_color { |
| console.bg(intense, color)?; |
| } |
| Ok(()) |
| } |
| } |
| |
| /// The set of available colors for the terminal foreground/background. |
| /// |
| /// The `Ansi256` and `Rgb` colors will only output the correct codes when |
| /// paired with the `Ansi` `WriteColor` implementation. |
| /// |
| /// The `Ansi256` and `Rgb` color types are not supported when writing colors |
| /// on Windows using the console. If they are used on Windows, then they are |
| /// silently ignored and no colors will be emitted. |
| /// |
| /// This set may expand over time. |
| /// |
| /// This type has a `FromStr` impl that can parse colors from their human |
| /// readable form. The format is as follows: |
| /// |
| /// 1. Any of the explicitly listed colors in English. They are matched |
| /// case insensitively. |
| /// 2. A single 8-bit integer, in either decimal or hexadecimal format. |
| /// 3. A triple of 8-bit integers separated by a comma, where each integer is |
| /// in decimal or hexadecimal format. |
| /// |
| /// Hexadecimal numbers are written with a `0x` prefix. |
| #[allow(missing_docs)] |
| #[derive(Clone, Copy, Debug, Eq, PartialEq)] |
| pub enum Color { |
| Black, |
| Blue, |
| Green, |
| Red, |
| Cyan, |
| Magenta, |
| Yellow, |
| White, |
| Ansi256(u8), |
| Rgb(u8, u8, u8), |
| #[doc(hidden)] |
| __Nonexhaustive, |
| } |
| |
| impl Color { |
| /// Translate this color to a wincon::Color. |
| #[cfg(windows)] |
| fn to_windows( |
| self, |
| intense: bool, |
| ) -> Option<(wincon::Intense, wincon::Color)> { |
| use wincon::Intense::{No, Yes}; |
| |
| let color = match self { |
| Color::Black => wincon::Color::Black, |
| Color::Blue => wincon::Color::Blue, |
| Color::Green => wincon::Color::Green, |
| Color::Red => wincon::Color::Red, |
| Color::Cyan => wincon::Color::Cyan, |
| Color::Magenta => wincon::Color::Magenta, |
| Color::Yellow => wincon::Color::Yellow, |
| Color::White => wincon::Color::White, |
| Color::Ansi256(0) => return Some((No, wincon::Color::Black)), |
| Color::Ansi256(1) => return Some((No, wincon::Color::Red)), |
| Color::Ansi256(2) => return Some((No, wincon::Color::Green)), |
| Color::Ansi256(3) => return Some((No, wincon::Color::Yellow)), |
| Color::Ansi256(4) => return Some((No, wincon::Color::Blue)), |
| Color::Ansi256(5) => return Some((No, wincon::Color::Magenta)), |
| Color::Ansi256(6) => return Some((No, wincon::Color::Cyan)), |
| Color::Ansi256(7) => return Some((No, wincon::Color::White)), |
| Color::Ansi256(8) => return Some((Yes, wincon::Color::Black)), |
| Color::Ansi256(9) => return Some((Yes, wincon::Color::Red)), |
| Color::Ansi256(10) => return Some((Yes, wincon::Color::Green)), |
| Color::Ansi256(11) => return Some((Yes, wincon::Color::Yellow)), |
| Color::Ansi256(12) => return Some((Yes, wincon::Color::Blue)), |
| Color::Ansi256(13) => return Some((Yes, wincon::Color::Magenta)), |
| Color::Ansi256(14) => return Some((Yes, wincon::Color::Cyan)), |
| Color::Ansi256(15) => return Some((Yes, wincon::Color::White)), |
| Color::Ansi256(_) => return None, |
| Color::Rgb(_, _, _) => return None, |
| Color::__Nonexhaustive => unreachable!(), |
| }; |
| let intense = if intense { Yes } else { No }; |
| Some((intense, color)) |
| } |
| |
| /// Parses a numeric color string, either ANSI or RGB. |
| fn from_str_numeric(s: &str) -> Result<Color, ParseColorError> { |
| // The "ansi256" format is a single number (decimal or hex) |
| // corresponding to one of 256 colors. |
| // |
| // The "rgb" format is a triple of numbers (decimal or hex) delimited |
| // by a comma corresponding to one of 256^3 colors. |
| |
| fn parse_number(s: &str) -> Option<u8> { |
| use std::u8; |
| |
| if s.starts_with("0x") { |
| u8::from_str_radix(&s[2..], 16).ok() |
| } else { |
| u8::from_str_radix(s, 10).ok() |
| } |
| } |
| |
| let codes: Vec<&str> = s.split(',').collect(); |
| if codes.len() == 1 { |
| if let Some(n) = parse_number(&codes[0]) { |
| Ok(Color::Ansi256(n)) |
| } else { |
| if s.chars().all(|c| c.is_digit(16)) { |
| Err(ParseColorError { |
| kind: ParseColorErrorKind::InvalidAnsi256, |
| given: s.to_string(), |
| }) |
| } else { |
| Err(ParseColorError { |
| kind: ParseColorErrorKind::InvalidName, |
| given: s.to_string(), |
| }) |
| } |
| } |
| } else if codes.len() == 3 { |
| let mut v = vec![]; |
| for code in codes { |
| let n = parse_number(code).ok_or_else(|| ParseColorError { |
| kind: ParseColorErrorKind::InvalidRgb, |
| given: s.to_string(), |
| })?; |
| v.push(n); |
| } |
| Ok(Color::Rgb(v[0], v[1], v[2])) |
| } else { |
| Err(if s.contains(",") { |
| ParseColorError { |
| kind: ParseColorErrorKind::InvalidRgb, |
| given: s.to_string(), |
| } |
| } else { |
| ParseColorError { |
| kind: ParseColorErrorKind::InvalidName, |
| given: s.to_string(), |
| } |
| }) |
| } |
| } |
| } |
| |
| /// An error from parsing an invalid color specification. |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| pub struct ParseColorError { |
| kind: ParseColorErrorKind, |
| given: String, |
| } |
| |
| #[derive(Clone, Debug, Eq, PartialEq)] |
| enum ParseColorErrorKind { |
| InvalidName, |
| InvalidAnsi256, |
| InvalidRgb, |
| } |
| |
| impl ParseColorError { |
| /// Return the string that couldn't be parsed as a valid color. |
| pub fn invalid(&self) -> &str { |
| &self.given |
| } |
| } |
| |
| impl error::Error for ParseColorError { |
| fn description(&self) -> &str { |
| use self::ParseColorErrorKind::*; |
| match self.kind { |
| InvalidName => "unrecognized color name", |
| InvalidAnsi256 => "invalid ansi256 color number", |
| InvalidRgb => "invalid RGB color triple", |
| } |
| } |
| } |
| |
| impl fmt::Display for ParseColorError { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| use self::ParseColorErrorKind::*; |
| match self.kind { |
| InvalidName => write!( |
| f, |
| "unrecognized color name '{}'. Choose from: \ |
| black, blue, green, red, cyan, magenta, yellow, \ |
| white", |
| self.given |
| ), |
| InvalidAnsi256 => write!( |
| f, |
| "unrecognized ansi256 color number, \ |
| should be '[0-255]' (or a hex number), but is '{}'", |
| self.given |
| ), |
| InvalidRgb => write!( |
| f, |
| "unrecognized RGB color triple, \ |
| should be '[0-255],[0-255],[0-255]' (or a hex \ |
| triple), but is '{}'", |
| self.given |
| ), |
| } |
| } |
| } |
| |
| impl FromStr for Color { |
| type Err = ParseColorError; |
| |
| fn from_str(s: &str) -> Result<Color, ParseColorError> { |
| match &*s.to_lowercase() { |
| "black" => Ok(Color::Black), |
| "blue" => Ok(Color::Blue), |
| "green" => Ok(Color::Green), |
| "red" => Ok(Color::Red), |
| "cyan" => Ok(Color::Cyan), |
| "magenta" => Ok(Color::Magenta), |
| "yellow" => Ok(Color::Yellow), |
| "white" => Ok(Color::White), |
| _ => Color::from_str_numeric(s), |
| } |
| } |
| } |
| |
| struct LossyStandardStream<W> { |
| wtr: W, |
| #[cfg(windows)] |
| is_console: bool, |
| } |
| |
| impl<W: io::Write> LossyStandardStream<W> { |
| #[cfg(not(windows))] |
| fn new(wtr: W) -> LossyStandardStream<W> { |
| LossyStandardStream { wtr: wtr } |
| } |
| |
| #[cfg(windows)] |
| fn new(wtr: W) -> LossyStandardStream<W> { |
| let is_console = wincon::Console::stdout().is_ok() |
| || wincon::Console::stderr().is_ok(); |
| LossyStandardStream { wtr: wtr, is_console: is_console } |
| } |
| |
| #[cfg(not(windows))] |
| fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStandardStream<Q> { |
| LossyStandardStream::new(wtr) |
| } |
| |
| #[cfg(windows)] |
| fn wrap<Q: io::Write>(&self, wtr: Q) -> LossyStandardStream<Q> { |
| LossyStandardStream { wtr: wtr, is_console: self.is_console } |
| } |
| |
| fn get_ref(&self) -> &W { |
| &self.wtr |
| } |
| } |
| |
| impl<W: WriteColor> WriteColor for LossyStandardStream<W> { |
| fn supports_color(&self) -> bool { |
| self.wtr.supports_color() |
| } |
| fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> { |
| self.wtr.set_color(spec) |
| } |
| fn reset(&mut self) -> io::Result<()> { |
| self.wtr.reset() |
| } |
| fn is_synchronous(&self) -> bool { |
| self.wtr.is_synchronous() |
| } |
| } |
| |
| impl<W: io::Write> io::Write for LossyStandardStream<W> { |
| #[cfg(not(windows))] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| self.wtr.write(buf) |
| } |
| |
| #[cfg(windows)] |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| if self.is_console { |
| write_lossy_utf8(&mut self.wtr, buf) |
| } else { |
| self.wtr.write(buf) |
| } |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| self.wtr.flush() |
| } |
| } |
| |
| #[cfg(windows)] |
| fn write_lossy_utf8<W: io::Write>(mut w: W, buf: &[u8]) -> io::Result<usize> { |
| match ::std::str::from_utf8(buf) { |
| Ok(s) => w.write(s.as_bytes()), |
| Err(ref e) if e.valid_up_to() == 0 => { |
| w.write(b"\xEF\xBF\xBD")?; |
| Ok(1) |
| } |
| Err(e) => w.write(&buf[..e.valid_up_to()]), |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::{ |
| Ansi, Color, ColorSpec, ParseColorError, ParseColorErrorKind, |
| StandardStream, WriteColor, |
| }; |
| |
| fn assert_is_send<T: Send>() {} |
| |
| #[test] |
| fn standard_stream_is_send() { |
| assert_is_send::<StandardStream>(); |
| } |
| |
| #[test] |
| fn test_simple_parse_ok() { |
| let color = "green".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Green)); |
| } |
| |
| #[test] |
| fn test_256_parse_ok() { |
| let color = "7".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Ansi256(7))); |
| |
| let color = "32".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Ansi256(32))); |
| |
| let color = "0xFF".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Ansi256(0xFF))); |
| } |
| |
| #[test] |
| fn test_256_parse_err_out_of_range() { |
| let color = "256".parse::<Color>(); |
| assert_eq!( |
| color, |
| Err(ParseColorError { |
| kind: ParseColorErrorKind::InvalidAnsi256, |
| given: "256".to_string(), |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_rgb_parse_ok() { |
| let color = "0,0,0".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); |
| |
| let color = "0,128,255".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Rgb(0, 128, 255))); |
| |
| let color = "0x0,0x0,0x0".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Rgb(0, 0, 0))); |
| |
| let color = "0x33,0x66,0xFF".parse::<Color>(); |
| assert_eq!(color, Ok(Color::Rgb(0x33, 0x66, 0xFF))); |
| } |
| |
| #[test] |
| fn test_rgb_parse_err_out_of_range() { |
| let color = "0,0,256".parse::<Color>(); |
| assert_eq!( |
| color, |
| Err(ParseColorError { |
| kind: ParseColorErrorKind::InvalidRgb, |
| given: "0,0,256".to_string(), |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_rgb_parse_err_bad_format() { |
| let color = "0,0".parse::<Color>(); |
| assert_eq!( |
| color, |
| Err(ParseColorError { |
| kind: ParseColorErrorKind::InvalidRgb, |
| given: "0,0".to_string(), |
| }) |
| ); |
| |
| let color = "not_a_color".parse::<Color>(); |
| assert_eq!( |
| color, |
| Err(ParseColorError { |
| kind: ParseColorErrorKind::InvalidName, |
| given: "not_a_color".to_string(), |
| }) |
| ); |
| } |
| |
| #[test] |
| fn test_var_ansi_write_rgb() { |
| let mut buf = Ansi::new(vec![]); |
| let _ = buf.write_color(true, &Color::Rgb(254, 253, 255), false); |
| assert_eq!(buf.0, b"\x1B[38;2;254;253;255m"); |
| } |
| |
| #[test] |
| fn test_reset() { |
| let spec = ColorSpec::new(); |
| let mut buf = Ansi::new(vec![]); |
| buf.set_color(&spec).unwrap(); |
| assert_eq!(buf.0, b"\x1B[0m"); |
| } |
| |
| #[test] |
| fn test_no_reset() { |
| let mut spec = ColorSpec::new(); |
| spec.set_reset(false); |
| |
| let mut buf = Ansi::new(vec![]); |
| buf.set_color(&spec).unwrap(); |
| assert_eq!(buf.0, b""); |
| } |
| |
| #[test] |
| fn test_var_ansi_write_256() { |
| let mut buf = Ansi::new(vec![]); |
| let _ = buf.write_color(false, &Color::Ansi256(7), false); |
| assert_eq!(buf.0, b"\x1B[48;5;7m"); |
| |
| let mut buf = Ansi::new(vec![]); |
| let _ = buf.write_color(false, &Color::Ansi256(208), false); |
| assert_eq!(buf.0, b"\x1B[48;5;208m"); |
| } |
| |
| fn all_attributes() -> Vec<ColorSpec> { |
| let mut result = vec![]; |
| for fg in vec![None, Some(Color::Red)] { |
| for bg in vec![None, Some(Color::Red)] { |
| for bold in vec![false, true] { |
| for underline in vec![false, true] { |
| for intense in vec![false, true] { |
| for italic in vec![false, true] { |
| let mut color = ColorSpec::new(); |
| color.set_fg(fg); |
| color.set_bg(bg); |
| color.set_bold(bold); |
| color.set_underline(underline); |
| color.set_intense(intense); |
| color.set_italic(italic); |
| result.push(color); |
| } |
| } |
| } |
| } |
| } |
| } |
| result |
| } |
| |
| #[test] |
| fn test_is_none() { |
| for (i, color) in all_attributes().iter().enumerate() { |
| assert_eq!( |
| i == 0, |
| color.is_none(), |
| "{:?} => {}", |
| color, |
| color.is_none() |
| ) |
| } |
| } |
| |
| #[test] |
| fn test_clear() { |
| for color in all_attributes() { |
| let mut color1 = color.clone(); |
| color1.clear(); |
| assert!(color1.is_none(), "{:?} => {:?}", color, color1); |
| } |
| } |
| } |