Skip to content

Commit

Permalink
Merge pull request #240 from BurntSushi/color
Browse files Browse the repository at this point in the history
Completely re-work colored output and tty handling.
  • Loading branch information
BurntSushi authored Nov 20, 2016
2 parents 03f7605 + e8a30cb commit 883d8fc
Show file tree
Hide file tree
Showing 23 changed files with 2,153 additions and 739 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ target
/grep/Cargo.lock
/globset/Cargo.lock
/ignore/Cargo.lock
/termcolor/Cargo.lock
/wincolor/Cargo.lock
35 changes: 20 additions & 15 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 2 additions & 0 deletions appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions ci/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 22 additions & 0 deletions doc/rg.1
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
16 changes: 16 additions & 0 deletions doc/rg.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 22 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 \
Expand Down
105 changes: 62 additions & 43 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 883d8fc

Please sign in to comment.