1use super::Matcher;
4use crate::selector::{LineSpec, Selector};
5use crate::{Line, MatchInfo};
6
7pub struct LineMatcher {
9 ranges: Vec<(u64, u64)>,
11 next_range: usize,
12 last_line_no: u64,
13}
14
15impl LineMatcher {
16 pub fn from_selector(sel: &Selector) -> Self {
18 let normalized = sel.normalize();
19 let specs: &[LineSpec] = match &normalized {
20 Selector::LineNumbers(s) => s,
21 Selector::All => &[],
22 Selector::Positions(_) => {
23 panic!("LineMatcher::from_selector called with positional selector")
24 }
25 };
26 let ranges = specs
27 .iter()
28 .map(|s| match s {
29 LineSpec::Single(n) => (*n as u64, *n as u64),
30 LineSpec::Range(a, b) => (*a as u64, *b as u64),
31 })
32 .collect();
33 Self {
34 ranges,
35 next_range: 0,
36 last_line_no: 0,
37 }
38 }
39}
40
41impl Matcher for LineMatcher {
42 fn match_line(&mut self, line: &Line) -> MatchInfo {
43 if line.no < self.last_line_no {
44 self.next_range = 0;
45 }
46 self.last_line_no = line.no;
47 while self
48 .ranges
49 .get(self.next_range)
50 .is_some_and(|&(_, end)| line.no > end)
51 {
52 self.next_range += 1;
53 }
54 let hit = self
55 .ranges
56 .get(self.next_range)
57 .is_some_and(|&(start, end)| line.no >= start && line.no <= end);
58 MatchInfo {
59 hit,
60 ..MatchInfo::default()
61 }
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 fn mk_line(n: u64) -> Line {
70 Line::new(n, Vec::new())
71 }
72
73 #[test]
74 fn single_line_hits_only_that_line() {
75 let sel = Selector::parse("5").unwrap();
76 let mut m = LineMatcher::from_selector(&sel);
77 assert!(!m.match_line(&mk_line(4)).hit);
78 assert!(m.match_line(&mk_line(5)).hit);
79 assert!(!m.match_line(&mk_line(6)).hit);
80 }
81
82 #[test]
83 fn range_hits_inclusive() {
84 let sel = Selector::parse("10-12").unwrap();
85 let mut m = LineMatcher::from_selector(&sel);
86 assert!(!m.match_line(&mk_line(9)).hit);
87 assert!(m.match_line(&mk_line(10)).hit);
88 assert!(m.match_line(&mk_line(11)).hit);
89 assert!(m.match_line(&mk_line(12)).hit);
90 assert!(!m.match_line(&mk_line(13)).hit);
91 }
92
93 #[test]
94 fn mixed_list_merges_ranges() {
95 let sel = Selector::parse("1,5,10-15,14").unwrap();
96 let mut m = LineMatcher::from_selector(&sel);
97 assert!(m.match_line(&mk_line(1)).hit);
98 assert!(!m.match_line(&mk_line(2)).hit);
99 assert!(m.match_line(&mk_line(5)).hit);
100 assert!(m.match_line(&mk_line(12)).hit);
101 assert!(m.match_line(&mk_line(15)).hit);
102 assert!(!m.match_line(&mk_line(16)).hit);
103 }
104}