1use super::{FormatOpts, Formatter, ansi};
6use crate::Emit;
7use std::io::{self, Write};
8
9pub struct FragmentFormatter {
10 pub opts: FormatOpts,
11 pub char_context: usize,
12}
13
14impl FragmentFormatter {
15 pub fn new(opts: FormatOpts, char_context: usize) -> Self {
16 Self { opts, char_context }
17 }
18}
19
20impl Formatter for FragmentFormatter {
21 fn write(&mut self, sink: &mut dyn Write, emit: &Emit) -> io::Result<()> {
22 self.opts.widen_for_line(emit.line.no);
23 let target_col_1 = emit
25 .match_info
26 .col
27 .or_else(|| emit.match_info.spans.first().map(|r| r.start + 1))
28 .unwrap_or(1);
29
30 let bytes = &emit.line.bytes;
31 let col_idx = target_col_1
32 .saturating_sub(1)
33 .min(bytes.len().saturating_sub(1));
34 let start = col_idx.saturating_sub(self.char_context);
35 let end = bytes.len().min(col_idx + self.char_context + 1);
36
37 let frag = String::from_utf8_lossy(&bytes[start..end]).to_string();
38 let prefix = self.opts.prefix(emit.line.no);
39
40 let rendered = if let Some(span) = emit.match_info.spans.first() {
42 if self.opts.color {
43 let hs = span.start.saturating_sub(start).min(frag.len());
44 let he = (span.end.saturating_sub(start)).min(frag.len());
45 if hs < he {
46 let (a, rest) = frag.split_at(hs);
47 let (b, c) = rest.split_at(he - hs);
48 format!("{a}{}{b}{}{c}", ansi::INVERSE, ansi::RESET)
49 } else {
50 frag.clone()
51 }
52 } else {
53 frag.clone()
54 }
55 } else {
56 frag.clone()
57 };
58
59 writeln!(sink, "{prefix}{rendered}")?;
60
61 let caret_offset = col_idx - start + prefix.len();
63 let spaces = " ".repeat(caret_offset);
64 let caret = ansi::paint(self.opts.color, ansi::GREEN, "^");
65 writeln!(sink, "{spaces}{caret}")
66 }
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::{Line, MatchInfo, Role};
73
74 #[test]
75 fn renders_fragment_with_caret_under_col() {
76 let line = Line::new(1, b"abcdefghij".to_vec());
77 let mi = MatchInfo {
78 hit: true,
79 col: Some(5),
80 ..Default::default()
81 };
82 let emit = Emit {
83 line: &line,
84 role: Role::Target,
85 match_info: &mi,
86 };
87 let opts = FormatOpts {
88 show_line_numbers: true,
89 show_filename: false,
90 filename: None,
91 color: false,
92 target_marker: false,
93 line_number_width: 4,
94 };
95 let mut f = FragmentFormatter::new(opts, 2);
96 let mut buf: Vec<u8> = Vec::new();
97 f.write(&mut buf, &emit).unwrap();
98 let s = String::from_utf8(buf).unwrap();
99 assert_eq!(s, " 1: cdefg\n ^\n");
102 }
103}