diff --git a/src/args.rs b/src/args.rs index 7b363d00b..d6a9657f0 100644 --- a/src/args.rs +++ b/src/args.rs @@ -9,11 +9,12 @@ use grep::{Grep, GrepBuilder}; use log; use num_cpus; use regex; +use term::Terminal; use walkdir::WalkDir; use gitignore::{Gitignore, GitignoreBuilder}; use ignore::Ignore; -use out::Out; +use out::{Out, OutBuffer}; use printer::Printer; use search::{InputBuffer, Searcher}; use search_buffer::BufferSearcher; @@ -438,8 +439,8 @@ impl Args { /// Create a new printer of individual search results that writes to the /// writer given. - pub fn printer(&self, wtr: W) -> Printer { - let mut p = Printer::new(wtr, self.color) + pub fn printer(&self, wtr: W) -> Printer { + let mut p = Printer::new(wtr) .column(self.column) .context_separator(self.context_separator.clone()) .eol(self.eol) @@ -454,8 +455,8 @@ impl Args { /// Create a new printer of search results for an entire file that writes /// to the writer given. - pub fn out(&self, wtr: W) -> Out { - let mut out = Out::new(wtr); + pub fn out(&self) -> Out { + let mut out = Out::new(self.color); if self.heading && !self.count { out = out.file_separator(b"".to_vec()); } else if self.before_context > 0 || self.after_context > 0 { @@ -464,6 +465,11 @@ impl Args { out } + /// Create a new buffer for use with searching. + pub fn outbuf(&self) -> OutBuffer { + OutBuffer::new(self.color) + } + /// Return the paths that should be searched. pub fn paths(&self) -> &[PathBuf] { &self.paths @@ -472,7 +478,7 @@ impl Args { /// Create a new line based searcher whose configuration is taken from the /// command line. This searcher supports a dizzying array of features: /// inverted matching, line counting, context control and more. - pub fn searcher<'a, R: io::Read, W: Send + io::Write>( + pub fn searcher<'a, R: io::Read, W: Send + Terminal>( &self, inp: &'a mut InputBuffer, printer: &'a mut Printer, @@ -493,7 +499,7 @@ impl Args { /// Create a new line based searcher whose configuration is taken from the /// command line. This search operates on an entire file all once (which /// may have been memory mapped). - pub fn searcher_buffer<'a, W: Send + io::Write>( + pub fn searcher_buffer<'a, W: Send + Terminal>( &self, printer: &'a mut Printer, grep: &'a Grep, diff --git a/src/main.rs b/src/main.rs index 722232a8a..493256f07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,7 +39,7 @@ use term::Terminal; use walkdir::DirEntry; use args::Args; -use out::Out; +use out::{NoColorTerminal, Out, OutBuffer}; use printer::Printer; use search::InputBuffer; @@ -90,7 +90,8 @@ fn run(args: Args) -> Result { return run_types(args); } let args = Arc::new(args); - let out = Arc::new(Mutex::new(args.out(io::stdout()))); + let out = Arc::new(Mutex::new(args.out())); + let outbuf = args.outbuf(); let mut workers = vec![]; let mut workq = { @@ -101,7 +102,7 @@ fn run(args: Args) -> Result { out: out.clone(), chan_work: stealer.clone(), inpbuf: args.input_buffer(), - outbuf: Some(vec![]), + outbuf: Some(outbuf.clone()), grep: args.grep(), match_count: 0, }; @@ -129,7 +130,8 @@ fn run(args: Args) -> Result { } fn run_files(args: Args) -> Result { - let mut printer = args.printer(io::BufWriter::new(io::stdout())); + let term = NoColorTerminal::new(io::BufWriter::new(io::stdout())); + let mut printer = args.printer(term); let mut file_count = 0; for p in args.paths() { if p == Path::new("-") { @@ -146,7 +148,8 @@ fn run_files(args: Args) -> Result { } fn run_types(args: Args) -> Result { - let mut printer = args.printer(io::BufWriter::new(io::stdout())); + let term = NoColorTerminal::new(io::BufWriter::new(io::stdout())); + let mut printer = args.printer(term); let mut ty_count = 0; for def in args.type_defs() { printer.type_def(def); @@ -168,10 +171,10 @@ enum WorkReady { struct Worker { args: Arc, - out: Arc>>, + out: Arc>, chan_work: Stealer, inpbuf: InputBuffer, - outbuf: Option>, + outbuf: Option, grep: Grep, match_count: u64, } @@ -203,12 +206,12 @@ impl Worker { let mut out = self.out.lock().unwrap(); out.write(&outbuf); } - self.outbuf = Some(outbuf.into_inner()); + self.outbuf = Some(outbuf); } self.match_count } - fn do_work( + fn do_work( &mut self, printer: &mut Printer, work: WorkReady, @@ -241,7 +244,7 @@ impl Worker { } } - fn search( + fn search( &mut self, printer: &mut Printer, path: &Path, @@ -256,7 +259,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 9fb31a093..149991278 100644 --- a/src/out.rs +++ b/src/out.rs @@ -1,10 +1,40 @@ use std::io::{self, Write}; +use std::sync::Arc; -use term::{StdoutTerminal, Terminal}; +use term::{self, Terminal}; +use term::color::Color; +use term::terminfo::TermInfo; #[cfg(windows)] use term::WinConsole; -use printer::Writer; +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::BufWriter::new(io::stdout()); + WinConsole::new(stdout) + .ok() + .map(|t| Box::new(t)) + .unwrap_or_else(|| { + let stdout = io::BufWriter::new(io::stdout()); + Box::new(NoColorTerminal::new(stdout)) + }) +} + +/// Gets a terminal that supports color if available. +#[cfg(not(windows))] +fn term_stdout(color: bool) -> StdoutTerminal { + let stdout = io::BufWriter::new(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 /// to the end user. @@ -12,34 +42,17 @@ use printer::Writer; /// (The difference between Out and Printer is that a Printer works with /// individual search results where as Out works with search results for each /// file as a whole. For example, it knows when to print a file separator.) -pub struct Out { - wtr: io::BufWriter, - term: Option>, +pub struct Out { + term: StdoutTerminal, printed: bool, file_separator: Option>, } -/// This is like term::stdout, but on Windows always uses WinConsole instead -/// of trying for a TerminfoTerminal. This may be a mistake. -#[cfg(windows)] -fn term_stdout() -> Option> { - WinConsole::new(io::stdout()) - .ok() - .map(|t| Box::new(t) as Box) -} - -#[cfg(not(windows))] -fn term_stdout() -> Option> { - // We never use this crap on *nix. - None -} - -impl Out { +impl Out { /// Create a new Out that writes to the wtr given. - pub fn new(wtr: W) -> Out { + pub fn new(color: bool) -> Out { Out { - wtr: io::BufWriter::new(wtr), - term: term_stdout(), + term: term_stdout(color), printed: false, file_separator: None, } @@ -49,39 +62,422 @@ impl Out { /// By default, no separator is printed. /// /// If sep is empty, then no file separator is printed. - pub fn file_separator(mut self, sep: Vec) -> Out { + pub fn file_separator(mut self, sep: Vec) -> Out { self.file_separator = Some(sep); self } /// Write the search results of a single file to the underlying wtr and /// flush wtr. - pub fn write(&mut self, buf: &Writer>) { + pub fn write(&mut self, buf: &OutBuffer) { if let Some(ref sep) = self.file_separator { if self.printed { - let _ = self.wtr.write_all(sep); - let _ = self.wtr.write_all(b"\n"); + let _ = self.term.write_all(sep); + let _ = self.term.write_all(b"\n"); } } match *buf { - Writer::Colored(ref tt) => { - let _ = self.wtr.write_all(tt.get_ref()); + OutBuffer::Colored(ref tt) => { + let _ = self.term.write_all(tt.get_ref()); } - Writer::Windows(ref w) => { - match self.term { - None => { - let _ = self.wtr.write_all(w.get_ref()); - } - Some(ref mut stdout) => { - w.print_stdout(stdout); - } - } + OutBuffer::Windows(ref w) => { + w.print_stdout(&mut self.term); } - Writer::NoColor(ref buf) => { - let _ = self.wtr.write_all(buf); + OutBuffer::NoColor(ref buf) => { + let _ = self.term.write_all(buf); } } - let _ = self.wtr.flush(); + let _ = self.term.flush(); self.printed = true; } } + +/// 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, +} + +#[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> = { + 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. + /// + /// 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![] + }); + } + if TERMINFO.is_none() { + return OutBuffer::NoColor(vec![]); + } + let info = TERMINFO.clone().unwrap(); + let tt = TerminfoTerminal::new_with_terminfo(vec![], info); + if !tt.supports_color() { + debug!("environment doesn't support coloring"); + return OutBuffer::NoColor(tt.into_inner()); + } + OutBuffer::Colored(tt) + } + + /// 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) => { + tt.get_mut().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( + &mut self, + mut f: F, + mut g: G, + ) -> term::Result<()> + where F: FnMut(&mut TerminfoTerminal>) -> term::Result<()>, + G: FnMut(&mut WindowsBuffer) -> term::Result<()> { + match *self { + OutBuffer::Colored(ref mut w) => f(w), + OutBuffer::Windows(ref mut w) => g(w), + OutBuffer::NoColor(_) => Err(term::Error::NotSupported), + } + } + + fn map_bool( + &self, + mut f: F, + mut g: G, + ) -> bool + where F: FnMut(&TerminfoTerminal>) -> bool, + G: FnMut(&WindowsBuffer) -> bool { + match *self { + OutBuffer::Colored(ref w) => f(w), + OutBuffer::Windows(ref w) => g(w), + OutBuffer::NoColor(_) => false, + } + } +} + +impl io::Write for OutBuffer { + 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), + } + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl term::Terminal for OutBuffer { + type Output = Vec; + + fn fg(&mut self, fg: term::color::Color) -> term::Result<()> { + self.map_result(|w| w.fg(fg), |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)) + } + + fn attr(&mut self, attr: term::Attr) -> term::Result<()> { + self.map_result(|w| w.attr(attr), |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)) + } + + fn reset(&mut self) -> term::Result<()> { + self.map_result(|w| w.reset(), |w| w.reset()) + } + + fn supports_reset(&self) -> bool { + self.map_bool(|w| w.supports_reset(), |w| w.supports_reset()) + } + + fn supports_color(&self) -> bool { + self.map_bool(|w| w.supports_color(), |w| w.supports_color()) + } + + fn cursor_up(&mut self) -> term::Result<()> { + self.map_result(|w| w.cursor_up(), |w| w.cursor_up()) + } + + fn delete_line(&mut self) -> term::Result<()> { + self.map_result(|w| w.delete_line(), |w| w.delete_line()) + } + + fn carriage_return(&mut self) -> term::Result<()> { + self.map_result(|w| w.carriage_return(), |w| w.carriage_return()) + } + + fn get_ref(&self) -> &Vec { + match *self { + OutBuffer::Colored(ref w) => w.get_ref(), + OutBuffer::Windows(ref w) => w.get_ref(), + OutBuffer::NoColor(ref w) => w, + } + } + + fn get_mut(&mut self) -> &mut Vec { + match *self { + OutBuffer::Colored(ref mut w) => w.get_mut(), + OutBuffer::Windows(ref mut w) => w.get_mut(), + OutBuffer::NoColor(ref mut w) => w, + } + } + + fn into_inner(self) -> Vec { + 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, + } + } +} + +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 b82e0fece..b5070b88f 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,17 +1,11 @@ -use std::io::{self, Write}; use std::path::Path; -use std::sync::Arc; use regex::bytes::Regex; -use term::{self, StdoutTerminal, Terminal}; -use term::color::*; -use term::terminfo::TermInfo; +use term::{Attr, Terminal}; +use term::color; -use terminal::TerminfoTerminal; use types::FileTypeDef; -use self::Writer::*; - /// Printer encapsulates all output logic for searching. /// /// Note that we currently ignore all write errors. It's probably worthwhile @@ -19,7 +13,7 @@ use self::Writer::*; /// writes to memory, neither of which commonly fail. pub struct Printer { /// The underlying writer. - wtr: Writer, + wtr: W, /// Whether anything has been printed to wtr yet. has_printed: bool, /// Whether to show column numbers for the first match or not. @@ -42,13 +36,11 @@ pub struct Printer { with_filename: bool, } -impl Printer { +impl Printer { /// Create a new printer that writes to wtr. - /// - /// `color` should be true if the printer should try to use coloring. - pub fn new(wtr: W, color: bool) -> Printer { + pub fn new(wtr: W) -> Printer { Printer { - wtr: Writer::new(wtr, color), + wtr: wtr, has_printed: false, column: false, context_separator: "--".to_string().into_bytes(), @@ -115,7 +107,7 @@ impl Printer { } /// Flushes the underlying writer and returns it. - pub fn into_inner(mut self) -> Writer { + pub fn into_inner(mut self) -> W { let _ = self.wtr.flush(); self.wtr } @@ -201,15 +193,15 @@ impl Printer { } pub fn write_match(&mut self, re: &Regex, buf: &[u8]) { - if !self.wtr.is_color() { + if !self.wtr.supports_color() { self.write(buf); return; } let mut last_written = 0; for (s, e) in re.find_iter(buf) { self.write(&buf[last_written..s]); - let _ = self.wtr.fg(BRIGHT_RED); - let _ = self.wtr.attr(term::Attr::Bold); + let _ = self.wtr.fg(color::BRIGHT_RED); + let _ = self.wtr.attr(Attr::Bold); self.write(&buf[s..e]); let _ = self.wtr.reset(); last_written = e; @@ -241,24 +233,24 @@ impl Printer { } fn write_heading>(&mut self, path: P) { - if self.wtr.is_color() { - let _ = self.wtr.fg(BRIGHT_GREEN); - let _ = self.wtr.attr(term::Attr::Bold); + if self.wtr.supports_color() { + let _ = self.wtr.fg(color::BRIGHT_GREEN); + let _ = self.wtr.attr(Attr::Bold); } self.write(path.as_ref().to_string_lossy().as_bytes()); self.write_eol(); - if self.wtr.is_color() { + if self.wtr.supports_color() { let _ = self.wtr.reset(); } } fn line_number(&mut self, n: u64, sep: u8) { - if self.wtr.is_color() { - let _ = self.wtr.fg(BRIGHT_BLUE); - let _ = self.wtr.attr(term::Attr::Bold); + if self.wtr.supports_color() { + let _ = self.wtr.fg(color::BRIGHT_BLUE); + let _ = self.wtr.attr(Attr::Bold); } self.write(n.to_string().as_bytes()); - if self.wtr.is_color() { + if self.wtr.supports_color() { let _ = self.wtr.reset(); } self.write(&[sep]); @@ -277,289 +269,3 @@ impl Printer { self.write(&[eol]); } } - -/// Writer corresponds to the final output buffer for search results. All -/// search results are written to a Writer and then a Writer is flushed to -/// stdout only after the full search has completed. -pub enum Writer { - Colored(TerminfoTerminal), - Windows(WindowsWriter), - NoColor(W), -} - -pub struct WindowsWriter { - wtr: W, - pos: usize, - colors: Vec, -} - -pub struct WindowsColor { - pos: usize, - opt: WindowsOption, -} - -pub enum WindowsOption { - Foreground(Color), - Background(Color), - Reset, -} - -lazy_static! { - static ref TERMINFO: Option> = { - match term::terminfo::TermInfo::from_env() { - Ok(info) => Some(Arc::new(info)), - Err(err) => { - debug!("error loading terminfo for coloring: {}", err); - None - } - } - }; -} - -impl Writer { - fn new(wtr: W, color: bool) -> Writer { - // 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 NoColor(wtr); - } - if cfg!(windows) { - return Windows(WindowsWriter { wtr: wtr, pos: 0, colors: vec![] }); - } - if TERMINFO.is_none() { - return NoColor(wtr); - } - let info = TERMINFO.clone().unwrap(); - let tt = TerminfoTerminal::new_with_terminfo(wtr, info); - if !tt.supports_color() { - debug!("environment doesn't support coloring"); - return NoColor(tt.into_inner()); - } - Colored(tt) - } - - fn is_color(&self) -> bool { - match *self { - Colored(_) => true, - Windows(_) => true, - NoColor(_) => false, - } - } - - fn map_result( - &mut self, - mut f: F, - mut g: G, - ) -> term::Result<()> - where F: FnMut(&mut TerminfoTerminal) -> term::Result<()>, - G: FnMut(&mut WindowsWriter) -> term::Result<()> { - match *self { - Colored(ref mut w) => f(w), - Windows(ref mut w) => g(w), - NoColor(_) => Err(term::Error::NotSupported), - } - } - - fn map_bool( - &self, - mut f: F, - mut g: G, - ) -> bool - where F: FnMut(&TerminfoTerminal) -> bool, - G: FnMut(&WindowsWriter) -> bool { - match *self { - Colored(ref w) => f(w), - Windows(ref w) => g(w), - NoColor(_) => false, - } - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - match *self { - Colored(ref mut w) => w.write(buf), - Windows(ref mut w) => w.write(buf), - NoColor(ref mut w) => w.write(buf), - } - } - - fn flush(&mut self) -> io::Result<()> { - match *self { - Colored(ref mut w) => w.flush(), - Windows(ref mut w) => w.flush(), - NoColor(ref mut w) => w.flush(), - } - } -} - -impl term::Terminal for Writer { - type Output = W; - - fn fg(&mut self, fg: term::color::Color) -> term::Result<()> { - self.map_result(|w| w.fg(fg), |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)) - } - - fn attr(&mut self, attr: term::Attr) -> term::Result<()> { - self.map_result(|w| w.attr(attr), |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)) - } - - fn reset(&mut self) -> term::Result<()> { - self.map_result(|w| w.reset(), |w| w.reset()) - } - - fn supports_reset(&self) -> bool { - self.map_bool(|w| w.supports_reset(), |w| w.supports_reset()) - } - - fn supports_color(&self) -> bool { - self.map_bool(|w| w.supports_color(), |w| w.supports_color()) - } - - fn cursor_up(&mut self) -> term::Result<()> { - self.map_result(|w| w.cursor_up(), |w| w.cursor_up()) - } - - fn delete_line(&mut self) -> term::Result<()> { - self.map_result(|w| w.delete_line(), |w| w.delete_line()) - } - - fn carriage_return(&mut self) -> term::Result<()> { - self.map_result(|w| w.carriage_return(), |w| w.carriage_return()) - } - - fn get_ref(&self) -> &W { - match *self { - Colored(ref w) => w.get_ref(), - Windows(ref w) => w.get_ref(), - NoColor(ref w) => w, - } - } - - fn get_mut(&mut self) -> &mut W { - match *self { - Colored(ref mut w) => w.get_mut(), - Windows(ref mut w) => w.get_mut(), - NoColor(ref mut w) => w, - } - } - - fn into_inner(self) -> W { - match self { - Colored(w) => w.into_inner(), - Windows(w) => w.into_inner(), - NoColor(w) => w, - } - } -} - -impl WindowsWriter { - fn push(&mut self, opt: WindowsOption) { - let pos = self.pos; - self.colors.push(WindowsColor { pos: pos, opt: opt }); - } -} - -impl WindowsWriter> { - /// Print the contents to the given terminal. - pub fn print_stdout(&self, tt: &mut Box) { - let mut last = 0; - for col in &self.colors { - let _ = tt.write_all(&self.wtr[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.wtr[last..]); - let _ = tt.flush(); - } -} - -impl io::Write for WindowsWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let n = try!(self.wtr.write(buf)); - self.pos += n; - Ok(n) - } - - fn flush(&mut self) -> io::Result<()> { - self.wtr.flush() - } -} - -impl term::Terminal for WindowsWriter { - type Output = W; - - 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) -> &W { - &self.wtr - } - - fn get_mut(&mut self) -> &mut W { - &mut self.wtr - } - - fn into_inner(self) -> W { - self.wtr - } -} diff --git a/src/search.rs b/src/search.rs index cb8eac5e1..523d1e4d0 100644 --- a/src/search.rs +++ b/src/search.rs @@ -11,6 +11,7 @@ use std::path::{Path, PathBuf}; use grep::{Grep, Match}; use memchr::{memchr, memrchr}; +use term::Terminal; use printer::Printer; @@ -98,7 +99,7 @@ impl Default for Options { } } -impl<'a, R: io::Read, W: Send + io::Write> Searcher<'a, R, W> { +impl<'a, R: io::Read, W: Send + Terminal> Searcher<'a, R, W> { /// Create a new searcher. /// /// `inp` is a reusable input buffer that is used as scratch space by this @@ -689,6 +690,7 @@ mod tests { use grep::{Grep, GrepBuilder}; use term::Terminal; + use out::OutBuffer; use printer::Printer; use super::{InputBuffer, Searcher, start_of_previous_lines}; @@ -731,7 +733,7 @@ fn main() { &Path::new("/baz.rs") } - type TestSearcher<'a> = Searcher<'a, io::Cursor>, Vec>; + type TestSearcher<'a> = Searcher<'a, io::Cursor>, OutBuffer>; fn search_smallcap TestSearcher>( pat: &str, @@ -739,7 +741,8 @@ fn main() { mut map: F, ) -> (u64, String) { let mut inp = InputBuffer::with_capacity(1); - let mut pp = Printer::new(vec![], false).with_filename(true); + let outbuf = OutBuffer::NoColor(vec![]); + let mut pp = Printer::new(outbuf).with_filename(true); let grep = GrepBuilder::new(pat).build().unwrap(); let count = { let searcher = Searcher::new( @@ -755,7 +758,8 @@ fn main() { mut map: F, ) -> (u64, String) { let mut inp = InputBuffer::with_capacity(4096); - let mut pp = Printer::new(vec![], false).with_filename(true); + let outbuf = OutBuffer::NoColor(vec![]); + let mut pp = Printer::new(outbuf).with_filename(true); let grep = GrepBuilder::new(pat).build().unwrap(); let count = { let searcher = Searcher::new( diff --git a/src/search_buffer.rs b/src/search_buffer.rs index 24ece4474..23578de98 100644 --- a/src/search_buffer.rs +++ b/src/search_buffer.rs @@ -1,8 +1,8 @@ use std::cmp; -use std::io; use std::path::Path; use grep::Grep; +use term::Terminal; use printer::Printer; use search::{IterLines, Options, count_lines, is_binary}; @@ -18,7 +18,7 @@ pub struct BufferSearcher<'a, W: 'a> { last_line: usize, } -impl<'a, W: Send + io::Write> BufferSearcher<'a, W> { +impl<'a, W: Send + Terminal> BufferSearcher<'a, W> { pub fn new( printer: &'a mut Printer, grep: &'a Grep, @@ -146,6 +146,7 @@ mod tests { use grep::{Grep, GrepBuilder}; use term::Terminal; + use out::OutBuffer; use printer::Printer; use super::BufferSearcher; @@ -184,14 +185,15 @@ fn main() { &Path::new("/baz.rs") } - type TestSearcher<'a> = BufferSearcher<'a, Vec>; + type TestSearcher<'a> = BufferSearcher<'a, OutBuffer>; fn search TestSearcher>( pat: &str, haystack: &str, mut map: F, ) -> (u64, String) { - let mut pp = Printer::new(vec![], false).with_filename(true); + let outbuf = OutBuffer::NoColor(vec![]); + let mut pp = Printer::new(outbuf).with_filename(true); let grep = GrepBuilder::new(pat).build().unwrap(); let count = { let searcher = BufferSearcher::new(