summaryrefslogtreecommitdiffstats
path: root/wincolor
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 /wincolor
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 'wincolor')
-rw-r--r--wincolor/Cargo.toml21
-rw-r--r--wincolor/README.md44
-rw-r--r--wincolor/src/lib.rs242
3 files changed, 307 insertions, 0 deletions
diff --git a/wincolor/Cargo.toml b/wincolor/Cargo.toml
new file mode 100644
index 00000000..226d85b0
--- /dev/null
+++ b/wincolor/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "wincolor"
+version = "0.1.0" #:version
+authors = ["Andrew Gallant <jamslam@gmail.com>"]
+description = """
+A simple Windows specific API for controlling text color in a Windows console.
+"""
+documentation = "https://docs.rs/wincolor"
+homepage = "https://github.com/BurntSushi/ripgrep/tree/master/wincolor"
+repository = "https://github.com/BurntSushi/ripgrep/tree/master/wincolor"
+readme = "README.md"
+keywords = ["windows", "win", "color", "ansi", "console"]
+license = "Unlicense/MIT"
+
+[lib]
+name = "wincolor"
+bench = false
+
+[dependencies]
+kernel32-sys = "0.2.2"
+winapi = "0.2.8"
diff --git a/wincolor/README.md b/wincolor/README.md
new file mode 100644
index 00000000..cc780340
--- /dev/null
+++ b/wincolor/README.md
@@ -0,0 +1,44 @@
+wincolor
+========
+A simple Windows specific API for controlling text color in a Windows console.
+The purpose of this crate is to expose the full inflexibility of the Windows
+console without any platform independent abstraction.
+
+[![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)
+
+Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org).
+
+### Documentation
+
+[https://docs.rs/wincolor](https://docs.rs/wincolor)
+
+### Usage
+
+Add this to your `Cargo.toml`:
+
+```toml
+[dependencies]
+wincolor = "0.1"
+```
+
+and this to your crate root:
+
+```rust
+extern crate wincolor;
+```
+
+### Example
+
+This is a simple example that shows how to write text with a foreground color
+of cyan and the intense attribute set:
+
+```rust
+use wincolor::{Console, Color, Intense};
+
+let mut con = Console::stdout().unwrap();
+con.fg(Intense::Yes, Color::Cyan).unwrap();
+println!("This text will be intense cyan.");
+con.reset().unwrap();
+println!("This text will be normal.");
+```
diff --git a/wincolor/src/lib.rs b/wincolor/src/lib.rs
new file mode 100644
index 00000000..a210b4b2
--- /dev/null
+++ b/wincolor/src/lib.rs
@@ -0,0 +1,242 @@
+/*!
+This crate provides a safe and simple Windows specific API to control
+text attributes in the Windows console. Text attributes are limited to
+foreground/background colors, as well as whether to make colors intense or not.
+
+# Example
+
+```no_run
+use wincolor::{Console, Color, Intense};
+
+let mut con = Console::stdout().unwrap();
+con.fg(Intense::Yes, Color::Cyan).unwrap();
+println!("This text will be intense cyan.");
+con.reset().unwrap();
+println!("This text will be normal.");
+```
+*/
+extern crate kernel32;
+extern crate winapi;
+
+use std::io;
+use std::mem;
+
+use winapi::{DWORD, HANDLE, WORD};
+use winapi::winbase::STD_OUTPUT_HANDLE;
+use winapi::wincon::{
+ FOREGROUND_BLUE as FG_BLUE,
+ FOREGROUND_GREEN as FG_GREEN,
+ FOREGROUND_RED as FG_RED,
+ FOREGROUND_INTENSITY as FG_INTENSITY,
+};
+
+const FG_CYAN: DWORD = FG_BLUE | FG_GREEN;
+const FG_MAGENTA: DWORD = FG_BLUE | FG_RED;
+const FG_YELLOW: DWORD = FG_GREEN | FG_RED;
+const FG_WHITE: DWORD = FG_BLUE | FG_GREEN | FG_RED;
+
+/// A Windows console.
+///
+/// This represents a very limited set of functionality available to a Windows
+/// console. In particular, it can only change text attributes such as color
+/// and intensity.
+///
+/// There is no way to "write" to this console. Simply write to
+/// stdout or stderr instead, while interleaving instructions to the console
+/// to change text attributes.
+///
+/// A common pitfall when using a console is to forget to flush writes to
+/// stdout before setting new text attributes.
+#[derive(Debug)]
+pub struct Console {
+ handle: HANDLE,
+ start_attr: TextAttributes,
+ cur_attr: TextAttributes,
+}
+
+unsafe impl Send for Console {}
+
+impl Drop for Console {
+ fn drop(&mut self) {
+ unsafe { kernel32::CloseHandle(self.handle); }
+ }
+}
+
+impl Console {
+ /// Create a new Console to stdout.
+ ///
+ /// If there was a problem creating the console, then an error is returned.
+ pub fn stdout() -> io::Result<Console> {
+ let mut info = unsafe { mem::zeroed() };
+ let (handle, res) = unsafe {
+ let handle = kernel32::GetStdHandle(STD_OUTPUT_HANDLE);
+ (handle, kernel32::GetConsoleScreenBufferInfo(handle, &mut info))
+ };
+ if res == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ let attr = TextAttributes::from_word(info.wAttributes);
+ Ok(Console {
+ handle: handle,
+ start_attr: attr,
+ cur_attr: attr,
+ })
+ }
+
+ /// Applies the current text attributes.
+ fn set(&mut self) -> io::Result<()> {
+ let attr = self.cur_attr.to_word();
+ let res = unsafe {
+ kernel32::SetConsoleTextAttribute(self.handle, attr)
+ };
+ if res == 0 {
+ return Err(io::Error::last_os_error());
+ }
+ Ok(())
+ }
+
+ /// Apply the given intensity and color attributes to the console
+ /// foreground.
+ ///
+ /// If there was a problem setting attributes on the console, then an error
+ /// is returned.
+ pub fn fg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
+ self.cur_attr.fg_color = color;
+ self.cur_attr.fg_intense = intense;
+ self.set()
+ }
+
+ /// Apply the given intensity and color attributes to the console
+ /// background.
+ ///
+ /// If there was a problem setting attributes on the console, then an error
+ /// is returned.
+ pub fn bg(&mut self, intense: Intense, color: Color) -> io::Result<()> {
+ self.cur_attr.bg_color = color;
+ self.cur_attr.bg_intense = intense;
+ self.set()
+ }
+
+ /// Reset the console text attributes to their original settings.
+ ///
+ /// The original settings correspond to the text attributes on the console
+ /// when this `Console` value was created.
+ ///
+ /// If there was a problem setting attributes on the console, then an error
+ /// is returned.
+ pub fn reset(&mut self) -> io::Result<()> {
+ self.cur_attr = self.start_attr;
+ self.set()
+ }
+}
+
+/// A representation of text attributes for the Windows console.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+struct TextAttributes {
+ fg_color: Color,
+ fg_intense: Intense,
+ bg_color: Color,
+ bg_intense: Intense,
+}
+
+impl TextAttributes {
+ fn to_word(&self) -> WORD {
+ let mut w = 0;
+ w |= self.fg_color.to_fg();
+ w |= self.fg_intense.to_fg();
+ w |= self.bg_color.to_bg();
+ w |= self.bg_intense.to_bg();
+ w as WORD
+ }
+
+ fn from_word(word: WORD) -> TextAttributes {
+ let attr = word as DWORD;
+ TextAttributes {
+ fg_color: Color::from_fg(attr),
+ fg_intense: Intense::from_fg(attr),
+ bg_color: Color::from_bg(attr),
+ bg_intense: Intense::from_bg(attr),
+ }
+ }
+}
+
+/// Whether to use intense colors or not.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Intense {
+ Yes,
+ No,
+}
+
+impl Intense {
+ fn to_bg(&self) -> DWORD {
+ self.to_fg() << 4
+ }
+
+ fn from_bg(word: DWORD) -> Intense {
+ Intense::from_fg(word >> 4)
+ }
+
+ fn to_fg(&self) -> DWORD {
+ match *self {
+ Intense::No => 0,
+ Intense::Yes => FG_INTENSITY,
+ }
+ }
+
+ fn from_fg(word: DWORD) -> Intense {
+ if word & FG_INTENSITY > 0 {
+ Intense::Yes
+ } else {
+ Intense::No
+ }
+ }
+}
+
+/// The set of available colors for use with a Windows console.
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+pub enum Color {
+ Black,
+ Blue,
+ Green,
+ Red,
+ Cyan,
+ Magenta,
+ Yellow,
+ White,
+}
+
+impl Color {
+ fn to_bg(&self) -> DWORD {
+ self.to_fg() << 4
+ }
+
+ fn from_bg(word: DWORD) -> Color {
+ Color::from_fg(word >> 4)
+ }
+
+ fn to_fg(&self) -> DWORD {
+ match *self {
+ Color::Black => 0,
+ Color::Blue => FG_BLUE,
+ Color::Green => FG_GREEN,
+ Color::Red => FG_RED,
+ Color::Cyan => FG_CYAN,
+ Color::Magenta => FG_MAGENTA,
+ Color::Yellow => FG_YELLOW,
+ Color::White => FG_WHITE,
+ }
+ }
+
+ fn from_fg(word: DWORD) -> Color {
+ match word & 0b111 {
+ FG_BLUE => Color::Blue,
+ FG_GREEN => Color::Green,
+ FG_RED => Color::Red,
+ FG_CYAN => Color::Cyan,
+ FG_MAGENTA => Color::Magenta,
+ FG_YELLOW => Color::Yellow,
+ FG_WHITE => Color::White,
+ _ => Color::Black,
+ }
+ }
+}