use crate::config::OutputConfig;
use crate::search::SearchResult;
use super::OutputFormatter;
use anyhow::Result;
use std::io::Write;
use termcolor::{Ansi, Color, ColorSpec, NoColor, WriteColor};
pub struct TextFormatter {
use_color: bool,
}
impl Default for TextFormatter {
fn default() -> Self {
Self::new()
}
}
impl TextFormatter {
pub fn new() -> Self {
let use_color = atty::is(atty::Stream::Stdout);
Self { use_color }
}
}
impl OutputFormatter for TextFormatter {
fn format<W: Write>(
&self,
results: &[SearchResult],
writer: &mut W,
config: &OutputConfig,
) -> Result<()> {
if self.use_color {
let mut color_writer = Ansi::new(writer);
self.format_colored(&mut color_writer, results, config)?;
} else {
let mut nocolor_writer = NoColor::new(writer);
self.format_plain(&mut nocolor_writer, results, config)?;
}
Ok(())
}
}
impl TextFormatter {
fn format_colored<W: Write + WriteColor>(
&self,
writer: &mut W,
results: &[SearchResult],
config: &OutputConfig,
) -> Result<()> {
for result in results.iter().filter(|r| !r.matches.is_empty()) {
writer.set_color(ColorSpec::new().set_fg(Some(Color::Green)).set_bold(true))?;
writeln!(writer, "{}", result.path.display())?;
writer.reset()?;
for m in &result.matches {
writer.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?;
write!(writer, "{:>6}: ", m.line_number)?;
writer.reset()?;
let line = &m.line_text;
let range = &m.column_range;
write!(writer, "{}", &line[..range.start])?;
writer.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
write!(writer, "{}", &line[range.start..range.end])?;
writer.reset()?;
writeln!(writer, "{}", &line[range.end..].trim_end())?;
if config.show_ast_context {
if let Some(ast_context) = &m.ast_context {
writer.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
writeln!(
writer,
" └─ AST Context: {}",
ast_context.summary
)?;
writer.reset()?;
}
}
}
writeln!(writer)?;
}
writer.reset()?;
Ok(())
}
fn format_plain<W: Write>(
&self,
writer: &mut W,
results: &[SearchResult],
config: &OutputConfig,
) -> Result<()> {
for result in results.iter().filter(|r| !r.matches.is_empty()) {
writeln!(writer, "{}", result.path.display())?;
for m in &result.matches {
writeln!(writer, "{}:{}", m.line_number, m.line_text.trim_end())?;
if config.show_ast_context {
if let Some(ast_context) = &m.ast_context {
writeln!(
writer,
" └─ AST Context: {}",
ast_context.summary
)?;
}
}
}
writeln!(writer)?;
}
Ok(())
}
}