summaryrefslogtreecommitdiffstats
path: root/termcolor
diff options
context:
space:
mode:
authorAndrew Gallant <jamslam@gmail.com>2016-11-20 11:14:52 -0500
committerAndrew Gallant <jamslam@gmail.com>2016-11-20 11:14:52 -0500
commite8a30cb8934106e63f9d0938c547e535acb7b888 (patch)
treef1f7bb102ddcfc858c9ff65d04c34ec46810986d /termcolor
parent03f76053228b646a7febed3853ab28f42b83b1ad (diff)
Completely re-work colored output and tty handling.
This commit completely guts all of the color handling code and replaces most of it with two new crates: wincolor and termcolor. wincolor provides a simple API to coloring using the Windows console and termcolor provides a platform independent coloring API tuned for multithreaded command line programs. This required a lot more flexibility than what the `term` crate provided, so it was dropped. We instead switch to writing ANSI escape sequences directly and ignore the TERMINFO database. In addition to fixing several bugs, this commit also permits end users to customize colors to a certain extent. For example, this command will set the match color to magenta and the line number background to yellow: rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo For tty handling, we've adopted a hack from `git` to do tty detection in MSYS/mintty terminals. As a result, ripgrep should get both color detection and piping correct on Windows regardless of which terminal you use. Finally, switch to line buffering. Performance doesn't seem to be impacted and it's an otherwise more user friendly option. Fixes #37, Fixes #51, Fixes #94, Fixes #117, Fixes #182, Fixes #231
Diffstat (limited to 'termcolor')
-rw-r--r--termcolor/Cargo.toml20
-rw-r--r--termcolor/README.md88
-rw-r--r--termcolor/src/lib.rs1071
3 files changed, 1179 insertions, 0 deletions
diff --git a/termcolor/Cargo.toml b/termcolor/Cargo.toml
new file mode 100644
index 00000000..2814a6ac
--- /dev/null
+++ b/termcolor/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+name = "termcolor"
+version = "0.1.0" #:version
+authors = ["Andrew Gallant <jamslam@gmail.com>"]
+description = """
+A simple cross platform library for writing colored text to a terminal.
+"""
+documentation = "https://docs.rs/termcolor"
+homepage = "https://github.com/BurntSushi/ripgrep/tree/master/termcolor"
+repository = "https://github.com/BurntSushi/ripgrep/tree/master/termcolor"
+readme = "README.md"
+keywords = ["windows", "win", "color", "ansi", "console"]
+license = "Unlicense/MIT"
+
+[lib]
+name = "termcolor"
+bench = false
+
+[target.'cfg(windows)'.dependencies]
+wincolor = { version = "0.1.0", path = "../wincolor" }
diff --git a/termcolor/README.md b/termcolor/README.md
new file mode 100644
index 00000000..98601590
--- /dev/null
+++ b/termcolor/README.md
@@ -0,0 +1,88 @@
+termcolor
+=========
+A simple cross platform library for writing colored text to a terminal. This
+library writes colored text either using standard ANSI escape sequences or
+by interacting with the Windows console. Several convenient abstractions
+are provided for use in single-threaded or multi-threaded command line
+applications.
+
+[![Windows build status](https://ci.appveyor.com/api/projects/status/github/BurntSushi/ripgrep?svg=true)](https://ci.appveyor.com/project/BurntSushi/ripgrep)
+[![](https://img.shields.io/crates/v/wincolor.svg)](https://crates.io/crates/wincolor)
+
+[![Linux build status](https://api.travis-ci.org/BurntSushi/ripgrep.png)](https://travis-ci.org/BurntSushi/ripgrep)
+[![Windows build status](https://ci.appveyor.com/api/projects/status/github/BurntSushi/ripgrep?svg=true)](https://ci.appveyor.com/project/BurntSushi/ripgrep)
+[![](https://img.shields.io/crates/v/termcolor.svg)](https://crates.io/crates/termcolor)
+
+Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
+
+### Documentation
+
+[https://docs.rs/termcolor](https://docs.rs/termcolor)
+
+### Usage
+
+Add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+termcolor = "0.1"
+```
+
+and this to your crate root:
+
+```rust
+extern crate termcolor;
+```
+
+### Organization
+
+The `WriteColor` trait extends the `io::Write` trait with methods for setting
+colors or resetting them.
+
+`Stdout` and `StdoutLock` both satisfy `WriteColor` and are analogous to
+`std::io::Stdout` and `std::io::StdoutLock`.
+
+`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 stdout 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 `Stdout`
+
+The `Stdout` 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
+use std::io::Write;
+use termcolor::{Color, ColorChoice, ColorSpec, Stdout, WriteColor};
+
+let mut stdout = Stdout::new(ColorChoice::Always);
+try!(stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
+try!(writeln!(&mut stdout, "green text!"));
+```
+
+### Example: using `BufferWriter`
+
+A `BufferWriter` can create buffers and write buffers to stdout. 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 stdout.
+
+```rust
+use std::io::Write;
+use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
+
+let mut bufwtr = BufferWriter::stdout(ColorChoice::Always);
+let mut buffer = bufwtr.buffer();
+try!(buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
+try!(writeln!(&mut buffer, "green text!"));
+try!(bufwtr.print(&buffer));
+```
diff --git a/termcolor/src/lib.rs b/termcolor/src/lib.rs
new file mode 100644
index 00000000..efaffbd2
--- /dev/null
+++ b/termcolor/src/lib.rs
@@ -0,0 +1,1071 @@
+/*!
+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.
+
+`Stdout` and `StdoutLock` both satisfy `WriteColor` and are analogous to
+`std::io::Stdout` and `std::io::StdoutLock`.
+
+`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 stdout 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 `Stdout`
+
+The `Stdout` 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, Stdout, WriteColor};
+
+let mut stdout = Stdout::new(ColorChoice::Always);
+try!(stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
+try!(writeln!(&mut stdout, "green text!"));
+# Ok(()) }
+```
+
+# Example: using `BufferWriter`
+
+A `BufferWriter` can create buffers and write buffers to stdout. 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 stdout.
+
+```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::stdout(ColorChoice::Always);
+let mut buffer = bufwtr.buffer();
+try!(buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green))));
+try!(writeln!(&mut buffer, "green text!"));
+try!(bufwtr.print(&buffer));
+# Ok(()) }
+```
+*/
+#![deny(missing_docs)]
+
+#[cfg(windows)]
+extern crate wincolor;
+
+use std::env;
+use std::error;
+use std::fmt;
+use std::io::{self, Write};
+use std::str::FromStr;
+#[cfg(windows)]
+use std::sync::{Mutex, MutexGuard};
+use std::sync::atomic::{AtomicBool, Ordering};
+
+/// 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<()>;
+}
+
+impl<'a, T: 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() }
+}
+
+/// 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, for example, then don't use
+ /// colors.
+ Auto,
+ /// Never emit colors.
+ Never,
+}
+
+impl ColorChoice {
+ /// Returns true if we should attempt to write colored output.
+ #[cfg(not(windows))]
+ fn should_attempt_color(&self) -> bool {
+ match *self {
+ ColorChoice::Always => true,
+ ColorChoice::AlwaysAnsi => true,
+ ColorChoice::Never => false,
+ ColorChoice::Auto => {
+ match env::var("TERM") {
+ Err(_) => false,
+ Ok(k) => k != "dumb",
+ }
+ }
+ }
+ }
+
+ /// Returns true if we should attempt to write colored output.
+ #[cfg(windows)]
+ fn should_attempt_color(&self) -> bool {
+ match *self {
+ ColorChoice::Always => true,
+ ColorChoice::AlwaysAnsi => true,
+ ColorChoice::Never => false,
+ ColorChoice::Auto => {
+ match env::var("TERM") {
+ Err(_) => true,
+ Ok(k) => k != "dumb",
+ }
+ }
+ }
+ }
+
+ /// 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",
+ }
+ }
+ }
+ }
+}
+
+/// Satisfies `io::Write` and `WriteColor`, and supports optional coloring
+/// to stdout.
+pub struct Stdout {
+ wtr: WriterInner<'static, io::Stdout>,
+}
+
+/// `StdoutLock` is a locked reference to a `Stdout`.
+///
+/// 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 `Stdout`.
+pub struct StdoutLock<'a> {
+ wtr: WriterInner<'a, io::StdoutLock<'a>>,
+}
+
+/// WriterInner is a (limited) generic representation of a writer. It is
+/// limited because W should only ever be stdout/stderr on Windows.
+enum WriterInner<'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 make 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: Mutex<wincolor::Console> },
+ #[cfg(windows)]
+ WindowsLocked { wtr: W, console: MutexGuard<'a, wincolor::Console> },
+}
+
+impl Stdout {
+ /// Create a new `Stdout` with the given color preferences.
+ ///
+ /// The specific color/style settings can be configured when writing via
+ /// the `WriteColor` trait.
+ #[cfg(not(windows))]
+ pub fn new(choice: ColorChoice) -> Stdout {
+ let wtr =
+ if choice.should_attempt_color() {
+ WriterInner::Ansi(Ansi(io::stdout()))
+ } else {
+ WriterInner::NoColor(NoColor(io::stdout()))
+ };
+ Stdout { wtr: wtr }
+ }
+
+ /// Create a new `Stdout` 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 via
+ /// the `WriteColor` trait.
+ #[cfg(windows)]
+ pub fn new(choice: ColorChoice) -> Stdout {
+ let wtr =
+ if choice.should_attempt_color() {
+ if choice.should_ansi() {
+ WriterInner::Ansi(Ansi(io::stdout()))
+ } else if let Ok(console) = wincolor::Console::stdout() {
+ WriterInner::Windows {
+ wtr: io::stdout(),
+ console: Mutex::new(console),
+ }
+ } else {
+ WriterInner::Ansi(Ansi(io::stdout()))
+ }
+ } else {
+ WriterInner::NoColor(NoColor(io::stdout()))
+ };
+ Stdout { wtr: 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 `StdoutLock` is still alive.
+ pub fn lock(&self) -> StdoutLock {
+ let locked = match self.wtr {
+ WriterInner::Unreachable(_) => unreachable!(),
+ WriterInner::NoColor(ref w) => {
+ WriterInner::NoColor(NoColor(w.0.lock()))
+ }
+ WriterInner::Ansi(ref w) => {
+ WriterInner::Ansi(Ansi(w.0.lock()))
+ }
+ #[cfg(windows)]
+ WriterInner::Windows { ref wtr, ref console } => {
+ WriterInner::WindowsLocked {
+ wtr: wtr.lock(),
+ console: console.lock().unwrap(),
+ }
+ }
+ #[cfg(windows)]
+ WriterInner::WindowsLocked{..} => {
+ panic!("cannot call Stdout.lock while a StdoutLock is alive");
+ }
+ };
+ StdoutLock { wtr: locked }
+ }
+}
+
+impl io::Write for Stdout {
+ fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.wtr.write(b) }
+ fn flush(&mut self) -> io::Result<()> { self.wtr.flush() }
+}
+
+impl WriteColor for Stdout {
+ 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() }
+}
+
+impl<'a> io::Write for StdoutLock<'a> {
+ fn write(&mut self, b: &[u8]) -> io::Result<usize> { self.wtr.write(b) }
+ fn flush(&mut self) -> io::Result<()> { self.wtr.flush() }
+}
+
+impl<'a> WriteColor for StdoutLock<'a> {
+ 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() }
+}
+
+impl<'a, W: io::Write> io::Write for WriterInner<'a, W> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ match *self {
+ WriterInner::Unreachable(_) => unreachable!(),
+ 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),
+ #[cfg(windows)]
+ WriterInner::WindowsLocked { ref mut wtr, .. } => wtr.write(buf),
+ }
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ match *self {
+ WriterInner::Unreachable(_) => unreachable!(),
+ WriterInner::NoColor(ref mut wtr) => wtr.flush(),
+ WriterInner::Ansi(ref mut wtr) => wtr.flush(),
+ #[cfg(windows)]
+ WriterInner::Windows { ref mut wtr, .. } => wtr.flush(),
+ #[cfg(windows)]
+ WriterInner::WindowsLocked { ref mut wtr, .. } => wtr.flush(),
+ }
+ }
+}
+
+impl<'a, W: io::Write> WriteColor for WriterInner<'a, W> {
+ fn supports_color(&self) -> bool {
+ match *self {
+ WriterInner::Unreachable(_) => unreachable!(),
+ WriterInner::NoColor(_) => false,
+ WriterInner::Ansi(_) => true,
+ #[cfg(windows)]
+ WriterInner::Windows { .. } => true,
+ #[cfg(windows)]
+ WriterInner::WindowsLocked { .. } => true,
+ }
+ }
+
+ fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
+ match *self {
+ WriterInner::Unreachable(_) => unreachable!(),
+ 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 } => {
+ try!(wtr.flush());
+ let mut console = console.lock().unwrap();
+ spec.write_console(&mut *console)
+ }
+ #[cfg(windows)]
+ WriterInner::WindowsLocked { ref mut wtr, ref mut console } => {
+ try!(wtr.flush());
+ spec.write_console(console)
+ }
+ }
+ }
+
+ fn reset(&mut self) -> io::Result<()> {
+ match *self {
+ WriterInner::Unreachable(_) => unreachable!(),
+ WriterInner::NoColor(ref mut wtr) => wtr.reset(),
+ WriterInner::Ansi(ref mut wtr) => wtr.reset(),
+ #[cfg(windows)]
+ WriterInner::Windows { ref mut wtr, ref mut console } => {
+ try!(wtr.flush());
+ try!(console.lock().unwrap().reset());
+ Ok(())
+ }
+ #[cfg(windows)]
+ WriterInner::WindowsLocked { ref mut wtr, ref mut console } => {
+ try!(wtr.flush());
+ try!(console.reset());
+ Ok(())
+ }
+ }
+ }
+}
+
+/// Writes colored buffers to stdout.
+///
+/// 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 {
+ stdout: io::Stdout,
+ printed: AtomicBool,
+ separator: Option<Vec<u8>>,
+ color_choice: ColorChoice,
+ #[cfg(windows)]
+ console: Option<Mutex<wincolor::Console>>,
+}
+
+impl BufferWriter {
+ /// Create a new `BufferWriter` that writes to stdout with the given
+ /// color preferences.
+ ///
+ /// The specific color/style settings can be configured when writing to
+ /// the buffers themselves.
+ #[cfg(not(windows))]
+ pub fn stdout(choice: ColorChoice) -> BufferWriter {
+ BufferWriter {
+ stdout: io::stdout(),
+ printed: AtomicBool::new(false),
+ separator: None,
+ color_choice: choice,
+ }
+ }
+
+ /// Create a new `BufferWriter` that writes to stdout 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)]
+ pub fn stdout(choice: ColorChoice) -> BufferWriter {
+ BufferWriter {
+ stdout: io::stdout(),
+ printed: AtomicBool::new(false),
+ separator: None,
+ color_choice: choice,
+ console: wincolor::Console::stdout().ok().map(Mutex::new),
+ }
+ }
+
+ /// 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 stdout = self.stdout.lock();
+ if let Some(ref sep) = self.separator {
+ if self.printed.load(Ordering::SeqCst) {
+ try!(stdout.write_all(sep));
+ try!(stdout.write_all(b"\n"));
+ }
+ }
+ match buf.0 {
+ BufferInner::NoColor(ref b) => try!(stdout.write_all(&b.0)),
+ BufferInner::Ansi(ref b) => try!(stdout.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();
+ try!(b.print(&mut *console, &mut stdout));
+ }
+ }
+ 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 {
+ 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),
+ }
+ }
+
+ 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 {
+ fn supports_color(&self) -> bool {
+ match self.0 {
+ BufferInner::NoColor(_) => false,
+ BufferInner::Ansi(_) => true,
+ #[cfg(windows)]
+ BufferInner::Windows(_) => true,
+ }
+ }
+
+ 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),
+ }
+ }
+
+ 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(),
+ }
+ }
+}
+
+/// 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> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.0.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.0.flush()
+ }
+}
+
+impl<W: io::Write> WriteColor for NoColor<W> {
+ fn supports_color(&self) -> bool { false }
+ fn set_color(&mut self, _: &ColorSpec) -> io::Result<()> { Ok(()) }
+ fn reset(&mut self) -> io::Result<()> { Ok(()) }
+}
+
+/// 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> {
+ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+ self.0.write(buf)
+ }
+
+ fn flush(&mut self) -> io::Result<()> {
+ self.0.flush()
+ }
+}
+
+impl<W: io::Write> WriteColor for Ansi<W> {
+ fn supports_color(&self) -> bool { true }
+
+ fn set_color(&mut self, spec: &ColorSpec) -> io::Result<()> {
+ try!(self.reset());
+ if let Some(ref c) = spec.fg_color {
+ try!(self.write_color(true, c, spec.bold));
+ }
+ if let Some(ref c) = spec.bg_color {
+ try!(self.write_color(false, c, spec.bold));
+ }
+ if spec.fg_color.is_none() && spec.bg_color.is_none() {
+ try!(self.write_str("\x1B[1m"));
+ }
+ Ok(())
+ }
+
+ fn reset(&mut self) -> io::Result<()> {
+ self.write_str("\x1B[m")
+ }
+}
+
+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,
+ bold: bool,
+ ) -> io::Result<()> {
+ // *sigh*... The termion crate doesn't compile on Windows, and we
+ // need to be able to write ANSI escape sequences on Windows, so I
+ // guess we have to roll this ourselves.
+ macro_rules! w {
+ ($selfie:expr, $fg:expr, $clr:expr) => {
+ if $fg {
+ $selfie.write_str(concat!("\x1B[38;5;", $clr, "m"))
+ } else {
+ $selfie.write_str(concat!("\x1B[48;5;", $clr, "m"))
+ }
+ }
+ }
+ if bold {
+ match *c {
+ Color::Black => w!(self, fg, "8"),
+ Color::Blue => w!(self, fg, "12"),
+ Color::Green => w!(self, fg, "10"),
+ Color::Red => w!(self, fg, "9"),
+ Color::Cyan => w!(self, fg, "14"),
+ Color::Magenta => w!(self, fg, "13"),
+ Color::Yellow => w!(self, fg, "11"),
+ Color::White => w!(self, fg, "15"),
+ Color::__Nonexhaustive => unreachable!(),
+ }
+ } else {
+ match *c {
+ Color::Black => w!(self, fg, "0"),
+ Color::Blue => w!(self, fg, "4"),
+ Color::Green => w!(self, fg, "2"),
+ Color::Red => w!(self, fg, "1"),
+ Color::Cyan => w!(self, fg, "6"),
+ Color::Magenta => w!(self, fg, "5"),
+ Color::Yellow => w!