1
0
mirror of https://github.com/BurntSushi/ripgrep.git synced 2024-12-12 19:18:24 +02:00

Stream results when feasible.

For example, when only a single file (or stdin) is being searched, then we
should be able to print directly to the terminal instead of intermediate
buffers. (The buffers are only necessary for parallelism.)

Closes #4.
This commit is contained in:
Andrew Gallant 2016-09-13 21:11:46 -04:00
parent f11d9fb922
commit fdca74148d
12 changed files with 493 additions and 650 deletions

View File

@ -9,16 +9,20 @@ use grep::{Grep, GrepBuilder};
use log; use log;
use num_cpus; use num_cpus;
use regex; use regex;
use term::Terminal; use term::{self, Terminal};
#[cfg(windows)]
use term::WinConsole;
use walkdir::WalkDir; use walkdir::WalkDir;
use atty; use atty;
use gitignore::{Gitignore, GitignoreBuilder}; use gitignore::{Gitignore, GitignoreBuilder};
use ignore::Ignore; use ignore::Ignore;
use out::{Out, OutBuffer}; use out::{Out, ColoredTerminal};
use printer::Printer; use printer::Printer;
use search_buffer::BufferSearcher; use search_buffer::BufferSearcher;
use search_stream::{InputBuffer, Searcher}; use search_stream::{InputBuffer, Searcher};
#[cfg(windows)]
use terminal_win::WindowsBuffer;
use types::{FileTypeDef, Types, TypesBuilder}; use types::{FileTypeDef, Types, TypesBuilder};
use walk; use walk;
@ -442,7 +446,7 @@ impl Args {
/// Create a new printer of individual search results that writes to the /// Create a new printer of individual search results that writes to the
/// writer given. /// writer given.
pub fn printer<W: Send + Terminal>(&self, wtr: W) -> Printer<W> { pub fn printer<W: Terminal + Send>(&self, wtr: W) -> Printer<W> {
let mut p = Printer::new(wtr) let mut p = Printer::new(wtr)
.column(self.column) .column(self.column)
.context_separator(self.context_separator.clone()) .context_separator(self.context_separator.clone())
@ -469,8 +473,29 @@ impl Args {
} }
/// Create a new buffer for use with searching. /// Create a new buffer for use with searching.
pub fn outbuf(&self) -> OutBuffer { #[cfg(not(windows))]
OutBuffer::new(self.color) 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 buffer for use with searching.
#[cfg(windows)]
pub fn stdout(&self) -> ColoredTerminal<WinConsole<io::Stdout>> {
ColoredTerminal::new_stdout(self.color)
} }
/// Return the paths that should be searched. /// Return the paths that should be searched.

View File

@ -21,7 +21,6 @@ additional rules such as whitelists (prefix of `!`) or directory-only globs
// TODO(burntsushi): Implement something similar, but for Mercurial. We can't // TODO(burntsushi): Implement something similar, but for Mercurial. We can't
// use this exact implementation because hgignore files are different. // use this exact implementation because hgignore files are different.
use std::env;
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::fs::File; use std::fs::File;
@ -89,21 +88,10 @@ pub struct Gitignore {
} }
impl Gitignore { impl Gitignore {
/// Create a new gitignore glob matcher from the gitignore file at the
/// given path. The root of the gitignore file is the basename of path.
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Gitignore, Error> {
let root = match path.as_ref().parent() {
Some(parent) => parent.to_path_buf(),
None => env::current_dir().unwrap_or(Path::new("/").to_path_buf()),
};
let mut builder = GitignoreBuilder::new(root);
try!(builder.add_path(path));
builder.build()
}
/// Create a new gitignore glob matcher from the given root directory and /// Create a new gitignore glob matcher from the given root directory and
/// string containing the contents of a gitignore file. /// string containing the contents of a gitignore file.
pub fn from_str<P: AsRef<Path>>( #[allow(dead_code)]
fn from_str<P: AsRef<Path>>(
root: P, root: P,
gitignore: &str, gitignore: &str,
) -> Result<Gitignore, Error> { ) -> Result<Gitignore, Error> {
@ -159,11 +147,6 @@ impl Gitignore {
pub fn num_ignores(&self) -> u64 { pub fn num_ignores(&self) -> u64 {
self.num_ignores self.num_ignores
} }
/// Returns the total number of whitelisted patterns.
pub fn num_whitelist(&self) -> u64 {
self.num_whitelist
}
} }
/// The result of a glob match. /// The result of a glob match.
@ -182,6 +165,7 @@ pub enum Match<'a> {
impl<'a> Match<'a> { impl<'a> Match<'a> {
/// Returns true if the match result implies the path should be ignored. /// Returns true if the match result implies the path should be ignored.
#[allow(dead_code)]
pub fn is_ignored(&self) -> bool { pub fn is_ignored(&self) -> bool {
match *self { match *self {
Match::Ignored(_) => true, Match::Ignored(_) => true,

View File

@ -95,6 +95,7 @@ impl Set {
} }
/// Returns the number of glob patterns in this set. /// Returns the number of glob patterns in this set.
#[allow(dead_code)]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.set.len() self.set.len()
} }
@ -137,6 +138,7 @@ impl SetBuilder {
/// ///
/// If the pattern could not be parsed as a glob, then an error is /// If the pattern could not be parsed as a glob, then an error is
/// returned. /// returned.
#[allow(dead_code)]
pub fn add(&mut self, pat: &str) -> Result<(), Error> { pub fn add(&mut self, pat: &str) -> Result<(), Error> {
self.add_with(pat, &MatchOptions::default()) self.add_with(pat, &MatchOptions::default())
} }
@ -205,6 +207,7 @@ impl Pattern {
/// Convert this pattern to a string that is guaranteed to be a valid /// Convert this pattern to a string that is guaranteed to be a valid
/// regular expression and will represent the matching semantics of this /// regular expression and will represent the matching semantics of this
/// glob pattern. This uses a default set of options. /// glob pattern. This uses a default set of options.
#[allow(dead_code)]
pub fn to_regex(&self) -> String { pub fn to_regex(&self) -> String {
self.to_regex_with(&MatchOptions::default()) self.to_regex_with(&MatchOptions::default())
} }
@ -315,7 +318,7 @@ impl<'a> Parser<'a> {
} }
return Ok(()); return Ok(());
} }
let last = self.p.tokens.pop().unwrap(); self.p.tokens.pop().unwrap();
if prev != Some('/') { if prev != Some('/') {
return Err(Error::InvalidRecursive); return Err(Error::InvalidRecursive);
} }

View File

@ -1,5 +1,3 @@
#![allow(dead_code, unused_variables)]
extern crate crossbeam; extern crate crossbeam;
extern crate docopt; extern crate docopt;
extern crate env_logger; extern crate env_logger;
@ -25,7 +23,7 @@ extern crate winapi;
use std::error::Error; use std::error::Error;
use std::fs::File; use std::fs::File;
use std::io; use std::io;
use std::path::Path; use std::path::{Path, PathBuf};
use std::process; use std::process;
use std::result; use std::result;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -38,9 +36,11 @@ use term::Terminal;
use walkdir::DirEntry; use walkdir::DirEntry;
use args::Args; use args::Args;
use out::{NoColorTerminal, Out, OutBuffer}; use out::{ColoredTerminal, Out};
use printer::Printer; use printer::Printer;
use search_stream::InputBuffer; use search_stream::InputBuffer;
#[cfg(windows)]
use terminal_win::WindowsBuffer;
macro_rules! errored { macro_rules! errored {
($($tt:tt)*) => { ($($tt:tt)*) => {
@ -64,7 +64,8 @@ mod out;
mod printer; mod printer;
mod search_buffer; mod search_buffer;
mod search_stream; mod search_stream;
mod terminal; #[cfg(windows)]
mod terminal_win;
mod types; mod types;
mod walk; mod walk;
@ -73,7 +74,7 @@ pub type Result<T> = result::Result<T, Box<Error + Send + Sync>>;
fn main() { fn main() {
match Args::parse().and_then(run) { match Args::parse().and_then(run) {
Ok(count) if count == 0 => process::exit(1), Ok(count) if count == 0 => process::exit(1),
Ok(count) => process::exit(0), Ok(_) => process::exit(0),
Err(err) => { Err(err) => {
eprintln!("{}", err); eprintln!("{}", err);
process::exit(1); process::exit(1);
@ -82,34 +83,40 @@ fn main() {
} }
fn run(args: Args) -> Result<u64> { fn run(args: Args) -> Result<u64> {
let args = Arc::new(args);
let paths = args.paths();
if args.files() { if args.files() {
return run_files(args); return run_files(args.clone());
} }
if args.type_list() { if args.type_list() {
return run_types(args); return run_types(args.clone());
} }
let args = Arc::new(args); if paths.len() == 1 && (paths[0] == Path::new("-") || paths[0].is_file()) {
return run_one(args.clone(), &paths[0]);
}
let out = Arc::new(Mutex::new(args.out())); let out = Arc::new(Mutex::new(args.out()));
let outbuf = args.outbuf();
let mut workers = vec![]; let mut workers = vec![];
let mut workq = { let mut workq = {
let (workq, stealer) = chase_lev::deque(); let (workq, stealer) = chase_lev::deque();
for _ in 0..args.threads() { for _ in 0..args.threads() {
let worker = Worker { let worker = MultiWorker {
args: args.clone(),
out: out.clone(),
chan_work: stealer.clone(), chan_work: stealer.clone(),
inpbuf: args.input_buffer(), out: out.clone(),
outbuf: Some(outbuf.clone()), outbuf: Some(args.outbuf()),
grep: args.grep(), worker: Worker {
match_count: 0, args: args.clone(),
inpbuf: args.input_buffer(),
grep: args.grep(),
match_count: 0,
},
}; };
workers.push(thread::spawn(move || worker.run())); workers.push(thread::spawn(move || worker.run()));
} }
workq workq
}; };
for p in args.paths() { for p in paths {
if p == Path::new("-") { if p == Path::new("-") {
workq.push(Work::Stdin) workq.push(Work::Stdin)
} else { } else {
@ -128,8 +135,27 @@ fn run(args: Args) -> Result<u64> {
Ok(match_count) Ok(match_count)
} }
fn run_files(args: Args) -> Result<u64> { fn run_one(args: Arc<Args>, path: &Path) -> Result<u64> {
let term = NoColorTerminal::new(io::BufWriter::new(io::stdout())); let mut worker = Worker {
args: args.clone(),
inpbuf: args.input_buffer(),
grep: args.grep(),
match_count: 0,
};
let term = args.stdout();
let mut printer = args.printer(term);
let work =
if path == Path::new("-") {
WorkReady::Stdin
} else {
WorkReady::PathFile(path.to_path_buf(), try!(File::open(path)))
};
worker.do_work(&mut printer, work);
Ok(worker.match_count)
}
fn run_files(args: Arc<Args>) -> Result<u64> {
let term = args.stdout();
let mut printer = args.printer(term); let mut printer = args.printer(term);
let mut file_count = 0; let mut file_count = 0;
for p in args.paths() { for p in args.paths() {
@ -146,8 +172,8 @@ fn run_files(args: Args) -> Result<u64> {
Ok(file_count) Ok(file_count)
} }
fn run_types(args: Args) -> Result<u64> { fn run_types(args: Arc<Args>) -> Result<u64> {
let term = NoColorTerminal::new(io::BufWriter::new(io::stdout())); let term = args.stdout();
let mut printer = args.printer(term); let mut printer = args.printer(term);
let mut ty_count = 0; let mut ty_count = 0;
for def in args.type_defs() { for def in args.type_defs() {
@ -165,22 +191,29 @@ enum Work {
enum WorkReady { enum WorkReady {
Stdin, Stdin,
File(DirEntry, File), DirFile(DirEntry, File),
PathFile(PathBuf, File),
}
struct MultiWorker {
chan_work: Stealer<Work>,
out: Arc<Mutex<Out>>,
#[cfg(not(windows))]
outbuf: Option<ColoredTerminal<term::TerminfoTerminal<Vec<u8>>>>,
#[cfg(windows)]
outbuf: Option<ColoredTerminal<WindowsBuffer>>,
worker: Worker,
} }
struct Worker { struct Worker {
args: Arc<Args>, args: Arc<Args>,
out: Arc<Mutex<Out>>,
chan_work: Stealer<Work>,
inpbuf: InputBuffer, inpbuf: InputBuffer,
outbuf: Option<OutBuffer>,
grep: Grep, grep: Grep,
match_count: u64, match_count: u64,
} }
impl Worker { impl MultiWorker {
fn run(mut self) -> u64 { fn run(mut self) -> u64 {
self.match_count = 0;
loop { loop {
let work = match self.chan_work.steal() { let work = match self.chan_work.steal() {
Steal::Empty | Steal::Abort => continue, Steal::Empty | Steal::Abort => continue,
@ -188,7 +221,7 @@ impl Worker {
Steal::Data(Work::Stdin) => WorkReady::Stdin, Steal::Data(Work::Stdin) => WorkReady::Stdin,
Steal::Data(Work::File(ent)) => { Steal::Data(Work::File(ent)) => {
match File::open(ent.path()) { match File::open(ent.path()) {
Ok(file) => WorkReady::File(ent, file), Ok(file) => WorkReady::DirFile(ent, file),
Err(err) => { Err(err) => {
eprintln!("{}: {}", ent.path().display(), err); eprintln!("{}: {}", ent.path().display(), err);
continue; continue;
@ -198,8 +231,8 @@ impl Worker {
}; };
let mut outbuf = self.outbuf.take().unwrap(); let mut outbuf = self.outbuf.take().unwrap();
outbuf.clear(); outbuf.clear();
let mut printer = self.args.printer(outbuf); let mut printer = self.worker.args.printer(outbuf);
self.do_work(&mut printer, work); self.worker.do_work(&mut printer, work);
let outbuf = printer.into_inner(); let outbuf = printer.into_inner();
if !outbuf.get_ref().is_empty() { if !outbuf.get_ref().is_empty() {
let mut out = self.out.lock().unwrap(); let mut out = self.out.lock().unwrap();
@ -207,10 +240,12 @@ impl Worker {
} }
self.outbuf = Some(outbuf); self.outbuf = Some(outbuf);
} }
self.match_count self.worker.match_count
} }
}
fn do_work<W: Send + Terminal>( impl Worker {
fn do_work<W: Terminal + Send>(
&mut self, &mut self,
printer: &mut Printer<W>, printer: &mut Printer<W>,
work: WorkReady, work: WorkReady,
@ -221,7 +256,7 @@ impl Worker {
let stdin = stdin.lock(); let stdin = stdin.lock();
self.search(printer, &Path::new("<stdin>"), stdin) self.search(printer, &Path::new("<stdin>"), stdin)
} }
WorkReady::File(ent, file) => { WorkReady::DirFile(ent, file) => {
let mut path = ent.path(); let mut path = ent.path();
if let Ok(p) = path.strip_prefix("./") { if let Ok(p) = path.strip_prefix("./") {
path = p; path = p;
@ -232,6 +267,17 @@ impl Worker {
self.search(printer, path, file) self.search(printer, path, file)
} }
} }
WorkReady::PathFile(path, file) => {
let mut path = &*path;
if let Ok(p) = path.strip_prefix("./") {
path = p;
}
if self.args.mmap() {
self.search_mmap(printer, path, &file)
} else {
self.search(printer, path, file)
}
}
}; };
match result { match result {
Ok(count) => { Ok(count) => {
@ -243,7 +289,7 @@ impl Worker {
} }
} }
fn search<R: io::Read, W: Send + Terminal>( fn search<R: io::Read, W: Terminal + Send>(
&mut self, &mut self,
printer: &mut Printer<W>, printer: &mut Printer<W>,
path: &Path, path: &Path,
@ -258,7 +304,7 @@ impl Worker {
).run().map_err(From::from) ).run().map_err(From::from)
} }
fn search_mmap<W: Send + Terminal>( fn search_mmap<W: Terminal + Send>(
&mut self, &mut self,
printer: &mut Printer<W>, printer: &mut Printer<W>,
path: &Path, path: &Path,

View File

@ -1,40 +1,12 @@
use std::io::{self, Write}; use std::io::{self, Write};
use std::sync::Arc;
use term::{self, Terminal}; use term::{self, Terminal};
use term::color::Color;
use term::terminfo::TermInfo; use term::terminfo::TermInfo;
#[cfg(windows)] #[cfg(windows)]
use term::WinConsole; use term::WinConsole;
use terminal::TerminfoTerminal;
pub type StdoutTerminal = Box<Terminal<Output=io::Stdout> + Send>;
/// Gets a terminal that supports color if available.
#[cfg(windows)] #[cfg(windows)]
fn term_stdout(color: bool) -> StdoutTerminal { use terminal_win::WindowsBuffer;
let stdout = io::stdout();
WinConsole::new(stdout)
.ok()
.map(|t| Box::new(t) as StdoutTerminal)
.unwrap_or_else(|| {
let stdout = io::stdout();
Box::new(NoColorTerminal::new(stdout)) as StdoutTerminal
})
}
/// Gets a terminal that supports color if available.
#[cfg(not(windows))]
fn term_stdout(color: bool) -> StdoutTerminal {
let stdout = io::stdout();
if !color || TERMINFO.is_none() {
Box::new(NoColorTerminal::new(stdout))
} else {
let info = TERMINFO.clone().unwrap();
Box::new(TerminfoTerminal::new_with_terminfo(stdout, info))
}
}
/// Out controls the actual output of all search results for a particular file /// Out controls the actual output of all search results for a particular file
/// to the end user. /// to the end user.
@ -43,16 +15,31 @@ fn term_stdout(color: bool) -> StdoutTerminal {
/// individual search results where as Out works with search results for each /// 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.) /// file as a whole. For example, it knows when to print a file separator.)
pub struct Out { pub struct Out {
term: StdoutTerminal, #[cfg(not(windows))]
term: ColoredTerminal<term::TerminfoTerminal<io::BufWriter<io::Stdout>>>,
#[cfg(windows)]
term: ColoredTerminal<WinConsole<io::Stdout>>,
printed: bool, printed: bool,
file_separator: Option<Vec<u8>>, file_separator: Option<Vec<u8>>,
} }
impl Out { impl Out {
/// Create a new Out that writes to the wtr given. /// 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 { pub fn new(color: bool) -> Out {
Out { Out {
term: term_stdout(color), term: ColoredTerminal::new_stdout(color),
printed: false, printed: false,
file_separator: None, file_separator: None,
} }
@ -69,154 +56,194 @@ impl Out {
/// Write the search results of a single file to the underlying wtr and /// Write the search results of a single file to the underlying wtr and
/// flush wtr. /// flush wtr.
pub fn write(&mut self, buf: &OutBuffer) { #[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 let Some(ref sep) = self.file_separator {
if self.printed { if self.printed {
let _ = self.term.write_all(sep); let _ = self.term.write_all(sep);
let _ = self.term.write_all(b"\n"); let _ = self.term.write_all(b"\n");
} }
} }
match *buf { }
OutBuffer::Colored(ref tt) => {
let _ = self.term.write_all(tt.get_ref()); fn write_done(&mut self) {
}
OutBuffer::Windows(ref w) => {
w.print_stdout(&mut self.term);
}
OutBuffer::NoColor(ref buf) => {
let _ = self.term.write_all(buf);
}
}
let _ = self.term.flush(); let _ = self.term.flush();
self.printed = true; self.printed = true;
} }
} }
/// OutBuffer corresponds to the final output buffer for search results. All /// ColoredTerminal provides optional colored output through the term::Terminal
/// search results are written to a buffer and then a buffer is flushed to /// trait. In particular, it will dynamically configure itself to use coloring
/// stdout only after the full search has completed. /// if it's available in the environment.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum OutBuffer { pub enum ColoredTerminal<T: Terminal + Send> {
Colored(TerminfoTerminal<Vec<u8>>), Colored(T),
Windows(WindowsBuffer), NoColor(T::Output),
NoColor(Vec<u8>),
} }
#[derive(Clone, Debug)] #[cfg(not(windows))]
pub struct WindowsBuffer { impl<W: io::Write + Send> ColoredTerminal<term::TerminfoTerminal<W>> {
buf: Vec<u8>,
pos: usize,
colors: Vec<WindowsColor>,
}
#[derive(Clone, Debug)]
pub struct WindowsColor {
pos: usize,
opt: WindowsOption,
}
#[derive(Clone, Debug)]
pub enum WindowsOption {
Foreground(Color),
Background(Color),
Reset,
}
lazy_static! {
static ref TERMINFO: Option<Arc<TermInfo>> = {
match TermInfo::from_env() {
Ok(info) => Some(Arc::new(info)),
Err(err) => {
debug!("error loading terminfo for coloring: {}", err);
None
}
}
};
}
impl OutBuffer {
/// Create a new output buffer. /// Create a new output buffer.
/// ///
/// When color is true, the buffer will attempt to support coloring. /// When color is true, the buffer will attempt to support coloring.
pub fn new(color: bool) -> OutBuffer { pub fn new(wtr: W, color: bool) -> Self {
// If we want color, build a TerminfoTerminal and see if the current lazy_static! {
// environment supports coloring. If not, bail with NoColor. To avoid // Only pay for parsing the terminfo once.
// losing our writer (ownership), do this the long way. 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 { if !color {
return OutBuffer::NoColor(vec![]); return ColoredTerminal::NoColor(wtr);
} }
if cfg!(windows) { let terminfo = match *TERMINFO {
return OutBuffer::Windows(WindowsBuffer { None => return ColoredTerminal::NoColor(wtr),
buf: vec![], Some(ref ti) => {
pos: 0, // Ug, this should go away with the next release of `term`.
colors: vec![] TermInfo {
}); names: ti.names.clone(),
} bools: ti.bools.clone(),
if TERMINFO.is_none() { numbers: ti.numbers.clone(),
return OutBuffer::NoColor(vec![]); strings: ti.strings.clone(),
} }
let info = TERMINFO.clone().unwrap(); }
let tt = TerminfoTerminal::new_with_terminfo(vec![], info); };
let tt = term::TerminfoTerminal::new_with_terminfo(wtr, terminfo);
if !tt.supports_color() { if !tt.supports_color() {
debug!("environment doesn't support coloring"); debug!("environment doesn't support coloring");
return OutBuffer::NoColor(tt.into_inner()); 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())
} }
OutBuffer::Colored(tt)
} }
/// Clear the give buffer of all search results such that it is reusable /// Clear the give buffer of all search results such that it is reusable
/// in another search. /// in another search.
pub fn clear(&mut self) { pub fn clear(&mut self) {
match *self { match *self {
OutBuffer::Colored(ref mut tt) => { ColoredTerminal::Colored(ref mut win) => win.clear(),
tt.get_mut().clear(); ColoredTerminal::NoColor(ref mut buf) => buf.clear(),
}
OutBuffer::Windows(ref mut win) => {
win.buf.clear();
win.colors.clear();
win.pos = 0;
}
OutBuffer::NoColor(ref mut buf) => {
buf.clear();
}
} }
} }
}
fn map_result<F, G>( #[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 self,
mut f: F, mut f: F,
mut g: G,
) -> term::Result<()> ) -> term::Result<()>
where F: FnMut(&mut TerminfoTerminal<Vec<u8>>) -> term::Result<()>, where F: FnMut(&mut T) -> term::Result<()> {
G: FnMut(&mut WindowsBuffer) -> term::Result<()> {
match *self { match *self {
OutBuffer::Colored(ref mut w) => f(w), ColoredTerminal::Colored(ref mut w) => f(w),
OutBuffer::Windows(ref mut w) => g(w), ColoredTerminal::NoColor(_) => Err(term::Error::NotSupported),
OutBuffer::NoColor(_) => Err(term::Error::NotSupported),
} }
} }
fn map_bool<F, G>( fn map_bool<F>(
&self, &self,
mut f: F, mut f: F,
mut g: G,
) -> bool ) -> bool
where F: FnMut(&TerminfoTerminal<Vec<u8>>) -> bool, where F: FnMut(&T) -> bool {
G: FnMut(&WindowsBuffer) -> bool {
match *self { match *self {
OutBuffer::Colored(ref w) => f(w), ColoredTerminal::Colored(ref w) => f(w),
OutBuffer::Windows(ref w) => g(w), ColoredTerminal::NoColor(_) => false,
OutBuffer::NoColor(_) => false,
} }
} }
} }
impl io::Write for OutBuffer { impl<T: Terminal + Send> io::Write for ColoredTerminal<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> { fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self { match *self {
OutBuffer::Colored(ref mut w) => w.write(buf), ColoredTerminal::Colored(ref mut w) => w.write(buf),
OutBuffer::Windows(ref mut w) => w.write(buf), ColoredTerminal::NoColor(ref mut w) => w.write(buf),
OutBuffer::NoColor(ref mut w) => w.write(buf),
} }
} }
@ -225,259 +252,67 @@ impl io::Write for OutBuffer {
} }
} }
impl term::Terminal for OutBuffer { impl<T: Terminal + Send> term::Terminal for ColoredTerminal<T> {
type Output = Vec<u8>; type Output = T::Output;
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> { fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
self.map_result(|w| w.fg(fg), |w| w.fg(fg)) self.map_result(|w| w.fg(fg))
} }
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> { fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
self.map_result(|w| w.bg(bg), |w| w.bg(bg)) self.map_result(|w| w.bg(bg))
} }
fn attr(&mut self, attr: term::Attr) -> term::Result<()> { fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
self.map_result(|w| w.attr(attr), |w| w.attr(attr)) self.map_result(|w| w.attr(attr))
} }
fn supports_attr(&self, attr: term::Attr) -> bool { fn supports_attr(&self, attr: term::Attr) -> bool {
self.map_bool(|w| w.supports_attr(attr), |w| w.supports_attr(attr)) self.map_bool(|w| w.supports_attr(attr))
} }
fn reset(&mut self) -> term::Result<()> { fn reset(&mut self) -> term::Result<()> {
self.map_result(|w| w.reset(), |w| w.reset()) self.map_result(|w| w.reset())
} }
fn supports_reset(&self) -> bool { fn supports_reset(&self) -> bool {
self.map_bool(|w| w.supports_reset(), |w| w.supports_reset()) self.map_bool(|w| w.supports_reset())
} }
fn supports_color(&self) -> bool { fn supports_color(&self) -> bool {
self.map_bool(|w| w.supports_color(), |w| w.supports_color()) self.map_bool(|w| w.supports_color())
} }
fn cursor_up(&mut self) -> term::Result<()> { fn cursor_up(&mut self) -> term::Result<()> {
self.map_result(|w| w.cursor_up(), |w| w.cursor_up()) self.map_result(|w| w.cursor_up())
} }
fn delete_line(&mut self) -> term::Result<()> { fn delete_line(&mut self) -> term::Result<()> {
self.map_result(|w| w.delete_line(), |w| w.delete_line()) self.map_result(|w| w.delete_line())
} }
fn carriage_return(&mut self) -> term::Result<()> { fn carriage_return(&mut self) -> term::Result<()> {
self.map_result(|w| w.carriage_return(), |w| w.carriage_return()) self.map_result(|w| w.carriage_return())
} }
fn get_ref(&self) -> &Vec<u8> { fn get_ref(&self) -> &Self::Output {
match *self { match *self {
OutBuffer::Colored(ref w) => w.get_ref(), ColoredTerminal::Colored(ref w) => w.get_ref(),
OutBuffer::Windows(ref w) => w.get_ref(), ColoredTerminal::NoColor(ref w) => w,
OutBuffer::NoColor(ref w) => w,
} }
} }
fn get_mut(&mut self) -> &mut Vec<u8> { fn get_mut(&mut self) -> &mut Self::Output {
match *self { match *self {
OutBuffer::Colored(ref mut w) => w.get_mut(), ColoredTerminal::Colored(ref mut w) => w.get_mut(),
OutBuffer::Windows(ref mut w) => w.get_mut(), ColoredTerminal::NoColor(ref mut w) => w,
OutBuffer::NoColor(ref mut w) => w,
} }
} }
fn into_inner(self) -> Vec<u8> { fn into_inner(self) -> Self::Output {
match self { match self {
OutBuffer::Colored(w) => w.into_inner(), ColoredTerminal::Colored(w) => w.into_inner(),
OutBuffer::Windows(w) => w.into_inner(), ColoredTerminal::NoColor(w) => w,
OutBuffer::NoColor(w) => w,
} }
} }
} }
impl WindowsBuffer {
fn push(&mut self, opt: WindowsOption) {
let pos = self.pos;
self.colors.push(WindowsColor { pos: pos, opt: opt });
}
}
impl WindowsBuffer {
/// Print the contents to the given terminal.
pub fn print_stdout(&self, tt: &mut StdoutTerminal) {
if !tt.supports_color() {
let _ = tt.write_all(&self.buf);
let _ = tt.flush();
return;
}
let mut last = 0;
for col in &self.colors {
let _ = tt.write_all(&self.buf[last..col.pos]);
match col.opt {
WindowsOption::Foreground(c) => {
let _ = tt.fg(c);
}
WindowsOption::Background(c) => {
let _ = tt.bg(c);
}
WindowsOption::Reset => {
let _ = tt.reset();
}
}
last = col.pos;
}
let _ = tt.write_all(&self.buf[last..]);
let _ = tt.flush();
}
}
impl io::Write for WindowsBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = try!(self.buf.write(buf));
self.pos += n;
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl term::Terminal for WindowsBuffer {
type Output = Vec<u8>;
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
self.push(WindowsOption::Foreground(fg));
Ok(())
}
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
self.push(WindowsOption::Background(bg));
Ok(())
}
fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn supports_attr(&self, attr: term::Attr) -> bool {
false
}
fn reset(&mut self) -> term::Result<()> {
self.push(WindowsOption::Reset);
Ok(())
}
fn supports_reset(&self) -> bool {
true
}
fn supports_color(&self) -> bool {
true
}
fn cursor_up(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn delete_line(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn carriage_return(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn get_ref(&self) -> &Vec<u8> {
&self.buf
}
fn get_mut(&mut self) -> &mut Vec<u8> {
&mut self.buf
}
fn into_inner(self) -> Vec<u8> {
self.buf
}
}
/// NoColorTerminal implements Terminal, but supports no coloring.
///
/// Its useful when an API requires a Terminal, but coloring isn't needed.
pub struct NoColorTerminal<W> {
wtr: W,
}
impl<W: Send + io::Write> NoColorTerminal<W> {
/// Wrap the given writer in a Terminal interface.
pub fn new(wtr: W) -> NoColorTerminal<W> {
NoColorTerminal {
wtr: wtr,
}
}
}
impl<W: Send + io::Write> io::Write for NoColorTerminal<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.wtr.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.wtr.flush()
}
}
impl<W: Send + io::Write> term::Terminal for NoColorTerminal<W> {
type Output = W;
fn fg(&mut self, fg: term::color::Color) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn bg(&mut self, bg: term::color::Color) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn attr(&mut self, attr: term::Attr) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn supports_attr(&self, attr: term::Attr) -> bool {
false
}
fn reset(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn supports_reset(&self) -> bool {
false
}
fn supports_color(&self) -> bool {
false
}
fn cursor_up(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn delete_line(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn carriage_return(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn get_ref(&self) -> &W {
&self.wtr
}
fn get_mut(&mut self) -> &mut W {
&mut self.wtr
}
fn into_inner(self) -> W {
self.wtr
}
}

View File

@ -36,7 +36,7 @@ pub struct Printer<W> {
with_filename: bool, with_filename: bool,
} }
impl<W: Send + Terminal> Printer<W> { impl<W: Terminal + Send> Printer<W> {
/// Create a new printer that writes to wtr. /// Create a new printer that writes to wtr.
pub fn new(wtr: W) -> Printer<W> { pub fn new(wtr: W) -> Printer<W> {
Printer { Printer {

View File

@ -151,10 +151,10 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> {
mod tests { mod tests {
use std::path::Path; use std::path::Path;
use grep::{Grep, GrepBuilder}; use grep::GrepBuilder;
use term::Terminal; use term::{Terminal, TerminfoTerminal};
use out::OutBuffer; use out::ColoredTerminal;
use printer::Printer; use printer::Printer;
use super::BufferSearcher; use super::BufferSearcher;
@ -168,38 +168,19 @@ but Doctor Watson has to have it taken out for him and dusted,
and exhibited clearly, with a label attached.\ and exhibited clearly, with a label attached.\
"; ";
const CODE: &'static str = "\
extern crate snap;
use std::io;
fn main() {
let stdin = io::stdin();
let stdout = io::stdout();
// Wrap the stdin reader in a Snappy reader.
let mut rdr = snap::Reader::new(stdin.lock());
let mut wtr = stdout.lock();
io::copy(&mut rdr, &mut wtr).expect(\"I/O operation failed\");
}
";
fn matcher(pat: &str) -> Grep {
GrepBuilder::new(pat).build().unwrap()
}
fn test_path() -> &'static Path { fn test_path() -> &'static Path {
&Path::new("/baz.rs") &Path::new("/baz.rs")
} }
type TestSearcher<'a> = BufferSearcher<'a, OutBuffer>; type TestSearcher<'a> =
BufferSearcher<'a, ColoredTerminal<TerminfoTerminal<Vec<u8>>>>;
fn search<F: FnMut(TestSearcher) -> TestSearcher>( fn search<F: FnMut(TestSearcher) -> TestSearcher>(
pat: &str, pat: &str,
haystack: &str, haystack: &str,
mut map: F, mut map: F,
) -> (u64, String) { ) -> (u64, String) {
let outbuf = OutBuffer::NoColor(vec![]); let outbuf = ColoredTerminal::NoColor(vec![]);
let mut pp = Printer::new(outbuf).with_filename(true); let mut pp = Printer::new(outbuf).with_filename(true);
let grep = GrepBuilder::new(pat).build().unwrap(); let grep = GrepBuilder::new(pat).build().unwrap();
let count = { let count = {

View File

@ -100,7 +100,7 @@ impl Default for Options {
} }
} }
impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> { impl<'a, R: io::Read, W: Terminal + Send> Searcher<'a, R, W> {
/// Create a new searcher. /// Create a new searcher.
/// ///
/// `inp` is a reusable input buffer that is used as scratch space by this /// `inp` is a reusable input buffer that is used as scratch space by this
@ -763,10 +763,10 @@ mod tests {
use std::io; use std::io;
use std::path::Path; use std::path::Path;
use grep::{Grep, GrepBuilder}; use grep::GrepBuilder;
use term::Terminal; use term::{Terminal, TerminfoTerminal};
use out::OutBuffer; use out::ColoredTerminal;
use printer::Printer; use printer::Printer;
use super::{InputBuffer, Searcher, start_of_previous_lines}; use super::{InputBuffer, Searcher, start_of_previous_lines};
@ -800,15 +800,15 @@ fn main() {
io::Cursor::new(s.to_string().into_bytes()) io::Cursor::new(s.to_string().into_bytes())
} }
fn matcher(pat: &str) -> Grep {
GrepBuilder::new(pat).build().unwrap()
}
fn test_path() -> &'static Path { fn test_path() -> &'static Path {
&Path::new("/baz.rs") &Path::new("/baz.rs")
} }
type TestSearcher<'a> = Searcher<'a, io::Cursor<Vec<u8>>, OutBuffer>; type TestSearcher<'a> = Searcher<
'a,
io::Cursor<Vec<u8>>,
ColoredTerminal<TerminfoTerminal<Vec<u8>>>,
>;
fn search_smallcap<F: FnMut(TestSearcher) -> TestSearcher>( fn search_smallcap<F: FnMut(TestSearcher) -> TestSearcher>(
pat: &str, pat: &str,
@ -816,7 +816,7 @@ fn main() {
mut map: F, mut map: F,
) -> (u64, String) { ) -> (u64, String) {
let mut inp = InputBuffer::with_capacity(1); let mut inp = InputBuffer::with_capacity(1);
let outbuf = OutBuffer::NoColor(vec![]); let outbuf = ColoredTerminal::NoColor(vec![]);
let mut pp = Printer::new(outbuf).with_filename(true); let mut pp = Printer::new(outbuf).with_filename(true);
let grep = GrepBuilder::new(pat).build().unwrap(); let grep = GrepBuilder::new(pat).build().unwrap();
let count = { let count = {
@ -833,7 +833,7 @@ fn main() {
mut map: F, mut map: F,
) -> (u64, String) { ) -> (u64, String) {
let mut inp = InputBuffer::with_capacity(4096); let mut inp = InputBuffer::with_capacity(4096);
let outbuf = OutBuffer::NoColor(vec![]); let outbuf = ColoredTerminal::NoColor(vec![]);
let mut pp = Printer::new(outbuf).with_filename(true); let mut pp = Printer::new(outbuf).with_filename(true);
let grep = GrepBuilder::new(pat).build().unwrap(); let grep = GrepBuilder::new(pat).build().unwrap();
let count = { let count = {

View File

@ -1,202 +0,0 @@
/*!
This module contains an implementation of the `term::Terminal` trait.
The actual implementation is copied almost verbatim from the `term` crate, so
this code is under the same license (MIT/Apache).
The specific reason why this is copied here is to wrap an Arc<TermInfo> instead
of a TermInfo. This makes multithreaded sharing much more performant.
N.B. This is temporary until this issue is fixed:
https://github.com/Stebalien/term/issues/64
*/
use std::io::{self, Write};
use std::sync::Arc;
use term::{Attr, Error, Result, Terminal, color};
use term::terminfo::TermInfo;
use term::terminfo::parm::{Param, Variables, expand};
/// A Terminal that knows how many colors it supports, with a reference to its
/// parsed Terminfo database record.
#[derive(Clone, Debug)]
pub struct TerminfoTerminal<T> {
num_colors: u16,
out: T,
ti: Arc<TermInfo>,
}
impl<T: Write + Send> Terminal for TerminfoTerminal<T> {
type Output = T;
fn fg(&mut self, color: color::Color) -> Result<()> {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
return apply_cap(&self.ti, "setaf", &[Param::Number(color as i32)], &mut self.out);
}
Err(Error::ColorOutOfRange)
}
fn bg(&mut self, color: color::Color) -> Result<()> {
let color = self.dim_if_necessary(color);
if self.num_colors > color {
return apply_cap(&self.ti, "setab", &[Param::Number(color as i32)], &mut self.out);
}
Err(Error::ColorOutOfRange)
}
fn attr(&mut self, attr: Attr) -> Result<()> {
match attr {
Attr::ForegroundColor(c) => self.fg(c),
Attr::BackgroundColor(c) => self.bg(c),
_ => apply_cap(&self.ti, cap_for_attr(attr), &[], &mut self.out),
}
}
fn supports_attr(&self, attr: Attr) -> bool {
match attr {
Attr::ForegroundColor(_) | Attr::BackgroundColor(_) => self.num_colors > 0,
_ => {
let cap = cap_for_attr(attr);
self.ti.strings.get(cap).is_some()
}
}
}
fn reset(&mut self) -> Result<()> {
reset(&self.ti, &mut self.out)
}
fn supports_reset(&self) -> bool {
["sgr0", "sgr", "op"].iter().any(|&cap| self.ti.strings.get(cap).is_some())
}
fn supports_color(&self) -> bool {
self.num_colors > 0 && self.supports_reset()
}
fn cursor_up(&mut self) -> Result<()> {
apply_cap(&self.ti, "cuu1", &[], &mut self.out)
}
fn delete_line(&mut self) -> Result<()> {
apply_cap(&self.ti, "dl", &[], &mut self.out)
}
fn carriage_return(&mut self) -> Result<()> {
apply_cap(&self.ti, "cr", &[], &mut self.out)
}
fn get_ref(&self) -> &T {
&self.out
}
fn get_mut(&mut self) -> &mut T {
&mut self.out
}
fn into_inner(self) -> T
where Self: Sized
{
self.out
}
}
impl<T: Write + Send> TerminfoTerminal<T> {
/// Create a new TerminfoTerminal with the given TermInfo and Write.
pub fn new_with_terminfo(out: T, terminfo: Arc<TermInfo>) -> TerminfoTerminal<T> {
let nc = if terminfo.strings.contains_key("setaf") &&
terminfo.strings.contains_key("setab") {
terminfo.numbers.get("colors").map_or(0, |&n| n)
} else {
0
};
TerminfoTerminal {
out: out,
ti: terminfo,
num_colors: nc,
}
}
/// Create a new TerminfoTerminal for the current environment with the given Write.
///
/// Returns `None` when the terminfo cannot be found or parsed.
pub fn new(out: T) -> Option<TerminfoTerminal<T>> {
TermInfo::from_env().map(move |ti| {
TerminfoTerminal::new_with_terminfo(out, Arc::new(ti))
}).ok()
}
fn dim_if_necessary(&self, color: color::Color) -> color::Color {
if color >= self.num_colors && color >= 8 && color < 16 {
color - 8
} else {
color
}
}
}
impl<T: Write> Write for TerminfoTerminal<T> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.out.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.out.flush()
}
}
fn cap_for_attr(attr: Attr) -> &'static str {
match attr {
Attr::Bold => "bold",
Attr::Dim => "dim",
Attr::Italic(true) => "sitm",
Attr::Italic(false) => "ritm",
Attr::Underline(true) => "smul",
Attr::Underline(false) => "rmul",
Attr::Blink => "blink",
Attr::Standout(true) => "smso",
Attr::Standout(false) => "rmso",
Attr::Reverse => "rev",
Attr::Secure => "invis",
Attr::ForegroundColor(_) => "setaf",
Attr::BackgroundColor(_) => "setab",
}
}
fn apply_cap(ti: &TermInfo, cmd: &str, params: &[Param], out: &mut io::Write) -> Result<()> {
match ti.strings.get(cmd) {
Some(cmd) => {
match expand(cmd, params, &mut Variables::new()) {
Ok(s) => {
try!(out.write_all(&s));
Ok(())
}
Err(e) => Err(e.into()),
}
}
None => Err(Error::NotSupported),
}
}
fn reset(ti: &TermInfo, out: &mut io::Write) -> Result<()> {
// are there any terminals that have color/attrs and not sgr0?
// Try falling back to sgr, then op
let cmd = match [("sgr0", &[] as &[Param]), ("sgr", &[Param::Number(0)]), ("op", &[])]
.iter()
.filter_map(|&(cap, params)| {
ti.strings.get(cap).map(|c| (c, params))
})
.next() {
Some((op, params)) => {
match expand(op, params, &mut Variables::new()) {
Ok(cmd) => cmd,
Err(e) => return Err(e.into()),
}
}
None => return Err(Error::NotSupported),
};
try!(out.write_all(&cmd));
Ok(())
}

176
src/terminal_win.rs Normal file
View File

@ -0,0 +1,176 @@
/*!
This module contains a Windows-only *in-memory* implementation of the
`term::Terminal` trait.
This particular implementation is a bit idiosyncratic, and the "in-memory"
specification is to blame. In particular, on Windows, coloring requires
communicating with the console synchronously as data is written to stdout.
This is anathema to how ripgrep fundamentally works: by writing search results
to intermediate thread local buffers in order to maximize parallelism.
Eliminating parallelism on Windows isn't an option, because that would negate
a tremendous performance benefit just for coloring.
We've worked around this by providing an implementation of `term::Terminal`
that records precisely where a color or a reset should be invoked, according
to a byte offset in the in memory buffer. When the buffer is actually printed,
we copy the bytes from the buffer to stdout incrementally while invoking the
corresponding console APIs for coloring at the right location.
(Another approach would be to do ANSI coloring unconditionally, then parse that
and translate it to console commands. The advantage of that approach is that
it doesn't require any additional memory for storing offsets. In practice
though, coloring is only used in the terminal, which tends to correspond to
searches that produce very few results with respect to the corpus searched.
Therefore, this is an acceptable trade off. Namely, we do not pay for it when
coloring is disabled.
*/
use std::io;
use term::{self, Terminal};
use term::color::Color;
/// An in-memory buffer that provides Windows console coloring.
#[derive(Clone, Debug)]
pub struct WindowsBuffer {
buf: Vec<u8>,
pos: usize,
colors: Vec<WindowsColor>,
}
/// A color associated with a particular location in a buffer.
#[derive(Clone, Debug)]
struct WindowsColor {
pos: usize,
opt: WindowsOption,
}
/// A color or reset directive that can be translated into an instruction to
/// the Windows console.
#[derive(Clone, Debug)]
enum WindowsOption {
Foreground(Color),
Background(Color),
Reset,
}
impl WindowsBuffer {
/// Create a new empty buffer for Windows console coloring.
pub fn new() -> WindowsBuffer {
WindowsBuffer {
buf: vec![],
pos: 0,
colors: vec![],
}
}
fn push(&mut self, opt: WindowsOption) {
let pos = self.pos;
self.colors.push(WindowsColor { pos: pos, opt: opt });
}
/// Print the contents to the given terminal.
pub fn print_stdout<T: Terminal + Send>(&self, tt: &mut T) {
if !tt.supports_color() {
let _ = tt.write_all(&self.buf);
let _ = tt.flush();
return;
}
let mut last = 0;
for col in &self.colors {
let _ = tt.write_all(&self.buf[last..col.pos]);
match col.opt {
WindowsOption::Foreground(c) => {
let _ = tt.fg(c);
}
WindowsOption::Background(c) => {
let _ = tt.bg(c);
}
WindowsOption::Reset => {
let _ = tt.reset();
}
}
last = col.pos;
}
let _ = tt.write_all(&self.buf[last..]);
let _ = tt.flush();
}
/// Clear the buffer.
pub fn clear(&mut self) {
self.buf.clear();
self.colors.clear();
self.pos = 0;
}
}
impl io::Write for WindowsBuffer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let n = try!(self.buf.write(buf));
self.pos += n;
Ok(n)
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl Terminal for WindowsBuffer {
type Output = Vec<u8>;
fn fg(&mut self, fg: Color) -> term::Result<()> {
self.push(WindowsOption::Foreground(fg));
Ok(())
}
fn bg(&mut self, bg: Color) -> term::Result<()> {
self.push(WindowsOption::Background(bg));
Ok(())
}
fn attr(&mut self, _attr: term::Attr) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn supports_attr(&self, _attr: term::Attr) -> bool {
false
}
fn reset(&mut self) -> term::Result<()> {
self.push(WindowsOption::Reset);
Ok(())
}
fn supports_reset(&self) -> bool {
true
}
fn supports_color(&self) -> bool {
true
}
fn cursor_up(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn delete_line(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn carriage_return(&mut self) -> term::Result<()> {
Err(term::Error::NotSupported)
}
fn get_ref(&self) -> &Vec<u8> {
&self.buf
}
fn get_mut(&mut self) -> &mut Vec<u8> {
&mut self.buf
}
fn into_inner(self) -> Vec<u8> {
self.buf
}
}

View File

@ -216,7 +216,7 @@ impl Types {
if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) { if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) {
return Match::Ignored(&self.unmatched_pat); return Match::Ignored(&self.unmatched_pat);
} }
if self.selected.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) { if self.selected.as_ref().map(|s|s.is_match(&*name)).unwrap_or(false) {
return Match::Whitelist(&self.unmatched_pat); return Match::Whitelist(&self.unmatched_pat);
} }
if self.has_selected { if self.has_selected {

View File

@ -135,8 +135,3 @@ impl Iterator for WalkEventIter {
} }
} }
} }
fn is_hidden(ent: &DirEntry) -> bool {
ent.depth() > 0 &&
ent.file_name().to_str().map(|s| s.starts_with(".")).unwrap_or(false)
}