use super::{FormatOpts, Formatter, ansi, push_lossy};
use crate::Emit;
use std::io::{self, Write};
pub struct FragmentFormatter {
pub opts: FormatOpts,
pub char_context: usize,
}
impl FragmentFormatter {
pub fn new(opts: FormatOpts, char_context: usize) -> Self {
Self { opts, char_context }
}
}
impl Formatter for FragmentFormatter {
fn write(&mut self, sink: &mut dyn Write, emit: &Emit) -> io::Result<()> {
self.opts.widen_for_line(emit.line.no);
let target_col_1 = emit
.match_info
.col
.or_else(|| emit.match_info.spans.first().map(|r| r.start + 1))
.unwrap_or(1);
let bytes = &emit.line.bytes;
let col_idx = target_col_1
.saturating_sub(1)
.min(bytes.len().saturating_sub(1));
let start = col_idx.saturating_sub(self.char_context);
let end = bytes.len().min(col_idx + self.char_context + 1);
let frag_bytes = &bytes[start..end];
let prefix = self.opts.prefix(emit.line.no);
let rendered = if let Some(span) = emit.match_info.spans.first() {
if self.opts.color {
let hs = span.start.max(start).min(end) - start;
let he = span.end.max(start).min(end) - start;
if hs < he {
let mut out = String::new();
push_lossy(&mut out, &frag_bytes[..hs]);
out.push_str(ansi::INVERSE);
push_lossy(&mut out, &frag_bytes[hs..he]);
out.push_str(ansi::RESET);
push_lossy(&mut out, &frag_bytes[he..]);
out
} else {
String::from_utf8_lossy(frag_bytes).to_string()
}
} else {
String::from_utf8_lossy(frag_bytes).to_string()
}
} else {
String::from_utf8_lossy(frag_bytes).to_string()
};
writeln!(sink, "{prefix}{rendered}")?;
let caret_offset = col_idx - start + prefix.len();
let spaces = " ".repeat(caret_offset);
let caret = ansi::paint(self.opts.color, ansi::GREEN, "^");
writeln!(sink, "{spaces}{caret}")
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Line, MatchInfo, Role};
#[test]
fn renders_fragment_with_caret_under_col() {
let line = Line::new(1, b"abcdefghij".to_vec());
let mi = MatchInfo {
hit: true,
col: Some(5),
..Default::default()
};
let emit = Emit {
line: &line,
role: Role::Target,
match_info: &mi,
};
let opts = FormatOpts {
show_line_numbers: true,
show_filename: false,
filename: None,
color: false,
target_marker: false,
line_number_width: 4,
};
let mut f = FragmentFormatter::new(opts, 2);
let mut buf: Vec<u8> = Vec::new();
f.write(&mut buf, &emit).unwrap();
let s = String::from_utf8(buf).unwrap();
assert_eq!(s, " 1: cdefg\n ^\n");
}
}