1use crate::{Emit, Line, MatchInfo, Role};
4use std::collections::VecDeque;
5
6pub trait Expander {
11 fn push(&mut self, line: Line, info: MatchInfo, out: &mut dyn FnMut(EmitOwned));
13
14 fn drain(&mut self, out: &mut dyn FnMut(EmitOwned));
16}
17
18#[derive(Debug, Clone)]
20pub struct EmitOwned {
21 pub line: Line,
22 pub role: Role,
23 pub match_info: MatchInfo,
24}
25
26impl EmitOwned {
27 pub fn borrow(&self) -> Emit<'_> {
28 Emit {
29 line: &self.line,
30 role: self.role,
31 match_info: &self.match_info,
32 }
33 }
34}
35
36pub struct NoContext;
38
39impl Expander for NoContext {
40 fn push(&mut self, line: Line, info: MatchInfo, out: &mut dyn FnMut(EmitOwned)) {
41 if info.hit {
42 out(EmitOwned {
43 line,
44 role: Role::Target,
45 match_info: info,
46 });
47 }
48 }
49
50 fn drain(&mut self, _out: &mut dyn FnMut(EmitOwned)) {}
51}
52
53pub struct LineContext {
55 n: usize,
56 before: VecDeque<(Line, MatchInfo)>,
58 trailing: usize,
60 last_emitted: u64,
62}
63
64impl LineContext {
65 pub fn new(n: usize) -> Self {
66 Self {
67 n,
68 before: VecDeque::with_capacity(n),
69 trailing: 0,
70 last_emitted: 0,
71 }
72 }
73
74 fn emit(&mut self, line: Line, info: MatchInfo, role: Role, out: &mut dyn FnMut(EmitOwned)) {
75 if line.no <= self.last_emitted {
76 return;
77 }
78 self.last_emitted = line.no;
79 out(EmitOwned {
80 line,
81 role,
82 match_info: info,
83 });
84 }
85}
86
87impl Expander for LineContext {
88 fn push(&mut self, line: Line, info: MatchInfo, out: &mut dyn FnMut(EmitOwned)) {
89 if info.hit {
90 let buffered: Vec<_> = self.before.drain(..).collect();
92 for (bl, bi) in buffered {
93 self.emit(bl, bi, Role::Context, out);
94 }
95 let hit_line = line;
96 let hit_info = info;
97 self.emit(hit_line, hit_info, Role::Target, out);
98 self.trailing = self.n;
99 } else if self.trailing > 0 {
100 self.trailing -= 1;
101 self.emit(line, info, Role::Context, out);
102 } else {
103 if self.n > 0 {
105 if self.before.len() == self.n {
106 self.before.pop_front();
107 }
108 self.before.push_back((line, info));
109 }
110 }
111 }
112
113 fn drain(&mut self, _out: &mut dyn FnMut(EmitOwned)) {
114 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 fn hit(n: u64) -> (Line, MatchInfo) {
123 (
124 Line::new(n, format!("line{n}").into_bytes()),
125 MatchInfo {
126 hit: true,
127 ..Default::default()
128 },
129 )
130 }
131 fn miss(n: u64) -> (Line, MatchInfo) {
132 (
133 Line::new(n, format!("line{n}").into_bytes()),
134 MatchInfo::default(),
135 )
136 }
137
138 fn collect<E: Expander>(mut e: E, inputs: Vec<(Line, MatchInfo)>) -> Vec<(u64, Role)> {
139 let mut out: Vec<(u64, Role)> = Vec::new();
140 {
141 let mut f = |emit: EmitOwned| out.push((emit.line.no, emit.role));
142 for (l, i) in inputs {
143 e.push(l, i, &mut f);
144 }
145 e.drain(&mut f);
146 }
147 out
148 }
149
150 #[test]
151 fn no_context_emits_only_hits() {
152 let out = collect(NoContext, vec![miss(1), hit(2), miss(3), hit(4)]);
153 assert_eq!(out, vec![(2, Role::Target), (4, Role::Target)]);
154 }
155
156 #[test]
157 fn line_context_emits_around_hit() {
158 let out = collect(
159 LineContext::new(1),
160 vec![miss(1), miss(2), hit(3), miss(4), miss(5)],
161 );
162 assert_eq!(
163 out,
164 vec![(2, Role::Context), (3, Role::Target), (4, Role::Context),]
165 );
166 }
167
168 #[test]
169 fn overlapping_contexts_do_not_duplicate() {
170 let out = collect(LineContext::new(1), vec![miss(1), hit(2), hit(3), miss(4)]);
171 assert_eq!(
172 out,
173 vec![
174 (1, Role::Context),
175 (2, Role::Target),
176 (3, Role::Target),
177 (4, Role::Context),
178 ]
179 );
180 }
181}