diff --git a/Cargo.lock b/Cargo.lock index 222f6bc..cacba2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,7 @@ dependencies = [ "ignore", "serde", "serde_json", + "termcolor", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 04ee773..6559b8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ grep = "0.3.1" ignore = "0.4.22" serde = { version = "1.0.204", features = ["derive"] } serde_json = "1.0.120" +termcolor = "1.4.1" [[bin]] name = "gg" diff --git a/README.md b/README.md index 6580da8..7db2eca 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,8 @@ Options: -U, --multiline enable multiline matching --json output in JSON format -f, --file-paths-only output file paths only + -A, --absolute-paths output absolute paths (defaults to relative) + -C, --colored-output toggle colored output (defaults to ON) -h, --help Print help -V, --version Print version diff --git a/src/cli.rs b/src/cli.rs index 36a41d2..3b6b39f 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -40,6 +40,14 @@ pub struct Cli { /// output file paths only #[clap(short = 'f', long, default_value_t = false)] pub file_paths_only: bool, + + /// output absolute paths (defaults to relative) + #[clap(short = 'A', long, default_value_t = false)] + pub absolute_paths: bool, + + /// toggle colored output (defaults to ON) + #[clap(short = 'C', long, default_value_t = true)] + pub colored_output: bool, } #[derive(Debug)] @@ -52,6 +60,8 @@ pub struct PostProcessedCli { pub respect_gitignore: bool, pub multiline: bool, pub print_mode: PrintMode, + pub absolute_paths: bool, + pub colored_output: bool, } pub fn process_cli_args(cli: Cli) -> anyhow::Result { @@ -70,5 +80,7 @@ pub fn process_cli_args(cli: Cli) -> anyhow::Result { } else { PrintMode::Text }, + absolute_paths: cli.absolute_paths, + colored_output: cli.colored_output, }) } diff --git a/src/main.rs b/src/main.rs index a81277d..16c63ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use clap::Parser; use crossbeam::queue::ArrayQueue; use ignore::DirEntry; +use printer::PrinterConfig; use crate::cli::{process_cli_args, Cli}; use crate::fs::walk_builder; @@ -49,7 +50,13 @@ pub fn main() -> anyhow::Result<()> { }) }); - let mut printer = Printer::new(cli_args.print_mode); + let printer_config = PrinterConfig { + mode: cli_args.print_mode, + absolute_paths: cli_args.absolute_paths, + colored_output: cli_args.colored_output, + ..Default::default() + }; + let mut printer = Printer::new(printer_config); queue .into_iter() .for_each(|file_results| printer.write(file_results).unwrap()); diff --git a/src/printer.rs b/src/printer.rs index f71c34d..fd68683 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -1,8 +1,13 @@ -use std::io::{self, Result, StdoutLock, Write}; +use std::{ + env::current_dir, + io::{Result, Write}, + path::{Path, PathBuf}, +}; +use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; -use crate::search::FileResults; +use crate::search::{FileResults, SearchResult}; -#[derive(Debug)] +#[derive(Debug, Eq, PartialEq)] pub enum PrintMode { Text, Json, @@ -10,33 +15,120 @@ pub enum PrintMode { } pub struct Printer { - writer: io::BufWriter>, - mode: PrintMode, + writer: BufferWriter, + buffer: Buffer, + config: PrinterConfig, + cwd: PathBuf, +} + +pub struct PrinterConfig { + pub mode: PrintMode, + // TODO: refactorize this + pub colored_output: bool, + pub color_specs: ColorSpecs, + pub absolute_paths: bool, +} + +impl Default for PrinterConfig { + fn default() -> PrinterConfig { + PrinterConfig { + mode: PrintMode::Text, + colored_output: true, + color_specs: ColorSpecs::default(), + absolute_paths: false, + } + } +} + +pub struct ColorSpecs { + paths: ColorSpec, + line_numbers: ColorSpec, + lines: ColorSpec, +} + +impl Default for ColorSpecs { + fn default() -> ColorSpecs { + let mut paths: ColorSpec = ColorSpec::new(); + paths + .set_fg(Some(Color::Green)) + .set_italic(true) + .set_bold(true) + .set_underline(true); + let mut line_numbers: ColorSpec = ColorSpec::new(); + line_numbers.set_fg(Some(Color::Yellow)).set_bold(true); + let mut lines: ColorSpec = ColorSpec::new(); + lines.set_fg(Some(Color::White)); + ColorSpecs { + paths, + line_numbers, + lines, + } + } } impl Printer { - pub fn new(mode: PrintMode) -> Printer { - let stdout = io::stdout(); - let lock = stdout.lock(); + pub fn new(config: PrinterConfig) -> Printer { + let color_choice = if config.mode == PrintMode::Text { + ColorChoice::Always + } else { + ColorChoice::Never + }; + let bufwriter = BufferWriter::stdout(color_choice); + let buffer = bufwriter.buffer(); Printer { - writer: io::BufWriter::new(lock), - mode, + writer: bufwriter, + buffer, + config, + cwd: current_dir().unwrap(), } } pub fn write(&mut self, results: FileResults) -> Result<()> { - self.writeln_to_buffer(match self.mode { - PrintMode::Text => format!("{}", results), - PrintMode::Json => serde_json::to_string(&results)?, - PrintMode::Files => format!("{}", results.path.to_string_lossy()), + let path: &Path; + if self.config.absolute_paths { + path = &results.path; + } else { + path = results.path.strip_prefix(self.cwd.clone()).unwrap(); + } + match self.config.mode { + PrintMode::Text => self.write_colored(path, results.results), + PrintMode::Json => self.writeln_to_buffer(serde_json::to_string(&results)?), + PrintMode::Files => { + self.writeln_to_buffer(format!("{}", results.path.to_string_lossy())) + } + } + } + + fn write_colored(&mut self, path: &Path, search_results: Vec) -> Result<()> { + self.write_colored_path(path)?; + self.write_colored_search_results(search_results)?; + self.write_newline_to_buffer() + } + + fn write_colored_path(&mut self, path: &Path) -> Result<()> { + self.buffer.set_color(&self.config.color_specs.paths)?; + writeln!(&mut self.buffer, "{}", path.to_string_lossy()) + } + + fn write_colored_search_results(&mut self, results: Vec) -> Result<()> { + results.iter().try_for_each(|result| { + self.buffer + .set_color(&self.config.color_specs.line_numbers)?; + write!(&mut self.buffer, "{}:\t", result.line_number)?; + self.buffer.set_color(&self.config.color_specs.lines)?; + write!(&mut self.buffer, "{}", result.line) }) } fn writeln_to_buffer(&mut self, text: String) -> Result<()> { - writeln!(self.writer, "{}", text) + writeln!(self.buffer, "{}", text) + } + + fn write_newline_to_buffer(&mut self) -> Result<()> { + writeln!(self.buffer, "") } pub fn print(&mut self) -> Result<()> { - self.writer.flush() + self.writer.print(&self.buffer) } }