summaryrefslogtreecommitdiffstats
path: root/src
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 /src
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 'src')
-rw-r--r--src/app.rs24
-rw-r--r--src/args.rs105
-rw-r--r--src/atty.rs111
-rw-r--r--src/main.rs60
-rw-r--r--src/out.rs374
-rw-r--r--src/printer.rs437
-rw-r--r--src/search_buffer.rs12
-rw-r--r--src/search_stream.rs14
-rw-r--r--src/terminal_win.rs176
-rw-r--r--src/worker.rs8
10 files changed, 600 insertions, 721 deletions
diff --git a/src/app.rs b/src/app.rs
index 33dfb271..231f6bcb 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -88,7 +88,9 @@ fn app<F>(next_line_help: bool, doc: F) -> App<'static, 'static>
.value_name("WHEN")
.takes_value(true)
.hide_possible_values(true)
- .possible_values(&["never", "always", "auto"]))
+ .possible_values(&["never", "auto", "always", "ansi"]))
+ .arg(flag("colors").value_name("SPEC")
+ .takes_value(true).multiple(true).number_of_values(1))
.arg(flag("fixed-strings").short("F"))
.arg(flag("glob").short("g")
.takes_value(true).multiple(true).number_of_values(1)
@@ -220,7 +222,25 @@ lazy_static! {
doc!(h, "color",
"When to use color. [default: auto]",
"When to use color in the output. The possible values are \
- never, always or auto. The default is auto.");
+ never, auto, always or ansi. The default is auto. When always \
+ is used, coloring is attempted based on your environment. When \
+ ansi used, coloring is forcefully done using ANSI escape color \
+ codes.");
+ doc!(h, "colors",
+ "Configure color settings and styles.",
+ "This flag specifies color settings for use in the output. \
+ This flag may be provided multiple times. Settings are applied \
+ iteratively. Colors are limited to one of eight choices: \
+ red, blue, green, cyan, magenta, yellow, white and black. \
+ Styles are limited to either nobold or bold.\n\nThe format \
+ of the flag is {type}:{attribute}:{value}. {type} should be \
+ one of path, line or match. {attribute} can be fg, bg or style. \
+ {value} is either a color (for fg and bg) or a text style. \
+ A special format, {type}:none, will clear all color settings \
+ for {type}.\n\nFor example, the following command will change \
+ the match color to magenta and the background color for line \
+ numbers to yellow:\n\n\
+ rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.");
doc!(h, "fixed-strings",
"Treat the pattern as a literal string.",
"Treat the pattern as a literal string instead of a regular \
diff --git a/src/args.rs b/src/args.rs
index 836b2819..a98a1b6a 100644
--- a/src/args.rs
+++ b/src/args.rs
@@ -13,21 +13,14 @@ use grep::{Grep, GrepBuilder};
use log;
use num_cpus;
use regex;
-use term::Terminal;
-#[cfg(not(windows))]
-use term;
-#[cfg(windows)]
-use term::WinConsole;
+use termcolor;
-use atty;
use app;
+use atty;
use ignore::overrides::{Override, OverrideBuilder};
use ignore::types::{FileTypeDef, Types, TypesBuilder};
use ignore;
-use out::{Out, ColoredTerminal};
-use printer::Printer;
-#[cfg(windows)]
-use terminal_win::WindowsBuffer;
+use printer::{ColorSpecs, Printer};
use unescape::unescape;
use worker::{Worker, WorkerBuilder};
@@ -40,6 +33,8 @@ pub struct Args {
after_context: usize,
before_context: usize,
color: bool,
+ color_choice: termcolor::ColorChoice,
+ colors: ColorSpecs,
column: bool,
context_separator: Vec<u8>,
count: bool,
@@ -132,8 +127,9 @@ impl Args {
/// Create a new printer of individual search results that writes to the
/// writer given.
- pub fn printer<W: Terminal + Send>(&self, wtr: W) -> Printer<W> {
+ pub fn printer<W: termcolor::WriteColor>(&self, wtr: W) -> Printer<W> {
let mut p = Printer::new(wtr)
+ .colors(self.colors.clone())
.column(self.column)
.context_separator(self.context_separator.clone())
.eol(self.eol)
@@ -147,16 +143,6 @@ impl Args {
p
}
- /// Create a new printer of search results for an entire file that writes
- /// to the writer given.
- pub fn out(&self) -> Out {
- let mut out = Out::new(self.color);
- if let Some(filesep) = self.file_separator() {
- out = out.file_separator(filesep);
- }
- out
- }
-
/// Retrieve the configured file separator.
pub fn file_separator(&self) -> Option<Vec<u8>> {
if self.heading && !self.count && !self.files_with_matches && !self.files_without_matches {
@@ -173,30 +159,17 @@ impl Args {
self.max_count == Some(0)
}
- /// Create a new buffer for use with searching.
- #[cfg(not(windows))]
- pub fn outbuf(&self) -> ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
- ColoredTerminal::new(vec![], self.color)
- }
-
- /// Create a new buffer for use with searching.
- #[cfg(windows)]
- pub fn outbuf(&self) -> ColoredTerminal<WindowsBuffer> {
- ColoredTerminal::new_buffer(self.color)
- }
-
- /// Create a new buffer for use with searching.
- #[cfg(not(windows))]
- pub fn stdout(
- &self,
- ) -> ColoredTerminal<term::TerminfoTerminal<io::BufWriter<io::Stdout>>> {
- ColoredTerminal::new(io::BufWriter::new(io::stdout()), self.color)
+ /// Create a new writer for single-threaded searching with color support.
+ pub fn stdout(&self) -> termcolor::Stdout {
+ termcolor::Stdout::new(self.color_choice)
}
- /// Create a new buffer for use with searching.
- #[cfg(windows)]
- pub fn stdout(&self) -> ColoredTerminal<WinConsole<io::Stdout>> {
- ColoredTerminal::new_stdout(self.color)
+ /// Create a new buffer writer for multi-threaded searching with color
+ /// support.
+ pub fn buffer_writer(&self) -> termcolor::BufferWriter {
+ let mut wtr = termcolor::BufferWriter::stdout(self.color_choice);
+ wtr.separator(self.file_separator());
+ wtr
}
/// Return the paths that should be searched.
@@ -312,6 +285,8 @@ impl<'a> ArgMatches<'a> {
after_context: after_context,
before_context: before_context,
color: self.color(),
+ color_choice: self.color_choice(),
+ colors: try!(self.color_specs()),
column: self.column(),
context_separator: self.context_separator(),
count: self.is_present("count"),
@@ -617,6 +592,50 @@ impl<'a> ArgMatches<'a> {
}
}
+ /// Returns the user's color choice based on command line parameters and
+ /// environment.
+ fn color_choice(&self) -> termcolor::ColorChoice {
+ let preference = match self.0.value_of_lossy("color") {
+ None => "auto".to_string(),
+ Some(v) => v.into_owned(),
+ };
+ if preference == "always" {
+ termcolor::ColorChoice::Always
+ } else if preference == "ansi" {
+ termcolor::ColorChoice::AlwaysAnsi
+ } else if self.is_present("vimgrep") {
+ termcolor::ColorChoice::Never
+ } else if preference == "auto" {
+ if atty::on_stdout() || self.is_present("pretty") {
+ termcolor::ColorChoice::Auto
+ } else {
+ termcolor::ColorChoice::Never
+ }
+ } else {
+ termcolor::ColorChoice::Never
+ }
+ }
+
+ /// Returns the color specifications given by the user on the CLI.
+ ///
+ /// If the was a problem parsing any of the provided specs, then an error
+ /// is returned.
+ fn color_specs(&self) -> Result<ColorSpecs> {
+ // Start with a default set of color specs.
+ let mut specs = vec![
+ "path:fg:green".parse().unwrap(),
+ "path:style:bold".parse().unwrap(),
+ "line:fg:blue".parse().unwrap(),
+ "line:style:bold".parse().unwrap(),
+ "match:fg:red".parse().unwrap(),
+ "match:style:bold".parse().unwrap(),
+ ];
+ for spec_str in self.values_of_lossy_vec("colors") {
+ specs.push(try!(spec_str.parse()));
+ }
+ Ok(ColorSpecs::new(&specs))
+ }
+
/// Returns the approximate number of threads that ripgrep should use.
fn threads(&self) -> Result<usize> {
let threads = try!(self.usize_of("threads")).unwrap_or(0);
diff --git a/src/atty.rs b/src/atty.rs
index 978c3749..9e96fe6e 100644
--- a/src/atty.rs
+++ b/src/atty.rs
@@ -4,6 +4,11 @@ from (or to) a terminal. Windows and Unix do this differently, so implement
both here.
*/
+#[cfg(windows)]
+use winapi::minwindef::DWORD;
+#[cfg(windows)]
+use winapi::winnt::HANDLE;
+
#[cfg(unix)]
pub fn stdin_is_readable() -> bool {
use std::fs::File;
@@ -44,26 +49,104 @@ pub fn on_stdout() -> bool {
/// Returns true if there is a tty on stdin.
#[cfg(windows)]
pub fn on_stdin() -> bool {
- // BUG: https://github.com/BurntSushi/ripgrep/issues/19
- // It's not clear to me how to determine whether there is a tty on stdin.
- // Checking GetConsoleMode(GetStdHandle(stdin)) != 0 appears to report
- // that stdin is a pipe, even if it's not in a cygwin terminal, for
- // example.
- //
- // To fix this, we just assume there is always a tty on stdin. If Windows
- // users need to search stdin, they'll have to pass -. Ug.
- true
+ use kernel32::GetStdHandle;
+ use winapi::winbase::{
+ STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
+ };
+
+ unsafe {
+ let stdin = GetStdHandle(STD_INPUT_HANDLE);
+ if console_on_handle(stdin) {
+ // False positives aren't possible. If we got a console then
+ // we definitely have a tty on stdin.
+ return true;
+ }
+ // Otherwise, it's possible to get a false negative. If we know that
+ // there's a console on stdout or stderr however, then this is a true
+ // negative.
+ if console_on_fd(STD_OUTPUT_HANDLE)
+ || console_on_fd(STD_ERROR_HANDLE) {
+ return false;
+ }
+ // Otherwise, we can't really tell, so we do a weird hack.
+ msys_tty_on_handle(stdin)
+ }
}
/// Returns true if there is a tty on stdout.
#[cfg(windows)]
pub fn on_stdout() -> bool {
- use kernel32;
- use winapi;
+ use kernel32::GetStdHandle;
+ use winapi::winbase::{
+ STD_INPUT_HANDLE, STD_OUTPUT_HANDLE, STD_ERROR_HANDLE,
+ };
+
+ unsafe {
+ let stdout = GetStdHandle(STD_OUTPUT_HANDLE);
+ if console_on_handle(stdout) {
+ // False positives aren't possible. If we got a console then
+ // we definitely have a tty on stdout.
+ return true;
+ }
+ // Otherwise, it's possible to get a false negative. If we know that
+ // there's a console on stdin or stderr however, then this is a true
+ // negative.
+ if console_on_fd(STD_INPUT_HANDLE) || console_on_fd(STD_ERROR_HANDLE) {
+ return false;
+ }
+ // Otherwise, we can't really tell, so we do a weird hack.
+ msys_tty_on_handle(stdout)
+ }
+}
+
+/// Returns true if there is an MSYS tty on the given handle.
+#[cfg(windows)]
+fn msys_tty_on_handle(handle: HANDLE) -> bool {
+ use std::ffi::OsString;
+ use std::mem;
+ use std::os::raw::c_void;
+ use std::os::windows::ffi::OsStringExt;
+ use std::slice;
+
+ use kernel32::{GetFileInformationByHandleEx};
+ use winapi::fileapi::FILE_NAME_INFO;
+ use winapi::minwinbase::FileNameInfo;
+ use winapi::minwindef::MAX_PATH;
unsafe {
- let fd = winapi::winbase::STD_OUTPUT_HANDLE;
- let mut out = 0;
- kernel32::GetConsoleMode(kernel32::GetStdHandle(fd), &mut out) != 0
+ let size = mem::size_of::<FILE_NAME_INFO>();
+ let mut name_info_bytes = vec![0u8; size + MAX_PATH];
+ let res = GetFileInformationByHandleEx(
+ handle,
+ FileNameInfo,
+ &mut *name_info_bytes as *mut _ as *mut c_void,
+ name_info_bytes.len() as u32);
+ if res == 0 {
+ return true;
+ }
+ let name_info: FILE_NAME_INFO =
+ *(name_info_bytes[0..size].as_ptr() as *const FILE_NAME_INFO);
+ let name_bytes =
+ &name_info_bytes[size..size + name_info.FileNameLength as usize];
+ let name_u16 = slice::from_raw_parts(
+ name_bytes.as_ptr() as *const u16, name_bytes.len() / 2);
+ let name = OsString::from_wide(name_u16)
+ .as_os_str().to_string_lossy().into_owned();
+ name.contains("msys-") || name.contains("-pty")
}
}
+
+/// Returns true if there is a console on the given file descriptor.
+#[cfg(windows)]
+unsafe fn console_on_fd(fd: DWORD) -> bool {
+ use kernel32::GetStdHandle;
+ console_on_handle(GetStdHandle(fd))
+}
+
+/// Returns true if there is a console on the given handle.
+#[cfg(windows)]
+fn console_on_handle(handle: HANDLE) -> bool {
+ use kernel32::GetConsoleMode;
+ let mut out = 0;
+ unsafe { GetConsoleMode(handle, &mut out) != 0 }
+}
diff --git a/src/main.rs b/src/main.rs
index 0eecb13b..2bf1cf02 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -18,7 +18,7 @@ extern crate memchr;
extern crate memmap;
extern crate num_cpus;
extern crate regex;
-extern crate term;
+extern crate termcolor;
#[cfg(windows)]
extern crate winapi;
@@ -31,9 +31,8 @@ use std::sync::{Arc, Mutex};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc;
use std::thread;
-use std::cmp;
-use term::Terminal;
+use termcolor::WriteColor;
use args::Args;
use worker::Work;
@@ -54,13 +53,10 @@ macro_rules! eprintln {
mod app;
mod args;
mod atty;
-mod out;
mod pathutil;
mod printer;
mod search_buffer;
mod search_stream;
-#[cfg(windows)]
-mod terminal_win;
mod unescape;
mod worker;
@@ -84,16 +80,13 @@ fn run(args: Arc<Args>) -> Result<u64> {
{
let args = args.clone();
ctrlc::set_handler(move || {
- let stdout = io::stdout();
- let mut stdout = stdout.lock();
-
- let _ = args.stdout().reset();
- let _ = stdout.flush();
-
+ let mut writer = args.stdout();
+ let _ = writer.reset();
+ let _ = writer.flush();
process::exit(1);
});
}
- let threads = cmp::max(1, args.threads() - 1);
+ let threads = args.threads();
if args.files() {
if threads == 1 || args.is_one_path() {
run_files_one_thread(args)
@@ -110,7 +103,7 @@ fn run(args: Arc<Args>) -> Result<u64> {
}
fn run_parallel(args: Arc<Args>) -> Result<u64> {
- let out = Arc::new(Mutex::new(args.out()));
+ let bufwtr = Arc::new(args.buffer_writer());
let quiet_matched = QuietMatched::new(args.quiet());
let paths_searched = Arc::new(AtomicUsize::new(0));
let match_count = Arc::new(AtomicUsize::new(0));
@@ -120,8 +113,8 @@ fn run_parallel(args: Arc<Args>) -> Result<u64> {
let quiet_matched = quiet_matched.clone();
let paths_searched = paths_searched.clone();
let match_count = match_count.clone();
- let out = out.clone();
- let mut outbuf = args.outbuf();
+ let bufwtr = bufwtr.clone();
+ let mut buf = bufwtr.buffer();
let mut worker = args.worker();
Box::new(move |result| {
use ignore::WalkState::*;
@@ -134,11 +127,11 @@ fn run_parallel(args: Arc<Args>) -> Result<u64> {
Some(dent) => dent,
};
paths_searched.fetch_add(1, Ordering::SeqCst);
- outbuf.clear();
+ buf.clear();
{
// This block actually executes the search and prints the
// results into outbuf.
- let mut printer = args.printer(&mut outbuf);
+ let mut printer = args.printer(&mut buf);
let count =
if dent.is_stdin() {
worker.run(&mut printer, Work::Stdin)
@@ -150,17 +143,9 @@ fn run_parallel(args: Arc<Args>) -> Result<u64> {
return Quit;
}
}
- if !outbuf.get_ref().is_empty() {
- // This should be the only mutex in all of ripgrep. Since the
- // common case is to report a small number of matches relative
- // to the corpus, this really shouldn't matter much.
- //
- // Still, it'd be nice to send this on a channel, but then we'd
- // need to manage a pool of outbufs, which would complicate the
- // code.
- let mut out = out.lock().unwrap();
- out.write(&outbuf);
- }
+ // BUG(burntsushi): We should handle this error instead of ignoring
+ // it. See: https://github.com/BurntSushi/ripgrep/issues/200
+ let _ = bufwtr.print(&buf);
Continue
})
});
@@ -173,8 +158,9 @@ fn run_parallel(args: Arc<Args>) -> Result<u64> {
}
fn run_one_thread(args: Arc<Args>) -> Result<u64> {
+ let stdout = args.stdout();
+ let mut stdout = stdout.lock();
let mut worker = args.worker();
- let mut term = args.stdout();
let mut paths_searched: u64 = 0;
let mut match_count = 0;
for result in args.walker() {
@@ -182,7 +168,7 @@ fn run_one_thread(args: Arc<Args>) -> Result<u64> {
None => continue,
Some(dent) => dent,
};
- let mut printer = args.printer(&mut term);
+ let mut printer = args.printer(&mut stdout);
if match_count > 0 {
if args.quiet() {
break;
@@ -211,8 +197,8 @@ fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
let print_args = args.clone();
let (tx, rx) = mpsc::channel::<ignore::DirEntry>();
let print_thread = thread::spawn(move || {
- let term = print_args.stdout();
- let mut printer = print_args.printer(term);
+ let stdout = print_args.stdout();
+ let mut printer = print_args.printer(stdout.lock());
let mut file_count = 0;
for dent in rx.iter() {
printer.path(dent.path());
@@ -234,8 +220,8 @@ fn run_files_parallel(args: Arc<Args>) -> Result<u64> {
}
fn run_files_one_thread(args: Arc<Args>) -> Result<u64> {
- let term = args.stdout();
- let mut printer = args.printer(term);
+ let stdout = args.stdout();
+ let mut printer = args.printer(stdout.lock());
let mut file_count = 0;
for result in args.walker() {
let dent = match get_or_log_dir_entry(result, args.no_messages()) {
@@ -249,8 +235,8 @@ fn run_files_one_thread(args: Arc<Args>) -> Result<u64> {
}
fn run_types(args: Arc<Args>) -> Result<u64> {
- let term = args.stdout();
- let mut printer = args.printer(term);
+ let stdout = args.stdout();
+ let mut printer = args.printer(stdout.lock());
let mut ty_count = 0;
for def in args.type_defs() {
printer.type_def(def);
diff --git a/src/out.rs b/src/out.rs
deleted file mode 100644
index 389f5458..00000000
--- a/src/out.rs
+++ /dev/null
@@ -1,374 +0,0 @@
-use std::io::{self, Write};
-
-use term::{self, Terminal};
-#[cfg(not(windows))]
-use term::terminfo::TermInfo;
-#[cfg(windows)]
-use term::WinConsole;
-
-#[cfg(windows)]
-use terminal_win::WindowsBuffer;
-
-/// Out controls the actual output of all search results for a particular file
-/// to the end user.
-///
-/// (The difference between Out and Printer is that a Printer works with
-/// individual search results where as Out works with search results for each
-/// file as a whole. For example, it knows when to print a file separator.)
-pub struct Out {
- #[cfg(not(windows))]
- term: ColoredTerminal<term::TerminfoTerminal<io::BufWriter<io::Stdout>>>,
- #[cfg(windows)]
- term: ColoredTerminal<WinConsole<io::Stdout>>,
- printed: bool,
- file_separator: Option<Vec<u8>>,
-}
-
-impl Out {
- /// Create a new Out that writes to the wtr given.
- #[cfg(not(windows))]
- pub fn new(color: bool) -> Out {
- let wtr = io::BufWriter::new(io::stdout());
- Out {
- term: ColoredTerminal::new(wtr, color),
- printed: false,
- file_separator: None,
- }
- }
-
- /// Create a new Out that writes to the wtr given.
- #[cfg(windows)]
- pub fn new(color: bool) -> Out {
- Out {
- term: ColoredTerminal::new_stdout(color),
- printed: false,
- file_separator: None,
- }
- }
-
- /// If set, the separator is printed between matches from different files.
- /// By default, no separator is printed.
- pub fn file_separator(mut self, sep: Vec<u8>) -> Out {
- self.file_separator = Some(sep);
- self
- }
-
- /// Write the search results of a single file to the underlying wtr and
- /// flush wtr.
- #[cfg(not(windows))]
- pub fn write(
- &mut self,
- buf: &ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>,
- ) {
- self.write_sep();
- match *buf {
- ColoredTerminal::Colored(ref tt) => {
- let _ = self.term.write_all(tt.get_ref());
- }
- ColoredTerminal::NoColor(ref buf) => {
- let _ = self.term.write_all(buf);
- }
- }
- self.write_done();
- }
- /// Write the search results of a single file to the underlying wtr and
- /// flush wtr.
- #[cfg(windows)]
- pub fn write(
- &mut self,
- buf: &ColoredTerminal<WindowsBuffer>,
- ) {
- self.write_sep();
- match *buf {
- ColoredTerminal::Colored(ref tt) => {
- tt.print_stdout(&mut self.term);
- }
- ColoredTerminal::NoColor(ref buf) => {
- let _ = self.term.write_all(buf);
- }
- }
- self.write_done();
- }
-
- fn write_sep(&mut self) {
- if let Some(ref sep) = self.file_separator {
- if self.printed {
- let _ = self.term.write_all(sep);
- let _ = self.term.write_all(b"\n");
- }
- }
- }
-
- fn write_done(&mut self) {
- let _ = self.term.flush();
- self.printed = true;
- }
-}
-
-/// ColoredTerminal provides optional colored output through the term::Terminal
-/// trait. In particular, it will dynamically configure itself to use coloring
-/// if it's available in the environment.
-#[derive(Clone, Debug)]
-pub enum ColoredTerminal<T: Terminal + Send> {
- Colored(T),
- NoColor(T::Output),
-}
-
-#[cfg(not(windows))]
-impl<W: io::Write + Send> ColoredTerminal<term::TerminfoTerminal<W>> {
- /// Create a new output buffer.
- ///
- /// When color is true, the buffer will attempt to support coloring.
- pub fn new(wtr: W, color: bool) -> Self {
- lazy_static! {
- // Only pay for parsing the terminfo once.
- static ref TERMINFO: Option<TermInfo> = {
- match TermInfo::from_env() {
- Ok(info) => Some(info),
- Err(err) => {
- debug!("error loading terminfo for coloring: {}", err);
- None
- }
- }
- };
- }
- // If we want color, build a term::TerminfoTerminal and see if the
- // current environment supports coloring. If not, bail with NoColor. To
- // avoid losing our writer (ownership), do this the long way.
- if !color {
- return ColoredTerminal::NoColor(wtr);
- }
- let terminfo = match *TERMINFO {
- None => return ColoredTerminal::NoColor(wtr),
- Some(ref ti) => {
- // Ug, this should go away with the next release of `term`.
- TermInfo {
- names: ti.names.clone(),
- bools: ti.bools.clone(),
- numbers: ti.numbers.clone(),
- strings: ti.strings.clone(),
- }
- }
- };
- let tt = term::TerminfoTerminal::new_with_terminfo(wtr, terminfo);
- if !tt.supports_color() {
- debug!("environment doesn't support coloring");
- return ColoredTerminal::NoColor(tt.into_inner());
- }
- ColoredTerminal::Colored(tt)
- }
-}
-
-#[cfg(not(windows))]
-impl ColoredTerminal<term::TerminfoTerminal<Vec<u8>>> {
- /// Clear the give buffer of all search results such that it is reusable
- /// in another search.
- pub fn clear(&mut self) {
- match *self {
- ColoredTerminal::Colored(ref mut tt) => {
- tt.get_mut().clear();
- }
- ColoredTerminal::NoColor(ref mut buf) => {
- buf.clear();
- }
- }
- }
-}
-
-#[cfg(windows)]
-impl ColoredTerminal<WindowsBuffer> {
- /// Create a new output buffer.
- ///
- /// When color is true, the buffer will attempt to support coloring.
- pub fn new_buffer(color: bool) -> Self {
- if !color {
- ColoredTerminal::NoColor(vec![])
- } else {
- ColoredTerminal::Colored(WindowsBuffer::new())
- }
- }
-
- /// Clear the give buffer of all search results such that it is reusable
- /// in another search.
- pub fn clear(&mut self) {
- match *self {
- ColoredTerminal::Colored(ref mut win) => win.clear(),
- ColoredTerminal::NoColor(ref mut buf) => buf.clear(),
- }
- }
-}
-
-#[cfg(windows)]
-impl ColoredTerminal<WinConsole<io::Stdout>> {
- /// Create a new output buffer.
- ///
- /// When color is true, the buffer will attempt to support coloring.
- pub fn new_stdout(color: bool) -> Self {
- if !color {
- return ColoredTerminal::NoColor(io::stdout());
- }
- match WinConsole::new(io::stdout()) {
- Ok(win) => ColoredTerminal::Colored(win),
- Err(_) => ColoredTerminal::NoColor(io::stdout()),
- }
- }
-}
-
-impl<T: Terminal + Send> ColoredTerminal<T> {
- fn map_result<F>(
- &mut self,
- mut f: F,
- ) -> term::Result<()>
- where F: FnMut(&mut T) -> term::Result<()> {
- match *self {
- ColoredTerminal::Colored(ref mut w) => f(w),
- ColoredTerminal::NoColor(_) => Err(term::Error::NotSupported),
- }
- }
-
- fn map_bool<F>(
- &self,
- mut f: F,
- ) -> bool
- where F: FnMut(&T) -> bool {
- match *self {
- ColoredTerminal::Colored(ref w) => f(w),
- ColoredTerminal::NoColor(_) => false,
- }
- }
-}
-
-impl<T: Terminal + Send> io::Write for ColoredTerminal<T> {
- fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
- match *self {
- ColoredTerminal::Colored(ref mut w) => w.write(buf),
- ColoredTerminal::NoColor(ref mut w) => w.write(buf),
- }
- }
-
- fn flush(&mut self) -> io::Result<()> {
- Ok(())
- }
-}
-
-impl<T: Terminal + Send> term::Terminal for ColoredTerminal<T> {
- type Output = T::Output;
-
- fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
- self.map_result(|w| w.fg(fg))
- }
-
- fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
- self.map_result(|w| w.bg(bg))
- }
-
- fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
- self.map_result(|w| w.attr(attr))
- }
-
- fn supports_attr(&self, attr: term::Attr) -> bool {
- self.map_bool(|w| w.supports_attr(attr))
- }
-
- fn reset(&mut self) -> term::Result<()> {
- self.map_result(|w| w.reset())
- }
-
- fn supports_reset(&self) -> bool {
- self.map_bool(|w| w.supports_reset())
- }
-
- fn supports_color(&self) -> bool {
- self.map_bool(|w| w.supports_color())
- }
-
- fn cursor_up(&mut self) -> term::Result<()> {
- self.map_result(|w| w.cursor_up())
- }
-
- fn delete_line(&mut self) -> term::Result<()> {
- self.map_result(|w| w.delete_line())
- }
-
- fn carriage_return(&mut self) -> term::Result<()> {
- self.map_result(|w| w.carriage_return())
- }
-
- fn get_ref(&self) -> &Self::Output {
- match *self {
- ColoredTerminal::Colored(ref w) => w.get_ref(),
- ColoredTerminal::NoColor(ref w) => w,
- }
- }
-
- fn get_mut(&mut self) -> &mut Self::Output {
- match *self {
- ColoredTerminal::Colored(ref mut w) => w.get_mut(),
- ColoredTerminal::NoColor(ref mut w) => w,
- }
- }
-
- fn into_inner(self) -> Self::Output {
- match self {
- ColoredTerminal::Colored(w) => w.into_inner(),
- ColoredTerminal::NoColor(w) => w,
- }
- }
-}
-
-impl<'a, T: Terminal + Send> term::Terminal for &'a mut ColoredTerminal<T> {
- type Output = T::Output;
-
- fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
- (**self).fg(fg)
- }
-
- fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
- (**self).bg(bg)
- }
-
- fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
- (**self).attr(attr)
- }
-
- fn supports_attr(&self, attr: term::Attr) -> bool {
- (**self).supports_attr(attr)
- }
-
- fn reset(&mut self) -> term::Result<()> {
- (**self).reset()
- }
-
- fn supports_reset(&self) -> bool {
- (**self).supports_reset()
- }
-
- fn supports_color(&self) -> bool {
- (**self).supports_color()
- }
-
- fn cursor_up(&mut self) -> term::Result<()> {
- (**self).cursor_up()
- }
-
- fn delete_line(&mut self) -> term::Result<()> {
- (**self).delete_line()
- }
-
- fn carriage_return(&mut self) -> term::Result<()> {
- (**self).carriage_return()
- }
-
- fn get_ref(&self) -> &Self::Output {
- (**self).get_ref()
- }
-
- fn get_mut(&mut self) -> &mut Self::Output {
- (**self).get_mut()
- }
-
- fn into_inner(self) -> Self::Output {
- // Good golly miss molly...
- unimplemented!()
- }
-}
diff --git a/src/printer.rs b/src/printer.rs
index 1b8e5965..4c04c0ee 100644
--- a/src/printer.rs
+++ b/src/printer.rs
@@ -1,8 +1,10 @@
+use std::error;
+use std::fmt;
use std::path::Path;
+use std::str::FromStr;
use regex::bytes::Regex;
-use term::{Attr, Terminal};
-use term::color;
+use termcolor::{Color, ColorSpec, ParseColorError, WriteColor};
use pathutil::strip_prefix;
use ignore::types::FileTypeDef;
@@ -40,38 +42,12 @@ pub struct Printer<W> {
replace: Option<Vec<u8>>,
/// Whether to prefix each match with the corresponding file name.
with_filename: bool,
- /// The choice of colors.
- color_choice: ColorChoice
+ /// The color specifications.
+ colors: ColorSpecs,
}
-struct ColorChoice {
- matched_line: color::Color,
- heading: color::Color,
- line_number: color::Color
-}