summaryrefslogtreecommitdiffstats
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
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
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock35
-rw-r--r--Cargo.toml6
-rw-r--r--appveyor.yml2
-rw-r--r--ci/script.sh2
-rw-r--r--doc/rg.122
-rw-r--r--doc/rg.1.md16
-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
-rw-r--r--termcolor/Cargo.toml20
-rw-r--r--termcolor/README.md88
-rw-r--r--termcolor/src/lib.rs1071
-rw-r--r--wincolor/Cargo.toml21
-rw-r--r--wincolor/README.md44
-rw-r--r--wincolor/src/lib.rs242
23 files changed, 2153 insertions, 739 deletions
diff --git a/.gitignore b/.gitignore
index d2fafa58..be83b91c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@ target
/grep/Cargo.lock
/globset/Cargo.lock
/ignore/Cargo.lock
+/termcolor/Cargo.lock
+/wincolor/Cargo.lock
diff --git a/Cargo.lock b/Cargo.lock
index ada226fb..df98857d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,14 +9,14 @@ dependencies = [
"grep 0.1.4",
"ignore 0.1.5",
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"memmap 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
"num_cpus 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
- "term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termcolor 0.1.0",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
@@ -106,7 +106,7 @@ version = "0.1.2"
dependencies = [
"aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -129,7 +129,7 @@ version = "0.1.5"
dependencies = [
"crossbeam 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)",
"globset 0.1.2",
- "lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -148,7 +148,7 @@ dependencies = [
[[package]]
name = "lazy_static"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
@@ -217,22 +217,20 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
-name = "term"
-version = "0.4.4"
+name = "term_size"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
-name = "term_size"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
+name = "termcolor"
+version = "0.1.0"
dependencies = [
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wincolor 0.1.0",
]
[[package]]
@@ -322,6 +320,14 @@ name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+[[package]]
+name = "wincolor"
+version = "0.1.0"
+dependencies = [
+ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
[metadata]
"checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
"checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6"
@@ -334,7 +340,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
"checksum fs2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "640001e1bd865c7c32806292822445af576a6866175b5225aa2087ca5e3de551"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
-"checksum lazy_static 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "49247ec2a285bb3dcb23cbd9c35193c025e7251bfce77c1d5da97e6362dffe7f"
+"checksum lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6abe0ee2e758cd6bc8a2cd56726359007748fbf4128da998b65d0b70f881e19b"
"checksum libc 0.2.17 (registry+https://github.com/rust-lang/crates.io-index)" = "044d1360593a78f5c8e5e710beccdc24ab71d1f01bc19a29bcacdba22e8475d8"
"checksum log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ab83497bf8bf4ed2a74259c1c802351fcd67a65baa86394b6ba73c36f4838054"
"checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20"
@@ -344,7 +350,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957"
"checksum simd 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "63b5847c2d766ca7ce7227672850955802fabd779ba616aeabead4c2c3877023"
"checksum strsim 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "50c069df92e4b01425a8bf3576d5d417943a6a7272fbabaf5bd80b1aaa76442e"
-"checksum term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3deff8a2b3b6607d6d7cc32ac25c0b33709453ca9cceac006caac51e963cf94a"
"checksum term_size 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3f7f5f3f71b0040cecc71af239414c23fd3c73570f5ff54cf50e03cef637f2a0"
"checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03"
"checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a"
diff --git a/Cargo.toml b/Cargo.toml
index afed8439..b7517030 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -38,11 +38,11 @@ memchr = "0.1"
memmap = "0.5"
num_cpus = "1"
regex = "0.1.77"
-term = "0.4"
+termcolor = { version = "0.1.0", path = "termcolor" }
[target.'cfg(windows)'.dependencies]
-kernel32-sys = "0.2"
-winapi = "0.2"
+kernel32-sys = "0.2.2"
+winapi = "0.2.8"
[build-dependencies]
clap = "2.18"
diff --git a/appveyor.yml b/appveyor.yml
index 800bc947..c089e07b 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -31,6 +31,8 @@ test_script:
- cargo test --verbose --manifest-path grep/Cargo.toml
- cargo test --verbose --manifest-path globset/Cargo.toml
- cargo test --verbose --manifest-path ignore/Cargo.toml
+ - cargo test --verbose --manifest-path wincolor/Cargo.toml
+ - cargo test --verbose --manifest-path termcolor/Cargo.toml
before_deploy:
# Generate artifacts for release
diff --git a/ci/script.sh b/ci/script.sh
index bf0731a2..ccda56f5 100644
--- a/ci/script.sh
+++ b/ci/script.sh
@@ -25,6 +25,8 @@ run_test_suite() {
cargo test --target $TARGET --verbose --manifest-path globset/Cargo.toml
cargo build --target $TARGET --verbose --manifest-path ignore/Cargo.toml
cargo test --target $TARGET --verbose --manifest-path ignore/Cargo.toml
+ cargo build --target $TARGET --verbose --manifest-path termcolor/Cargo.toml
+ cargo test --target $TARGET --verbose --manifest-path termcolor/Cargo.toml
# sanity check the file type
file target/$TARGET/debug/rg
diff --git a/doc/rg.1 b/doc/rg.1
index d0790955..bf49ec57 100644
--- a/doc/rg.1
+++ b/doc/rg.1
@@ -143,6 +143,28 @@ Show NUM lines before and after each match.
.RS
.RE
.TP
+.B \-\-colors \f[I]SPEC\f[] ...
+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.
+.RS
+.PP
+The 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}.
+.PP
+For example, the following command will change the match color to
+magenta and the background color for line numbers to yellow:
+.PP
+rg \-\-colors \[aq]match:fg:magenta\[aq] \-\-colors
+\[aq]line:bg:yellow\[aq] foo.
+.RE
+.TP
.B \-\-column
Show column numbers (1 based) in output.
This only shows the column numbers for the first match on each line.
diff --git a/doc/rg.1.md b/doc/rg.1.md
index 8e6226d7..11487d69 100644
--- a/doc/rg.1.md
+++ b/doc/rg.1.md
@@ -95,6 +95,22 @@ Project home page: https://github.com/BurntSushi/ripgrep
-C, --context *NUM*
: Show NUM lines before and after each match.
+--colors *SPEC* ...
+: 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.
+
+ The 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}.
+
+ For example, the following command will change the match color to magenta
+ and the background color for line numbers to yellow:
+
+ rg --colors 'match:fg:magenta' --colors 'line:bg:yellow' foo.
+
--column
: Show column numbers (1 based) in output. This only shows the column
numbers for the first match on each line. Note that this doesn't try
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: Opti