use std::io::{self, Write};
use super::{FileMatches, LineKind, Searcher};
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[non_exhaustive]
pub enum Listing {
#[default]
Lines,
Count,
FilesWithMatches,
FilesWithoutMatch,
Quiet,
}
#[derive(Clone, Debug)]
pub struct GitFormat {
listing: Listing,
names: bool,
line_numbers: bool,
column: bool,
only_matching: bool,
heading: bool,
break_between: bool,
null: bool,
printed_lines: bool,
printed_file: bool,
}
impl Default for GitFormat {
fn default() -> Self {
Self::new()
}
}
impl GitFormat {
pub fn new() -> Self {
Self {
listing: Listing::Lines,
names: true,
line_numbers: false,
column: false,
only_matching: false,
heading: false,
break_between: false,
null: false,
printed_lines: false,
printed_file: false,
}
}
#[must_use]
pub fn with_listing(mut self, listing: Listing) -> Self {
self.listing = listing;
self
}
#[must_use]
pub fn with_names(mut self, names: bool) -> Self {
self.names = names;
self
}
#[must_use]
pub fn with_line_numbers(mut self, line_numbers: bool) -> Self {
self.line_numbers = line_numbers;
self
}
#[must_use]
pub fn with_column(mut self, column: bool) -> Self {
self.column = column;
self
}
#[must_use]
pub fn with_only_matching(mut self, only_matching: bool) -> Self {
self.only_matching = only_matching;
self
}
#[must_use]
pub fn with_heading(mut self, heading: bool) -> Self {
self.heading = heading;
self
}
#[must_use]
pub fn with_break(mut self, break_between: bool) -> Self {
self.break_between = break_between;
self
}
#[must_use]
pub fn with_null(mut self, null: bool) -> Self {
self.null = null;
self
}
pub fn write(
&mut self,
out: &mut impl Write,
name: &[u8],
content: &[u8],
matches: &FileMatches,
searcher: &Searcher,
) -> io::Result<()> {
let end = if self.null { b'\0' } else { b'\n' };
match self.listing {
Listing::Quiet => Ok(()),
Listing::FilesWithMatches if matches.is_match() => {
out.write_all(name)?;
out.write_all(&[end])
}
Listing::FilesWithoutMatch if !matches.is_match() => {
out.write_all(name)?;
out.write_all(&[end])
}
Listing::FilesWithMatches | Listing::FilesWithoutMatch => Ok(()),
Listing::Count if matches.is_match() => {
if self.names {
out.write_all(name)?;
out.write_all(if self.null { b"\0" } else { b":" })?;
}
writeln!(out, "{}", matches.count)
}
Listing::Count => Ok(()),
Listing::Lines if !matches.is_match() => Ok(()),
Listing::Lines if matches.binary => {
out.write_all(b"Binary file ")?;
out.write_all(name)?;
out.write_all(b" matches\n")
}
Listing::Lines => self.write_lines(out, name, content, matches, searcher),
}
}
fn write_lines(
&mut self,
out: &mut impl Write,
name: &[u8],
content: &[u8],
matches: &FileMatches,
searcher: &Searcher,
) -> io::Result<()> {
if self.break_between && self.printed_file {
out.write_all(b"\n")?;
}
self.printed_file = true;
let separators = searcher.options().has_context();
let mut previous: Option<usize> = None;
for line in &matches.lines {
if separators {
let gap = match previous {
Some(previous) => line.number != previous + 1,
None => self.printed_lines && !self.break_between,
};
if gap {
out.write_all(b"--\n")?;
}
}
if previous.is_none() && self.heading {
out.write_all(name)?;
out.write_all(b"\n")?;
}
previous = Some(line.number);
self.printed_lines = true;
let text = content.get(line.range.clone()).unwrap_or_default();
let mark = match line.kind {
LineKind::Selected => b':',
LineKind::Context => b'-',
LineKind::Function => b'=',
};
if line.kind != LineKind::Selected {
if !self.only_matching {
self.prefix(out, name, line.number, None, mark)?;
out.write_all(text)?;
out.write_all(b"\n")?;
}
continue;
}
let found = if self.only_matching || self.column {
searcher.query().matches(text)
} else {
Vec::new()
};
if self.only_matching && !searcher.options().invert() {
for span in found {
self.prefix(out, name, line.number, Some(span.start + 1), mark)?;
out.write_all(text.get(span).unwrap_or_default())?;
out.write_all(b"\n")?;
}
} else {
let column = found.first().map_or(1, |span| span.start + 1);
self.prefix(out, name, line.number, Some(column), mark)?;
out.write_all(text)?;
out.write_all(b"\n")?;
}
}
Ok(())
}
fn prefix(
&self,
out: &mut impl Write,
name: &[u8],
number: usize,
column: Option<usize>,
mark: u8,
) -> io::Result<()> {
let mark = if self.null { b'\0' } else { mark };
if self.names && !self.heading {
out.write_all(name)?;
out.write_all(&[mark])?;
}
if self.line_numbers {
write!(out, "{number}")?;
out.write_all(&[mark])?;
}
if self.column
&& let Some(column) = column
{
write!(out, "{column}")?;
out.write_all(&[mark])?;
}
Ok(())
}
}