From fdca74148dbb168035f538506621e59b92766090 Mon Sep 17 00:00:00 2001 From: Andrew Gallant Date: Tue, 13 Sep 2016 21:11:46 -0400 Subject: [PATCH] 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. --- src/args.rs | 35 ++- src/gitignore.rs | 22 +- src/glob.rs | 5 +- src/main.rs | 118 +++++++--- src/out.rs | 513 +++++++++++++++---------------------------- src/printer.rs | 2 +- src/search_buffer.rs | 31 +-- src/search_stream.rs | 22 +- src/terminal.rs | 202 ----------------- src/terminal_win.rs | 176 +++++++++++++++ src/types.rs | 2 +- src/walk.rs | 5 - 12 files changed, 488 insertions(+), 645 deletions(-) create mode 100644 src/terminal_win.rs diff --git a/src/args.rs b/src/args.rs index 66af8f1a4..1ddc04970 100644 --- a/src/args.rs +++ b/src/args.rs @@ -9,16 +9,20 @@ use grep::{Grep, GrepBuilder}; use log; use num_cpus; use regex; -use term::Terminal; +use term::{self, Terminal}; +#[cfg(windows)] +use term::WinConsole; use walkdir::WalkDir; use atty; use gitignore::{Gitignore, GitignoreBuilder}; use ignore::Ignore; -use out::{Out, OutBuffer}; +use out::{Out, ColoredTerminal}; use printer::Printer; use search_buffer::BufferSearcher; use search_stream::{InputBuffer, Searcher}; +#[cfg(windows)] +use terminal_win::WindowsBuffer; use types::{FileTypeDef, Types, TypesBuilder}; use walk; @@ -442,7 +446,7 @@ impl Args { /// Create a new printer of individual search results that writes to the /// writer given. - pub fn printer(&self, wtr: W) -> Printer { + pub fn printer(&self, wtr: W) -> Printer { let mut p = Printer::new(wtr) .column(self.column) .context_separator(self.context_separator.clone()) @@ -469,8 +473,29 @@ impl Args { } /// Create a new buffer for use with searching. - pub fn outbuf(&self) -> OutBuffer { - OutBuffer::new(self.color) + #[cfg(not(windows))] + pub fn outbuf(&self) -> ColoredTerminal>> { + ColoredTerminal::new(vec![], self.color) + } + + /// Create a new buffer for use with searching. + #[cfg(windows)] + pub fn outbuf(&self) -> ColoredTerminal { + ColoredTerminal::new_buffer(self.color) + } + + /// Create a new buffer for use with searching. + #[cfg(not(windows))] + pub fn stdout( + &self, + ) -> ColoredTerminal>> { + ColoredTerminal::new(io::BufWriter::new(io::stdout()), self.color) + } + + /// Create a new buffer for use with searching. + #[cfg(windows)] + pub fn stdout(&self) -> ColoredTerminal> { + ColoredTerminal::new_stdout(self.color) } /// Return the paths that should be searched. diff --git a/src/gitignore.rs b/src/gitignore.rs index fd8fd6f20..31a16ea60 100644 --- a/src/gitignore.rs +++ b/src/gitignore.rs @@ -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 // use this exact implementation because hgignore files are different. -use std::env; use std::error::Error as StdError; use std::fmt; use std::fs::File; @@ -89,21 +88,10 @@ pub struct 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>(path: P) -> Result { - 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 /// string containing the contents of a gitignore file. - pub fn from_str>( + #[allow(dead_code)] + fn from_str>( root: P, gitignore: &str, ) -> Result { @@ -159,11 +147,6 @@ impl Gitignore { pub fn num_ignores(&self) -> u64 { 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. @@ -182,6 +165,7 @@ pub enum Match<'a> { impl<'a> Match<'a> { /// Returns true if the match result implies the path should be ignored. + #[allow(dead_code)] pub fn is_ignored(&self) -> bool { match *self { Match::Ignored(_) => true, diff --git a/src/glob.rs b/src/glob.rs index aaee018de..f16a75e41 100644 --- a/src/glob.rs +++ b/src/glob.rs @@ -95,6 +95,7 @@ impl Set { } /// Returns the number of glob patterns in this set. + #[allow(dead_code)] pub fn len(&self) -> usize { self.set.len() } @@ -137,6 +138,7 @@ impl SetBuilder { /// /// If the pattern could not be parsed as a glob, then an error is /// returned. + #[allow(dead_code)] pub fn add(&mut self, pat: &str) -> Result<(), Error> { 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 /// regular expression and will represent the matching semantics of this /// glob pattern. This uses a default set of options. + #[allow(dead_code)] pub fn to_regex(&self) -> String { self.to_regex_with(&MatchOptions::default()) } @@ -315,7 +318,7 @@ impl<'a> Parser<'a> { } return Ok(()); } - let last = self.p.tokens.pop().unwrap(); + self.p.tokens.pop().unwrap(); if prev != Some('/') { return Err(Error::InvalidRecursive); } diff --git a/src/main.rs b/src/main.rs index 97cfc8886..a8c82d049 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![allow(dead_code, unused_variables)] - extern crate crossbeam; extern crate docopt; extern crate env_logger; @@ -25,7 +23,7 @@ extern crate winapi; use std::error::Error; use std::fs::File; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::process; use std::result; use std::sync::{Arc, Mutex}; @@ -38,9 +36,11 @@ use term::Terminal; use walkdir::DirEntry; use args::Args; -use out::{NoColorTerminal, Out, OutBuffer}; +use out::{ColoredTerminal, Out}; use printer::Printer; use search_stream::InputBuffer; +#[cfg(windows)] +use terminal_win::WindowsBuffer; macro_rules! errored { ($($tt:tt)*) => { @@ -64,7 +64,8 @@ mod out; mod printer; mod search_buffer; mod search_stream; -mod terminal; +#[cfg(windows)] +mod terminal_win; mod types; mod walk; @@ -73,7 +74,7 @@ pub type Result = result::Result>; fn main() { match Args::parse().and_then(run) { Ok(count) if count == 0 => process::exit(1), - Ok(count) => process::exit(0), + Ok(_) => process::exit(0), Err(err) => { eprintln!("{}", err); process::exit(1); @@ -82,34 +83,40 @@ fn main() { } fn run(args: Args) -> Result { + let args = Arc::new(args); + let paths = args.paths(); if args.files() { - return run_files(args); + return run_files(args.clone()); } 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 outbuf = args.outbuf(); let mut workers = vec![]; let mut workq = { let (workq, stealer) = chase_lev::deque(); for _ in 0..args.threads() { - let worker = Worker { - args: args.clone(), - out: out.clone(), + let worker = MultiWorker { chan_work: stealer.clone(), - inpbuf: args.input_buffer(), - outbuf: Some(outbuf.clone()), - grep: args.grep(), - match_count: 0, + out: out.clone(), + outbuf: Some(args.outbuf()), + worker: Worker { + args: args.clone(), + inpbuf: args.input_buffer(), + grep: args.grep(), + match_count: 0, + }, }; workers.push(thread::spawn(move || worker.run())); } workq }; - for p in args.paths() { + for p in paths { if p == Path::new("-") { workq.push(Work::Stdin) } else { @@ -128,8 +135,27 @@ fn run(args: Args) -> Result { Ok(match_count) } -fn run_files(args: Args) -> Result { - let term = NoColorTerminal::new(io::BufWriter::new(io::stdout())); +fn run_one(args: Arc, path: &Path) -> Result { + 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) -> Result { + let term = args.stdout(); let mut printer = args.printer(term); let mut file_count = 0; for p in args.paths() { @@ -146,8 +172,8 @@ fn run_files(args: Args) -> Result { Ok(file_count) } -fn run_types(args: Args) -> Result { - let term = NoColorTerminal::new(io::BufWriter::new(io::stdout())); +fn run_types(args: Arc) -> Result { + let term = args.stdout(); let mut printer = args.printer(term); let mut ty_count = 0; for def in args.type_defs() { @@ -165,22 +191,29 @@ enum Work { enum WorkReady { Stdin, - File(DirEntry, File), + DirFile(DirEntry, File), + PathFile(PathBuf, File), +} + +struct MultiWorker { + chan_work: Stealer, + out: Arc>, + #[cfg(not(windows))] + outbuf: Option>>>, + #[cfg(windows)] + outbuf: Option>, + worker: Worker, } struct Worker { args: Arc, - out: Arc>, - chan_work: Stealer, inpbuf: InputBuffer, - outbuf: Option, grep: Grep, match_count: u64, } -impl Worker { +impl MultiWorker { fn run(mut self) -> u64 { - self.match_count = 0; loop { let work = match self.chan_work.steal() { Steal::Empty | Steal::Abort => continue, @@ -188,7 +221,7 @@ impl Worker { Steal::Data(Work::Stdin) => WorkReady::Stdin, Steal::Data(Work::File(ent)) => { match File::open(ent.path()) { - Ok(file) => WorkReady::File(ent, file), + Ok(file) => WorkReady::DirFile(ent, file), Err(err) => { eprintln!("{}: {}", ent.path().display(), err); continue; @@ -198,8 +231,8 @@ impl Worker { }; let mut outbuf = self.outbuf.take().unwrap(); outbuf.clear(); - let mut printer = self.args.printer(outbuf); - self.do_work(&mut printer, work); + let mut printer = self.worker.args.printer(outbuf); + self.worker.do_work(&mut printer, work); let outbuf = printer.into_inner(); if !outbuf.get_ref().is_empty() { let mut out = self.out.lock().unwrap(); @@ -207,10 +240,12 @@ impl Worker { } self.outbuf = Some(outbuf); } - self.match_count + self.worker.match_count } +} - fn do_work( +impl Worker { + fn do_work( &mut self, printer: &mut Printer, work: WorkReady, @@ -221,7 +256,7 @@ impl Worker { let stdin = stdin.lock(); self.search(printer, &Path::new(""), stdin) } - WorkReady::File(ent, file) => { + WorkReady::DirFile(ent, file) => { let mut path = ent.path(); if let Ok(p) = path.strip_prefix("./") { path = p; @@ -232,6 +267,17 @@ impl Worker { 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 { Ok(count) => { @@ -243,7 +289,7 @@ impl Worker { } } - fn search( + fn search( &mut self, printer: &mut Printer, path: &Path, @@ -258,7 +304,7 @@ impl Worker { ).run().map_err(From::from) } - fn search_mmap( + fn search_mmap( &mut self, printer: &mut Printer, path: &Path, diff --git a/src/out.rs b/src/out.rs index 8a9d6abbd..384e227c1 100644 --- a/src/out.rs +++ b/src/out.rs @@ -1,40 +1,12 @@ use std::io::{self, Write}; -use std::sync::Arc; use term::{self, Terminal}; -use term::color::Color; use term::terminfo::TermInfo; #[cfg(windows)] use term::WinConsole; -use terminal::TerminfoTerminal; - -pub type StdoutTerminal = Box + Send>; - -/// Gets a terminal that supports color if available. #[cfg(windows)] -fn term_stdout(color: bool) -> StdoutTerminal { - 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)) - } -} +use terminal_win::WindowsBuffer; /// Out controls the actual output of all search results for a particular file /// 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 /// file as a whole. For example, it knows when to print a file separator.) pub struct Out { - term: StdoutTerminal, + #[cfg(not(windows))] + term: ColoredTerminal>>, + #[cfg(windows)] + term: ColoredTerminal>, printed: bool, file_separator: Option>, } 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: term_stdout(color), + term: ColoredTerminal::new_stdout(color), printed: false, file_separator: None, } @@ -69,154 +56,194 @@ impl Out { /// Write the search results of a single file to the underlying wtr and /// flush wtr. - pub fn write(&mut self, buf: &OutBuffer) { - if let Some(ref sep) = self.file_separator { - if self.printed { - let _ = self.term.write_all(sep); - let _ = self.term.write_all(b"\n"); - } - } + #[cfg(not(windows))] + pub fn write( + &mut self, + buf: &ColoredTerminal>>, + ) { + self.write_sep(); match *buf { - OutBuffer::Colored(ref tt) => { + ColoredTerminal::Colored(ref tt) => { let _ = self.term.write_all(tt.get_ref()); } - OutBuffer::Windows(ref w) => { - w.print_stdout(&mut self.term); + ColoredTerminal::NoColor(ref buf) => { + let _ = self.term.write_all(buf); } - OutBuffer::NoColor(ref 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, + ) { + 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); } } - let _ = self.term.flush(); - self.printed = true; + self.write_done(); } -} -/// OutBuffer corresponds to the final output buffer for search results. All -/// search results are written to a buffer and then a buffer is flushed to -/// stdout only after the full search has completed. -#[derive(Clone, Debug)] -pub enum OutBuffer { - Colored(TerminfoTerminal>), - Windows(WindowsBuffer), - NoColor(Vec), -} - -#[derive(Clone, Debug)] -pub struct WindowsBuffer { - buf: Vec, - pos: usize, - colors: Vec, -} + 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"); + } + } + } -#[derive(Clone, Debug)] -pub struct WindowsColor { - pos: usize, - opt: WindowsOption, + 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 WindowsOption { - Foreground(Color), - Background(Color), - Reset, -} - -lazy_static! { - static ref TERMINFO: Option> = { - match TermInfo::from_env() { - Ok(info) => Some(Arc::new(info)), - Err(err) => { - debug!("error loading terminfo for coloring: {}", err); - None - } - } - }; +pub enum ColoredTerminal { + Colored(T), + NoColor(T::Output), } -impl OutBuffer { +#[cfg(not(windows))] +impl ColoredTerminal> { /// Create a new output buffer. /// /// When color is true, the buffer will attempt to support coloring. - pub fn new(color: bool) -> OutBuffer { - // If we want color, build a 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 OutBuffer::NoColor(vec![]); - } - if cfg!(windows) { - return OutBuffer::Windows(WindowsBuffer { - buf: vec![], - pos: 0, - colors: vec![] - }); + pub fn new(wtr: W, color: bool) -> Self { + lazy_static! { + // Only pay for parsing the terminfo once. + static ref TERMINFO: Option = { + match TermInfo::from_env() { + Ok(info) => Some(info), + Err(err) => { + debug!("error loading terminfo for coloring: {}", err); + None + } + } + }; } - if TERMINFO.is_none() { - return OutBuffer::NoColor(vec![]); + // 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 info = TERMINFO.clone().unwrap(); - let tt = TerminfoTerminal::new_with_terminfo(vec![], info); + 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 OutBuffer::NoColor(tt.into_inner()); + return ColoredTerminal::NoColor(tt.into_inner()); } - OutBuffer::Colored(tt) + ColoredTerminal::Colored(tt) } +} +#[cfg(not(windows))] +impl ColoredTerminal>> { /// Clear the give buffer of all search results such that it is reusable /// in another search. pub fn clear(&mut self) { match *self { - OutBuffer::Colored(ref mut tt) => { + ColoredTerminal::Colored(ref mut tt) => { tt.get_mut().clear(); } - OutBuffer::Windows(ref mut win) => { - win.buf.clear(); - win.colors.clear(); - win.pos = 0; - } - OutBuffer::NoColor(ref mut buf) => { + ColoredTerminal::NoColor(ref mut buf) => { buf.clear(); } } } +} + +#[cfg(windows)] +impl ColoredTerminal { + /// 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()) + } + } - fn map_result( + /// 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> { + /// 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 ColoredTerminal { + fn map_result( &mut self, mut f: F, - mut g: G, ) -> term::Result<()> - where F: FnMut(&mut TerminfoTerminal>) -> term::Result<()>, - G: FnMut(&mut WindowsBuffer) -> term::Result<()> { + where F: FnMut(&mut T) -> term::Result<()> { match *self { - OutBuffer::Colored(ref mut w) => f(w), - OutBuffer::Windows(ref mut w) => g(w), - OutBuffer::NoColor(_) => Err(term::Error::NotSupported), + ColoredTerminal::Colored(ref mut w) => f(w), + ColoredTerminal::NoColor(_) => Err(term::Error::NotSupported), } } - fn map_bool( + fn map_bool( &self, mut f: F, - mut g: G, ) -> bool - where F: FnMut(&TerminfoTerminal>) -> bool, - G: FnMut(&WindowsBuffer) -> bool { + where F: FnMut(&T) -> bool { match *self { - OutBuffer::Colored(ref w) => f(w), - OutBuffer::Windows(ref w) => g(w), - OutBuffer::NoColor(_) => false, + ColoredTerminal::Colored(ref w) => f(w), + ColoredTerminal::NoColor(_) => false, } } } -impl io::Write for OutBuffer { +impl io::Write for ColoredTerminal { fn write(&mut self, buf: &[u8]) -> io::Result { match *self { - OutBuffer::Colored(ref mut w) => w.write(buf), - OutBuffer::Windows(ref mut w) => w.write(buf), - OutBuffer::NoColor(ref mut w) => w.write(buf), + ColoredTerminal::Colored(ref mut w) => w.write(buf), + ColoredTerminal::NoColor(ref mut w) => w.write(buf), } } @@ -225,259 +252,67 @@ impl io::Write for OutBuffer { } } -impl term::Terminal for OutBuffer { - type Output = Vec; +impl term::Terminal for ColoredTerminal { + type Output = T::Output; 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<()> { - 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<()> { - 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 { - 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<()> { - self.map_result(|w| w.reset(), |w| w.reset()) + self.map_result(|w| w.reset()) } 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 { - 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<()> { - 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<()> { - 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<()> { - self.map_result(|w| w.carriage_return(), |w| w.carriage_return()) + self.map_result(|w| w.carriage_return()) } - fn get_ref(&self) -> &Vec { + fn get_ref(&self) -> &Self::Output { match *self { - OutBuffer::Colored(ref w) => w.get_ref(), - OutBuffer::Windows(ref w) => w.get_ref(), - OutBuffer::NoColor(ref w) => w, + ColoredTerminal::Colored(ref w) => w.get_ref(), + ColoredTerminal::NoColor(ref w) => w, } } - fn get_mut(&mut self) -> &mut Vec { + fn get_mut(&mut self) -> &mut Self::Output { match *self { - OutBuffer::Colored(ref mut w) => w.get_mut(), - OutBuffer::Windows(ref mut w) => w.get_mut(), - OutBuffer::NoColor(ref mut w) => w, + ColoredTerminal::Colored(ref mut w) => w.get_mut(), + ColoredTerminal::NoColor(ref mut w) => w, } } - fn into_inner(self) -> Vec { + fn into_inner(self) -> Self::Output { match self { - OutBuffer::Colored(w) => w.into_inner(), - OutBuffer::Windows(w) => w.into_inner(), - 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 { - 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; - - 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 { - &self.buf - } - - fn get_mut(&mut self) -> &mut Vec { - &mut self.buf - } - - fn into_inner(self) -> Vec { - 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 { - wtr: W, -} - -impl NoColorTerminal { - /// Wrap the given writer in a Terminal interface. - pub fn new(wtr: W) -> NoColorTerminal { - NoColorTerminal { - wtr: wtr, + ColoredTerminal::Colored(w) => w.into_inner(), + ColoredTerminal::NoColor(w) => w, } } } - -impl io::Write for NoColorTerminal { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.wtr.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.wtr.flush() - } -} - -impl term::Terminal for NoColorTerminal { - 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 - } -} diff --git a/src/printer.rs b/src/printer.rs index b5070b88f..afc0de4f0 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -36,7 +36,7 @@ pub struct Printer { with_filename: bool, } -impl Printer { +impl Printer { /// Create a new printer that writes to wtr. pub fn new(wtr: W) -> Printer { Printer { diff --git a/src/search_buffer.rs b/src/search_buffer.rs index d91c1a82b..fb16bf0b5 100644 --- a/src/search_buffer.rs +++ b/src/search_buffer.rs @@ -151,10 +151,10 @@ impl<'a, W: Send + Terminal> BufferSearcher<'a, W> { mod tests { use std::path::Path; - use grep::{Grep, GrepBuilder}; - use term::Terminal; + use grep::GrepBuilder; + use term::{Terminal, TerminfoTerminal}; - use out::OutBuffer; + use out::ColoredTerminal; use printer::Printer; 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.\ "; - 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 { &Path::new("/baz.rs") } - type TestSearcher<'a> = BufferSearcher<'a, OutBuffer>; + type TestSearcher<'a> = + BufferSearcher<'a, ColoredTerminal>>>; fn search TestSearcher>( pat: &str, haystack: &str, mut map: F, ) -> (u64, String) { - let outbuf = OutBuffer::NoColor(vec![]); + let outbuf = ColoredTerminal::NoColor(vec![]); let mut pp = Printer::new(outbuf).with_filename(true); let grep = GrepBuilder::new(pat).build().unwrap(); let count = { diff --git a/src/search_stream.rs b/src/search_stream.rs index ec8453de1..fbae781c1 100644 --- a/src/search_stream.rs +++ b/src/search_stream.rs @@ -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. /// /// `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::path::Path; - use grep::{Grep, GrepBuilder}; - use term::Terminal; + use grep::GrepBuilder; + use term::{Terminal, TerminfoTerminal}; - use out::OutBuffer; + use out::ColoredTerminal; use printer::Printer; use super::{InputBuffer, Searcher, start_of_previous_lines}; @@ -800,15 +800,15 @@ fn main() { io::Cursor::new(s.to_string().into_bytes()) } - fn matcher(pat: &str) -> Grep { - GrepBuilder::new(pat).build().unwrap() - } - fn test_path() -> &'static Path { &Path::new("/baz.rs") } - type TestSearcher<'a> = Searcher<'a, io::Cursor>, OutBuffer>; + type TestSearcher<'a> = Searcher< + 'a, + io::Cursor>, + ColoredTerminal>>, + >; fn search_smallcap TestSearcher>( pat: &str, @@ -816,7 +816,7 @@ fn main() { mut map: F, ) -> (u64, String) { 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 grep = GrepBuilder::new(pat).build().unwrap(); let count = { @@ -833,7 +833,7 @@ fn main() { mut map: F, ) -> (u64, String) { 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 grep = GrepBuilder::new(pat).build().unwrap(); let count = { diff --git a/src/terminal.rs b/src/terminal.rs index 50461e740..e69de29bb 100644 --- a/src/terminal.rs +++ b/src/terminal.rs @@ -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 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 { - num_colors: u16, - out: T, - ti: Arc, -} - -impl Terminal for TerminfoTerminal { - 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 TerminfoTerminal { - /// Create a new TerminfoTerminal with the given TermInfo and Write. - pub fn new_with_terminfo(out: T, terminfo: Arc) -> TerminfoTerminal { - 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> { - 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 Write for TerminfoTerminal { - fn write(&mut self, buf: &[u8]) -> io::Result { - 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(()) -} diff --git a/src/terminal_win.rs b/src/terminal_win.rs new file mode 100644 index 000000000..89f1e7342 --- /dev/null +++ b/src/terminal_win.rs @@ -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, + pos: usize, + colors: Vec, +} + +/// 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(&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 { + 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; + + 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 { + &self.buf + } + + fn get_mut(&mut self) -> &mut Vec { + &mut self.buf + } + + fn into_inner(self) -> Vec { + self.buf + } +} diff --git a/src/types.rs b/src/types.rs index f33154919..6b9aea91c 100644 --- a/src/types.rs +++ b/src/types.rs @@ -216,7 +216,7 @@ impl Types { if self.negated.as_ref().map(|s| s.is_match(&*name)).unwrap_or(false) { 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); } if self.has_selected { diff --git a/src/walk.rs b/src/walk.rs index e60e26055..b100802a4 100644 --- a/src/walk.rs +++ b/src/walk.rs @@ -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) -}