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:
parent
f11d9fb922
commit
fdca74148d
35
src/args.rs
35
src/args.rs
@ -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.
|
||||||
|
@ -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,
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
118
src/main.rs
118
src/main.rs
@ -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,
|
||||||
|
523
src/out.rs
523
src/out.rs
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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 = {
|
||||||
|
@ -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 = {
|
||||||
|
202
src/terminal.rs
202
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<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
176
src/terminal_win.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -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 {
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user