1use std::collections::HashMap;
8use std::path::PathBuf;
9use std::sync::{Mutex, PoisonError};
10
11static GLOBAL: Mutex<Option<DeliveredRanges>> = Mutex::new(None);
12
13pub fn global() -> std::sync::MutexGuard<'static, Option<DeliveredRanges>> {
15 GLOBAL.lock().unwrap_or_else(PoisonError::into_inner)
16}
17
18#[derive(Debug, Clone, Default)]
20pub struct IntervalSet {
21 intervals: Vec<(usize, usize)>,
22}
23
24impl IntervalSet {
25 pub fn insert(&mut self, start: usize, end: usize) {
27 if start > end {
28 return;
29 }
30 self.intervals.push((start, end));
31 self.merge();
32 }
33
34 pub fn novel_lines(&self, start: usize, end: usize) -> Vec<(usize, usize)> {
36 if start > end {
37 return Vec::new();
38 }
39 let mut novel = Vec::new();
40 let mut cursor = start;
41
42 for &(ds, de) in &self.intervals {
43 if ds > cursor {
44 novel.push((cursor, ds.min(end + 1) - 1));
45 }
46 if de >= cursor {
47 cursor = de + 1;
48 }
49 if cursor > end {
50 break;
51 }
52 }
53
54 if cursor <= end {
55 novel.push((cursor, end));
56 }
57
58 novel
59 }
60
61 pub fn overlap_fraction(&self, start: usize, end: usize) -> f64 {
63 if start > end {
64 return 0.0;
65 }
66 let total = (end - start + 1) as f64;
67 let delivered: usize = self
68 .intervals
69 .iter()
70 .map(|&(ds, de)| {
71 let overlap_start = ds.max(start);
72 let overlap_end = de.min(end);
73 if overlap_start <= overlap_end {
74 overlap_end - overlap_start + 1
75 } else {
76 0
77 }
78 })
79 .sum();
80 delivered as f64 / total
81 }
82
83 fn merge(&mut self) {
84 self.intervals.sort_by_key(|&(s, _)| s);
85 let mut merged: Vec<(usize, usize)> = Vec::new();
86 for (s, e) in self.intervals.drain(..) {
87 if let Some(last) = merged.last_mut()
88 && s <= last.1 + 1
89 {
90 last.1 = last.1.max(e);
91 continue;
92 }
93 merged.push((s, e));
94 }
95 self.intervals = merged;
96 }
97}
98
99#[derive(Debug, Clone, Default)]
101pub struct DeliveredRanges {
102 files: HashMap<PathBuf, IntervalSet>,
103}
104
105impl DeliveredRanges {
106 pub fn new() -> Self {
107 Self::default()
108 }
109
110 pub fn record(&mut self, path: &str, start: usize, end: usize) {
112 self.files
113 .entry(PathBuf::from(path))
114 .or_default()
115 .insert(start, end);
116 }
117
118 pub fn novel_ranges(&self, path: &str, start: usize, end: usize) -> Vec<(usize, usize)> {
120 match self.files.get(&PathBuf::from(path)) {
121 Some(set) => set.novel_lines(start, end),
122 None => vec![(start, end)],
123 }
124 }
125
126 pub fn overlap_fraction(&self, path: &str, start: usize, end: usize) -> f64 {
128 match self.files.get(&PathBuf::from(path)) {
129 Some(set) => set.overlap_fraction(start, end),
130 None => 0.0,
131 }
132 }
133
134 pub fn record_full(&mut self, path: &str, line_count: usize) {
136 if line_count > 0 {
137 self.record(path, 1, line_count);
138 }
139 }
140
141 pub fn invalidate(&mut self, path: &str) {
143 self.files.remove(&PathBuf::from(path));
144 }
145
146 pub fn reset(&mut self) {
147 self.files.clear();
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn interval_set_insert_and_merge() {
157 let mut set = IntervalSet::default();
158 set.insert(1, 10);
159 set.insert(15, 20);
160 set.insert(8, 17);
161 assert_eq!(set.intervals, vec![(1, 20)]);
162 }
163
164 #[test]
165 fn interval_set_novel_lines() {
166 let mut set = IntervalSet::default();
167 set.insert(1, 50);
168 set.insert(80, 100);
169
170 let novel = set.novel_lines(40, 90);
171 assert_eq!(novel, vec![(51, 79)]);
172 }
173
174 #[test]
175 fn interval_set_all_novel_when_empty() {
176 let set = IntervalSet::default();
177 assert_eq!(set.novel_lines(1, 100), vec![(1, 100)]);
178 }
179
180 #[test]
181 fn interval_set_nothing_novel_when_fully_covered() {
182 let mut set = IntervalSet::default();
183 set.insert(1, 200);
184 assert!(set.novel_lines(50, 150).is_empty());
185 }
186
187 #[test]
188 fn overlap_fraction_partial() {
189 let mut set = IntervalSet::default();
190 set.insert(1, 50);
191 let frac = set.overlap_fraction(1, 100);
192 assert!((frac - 0.5).abs() < 0.01);
193 }
194
195 #[test]
196 fn overlap_fraction_zero_when_empty() {
197 let set = IntervalSet::default();
198 assert_eq!(set.overlap_fraction(1, 100), 0.0);
199 }
200
201 #[test]
202 fn delivered_ranges_record_and_query() {
203 let mut dr = DeliveredRanges::new();
204 dr.record("src/db.py", 1, 100);
205
206 let novel = dr.novel_ranges("src/db.py", 50, 200);
207 assert_eq!(novel, vec![(101, 200)]);
208
209 let overlap = dr.overlap_fraction("src/db.py", 50, 200);
210 assert!((overlap - 50.0 / 151.0).abs() < 0.01);
211 }
212
213 #[test]
214 fn delivered_ranges_full_file() {
215 let mut dr = DeliveredRanges::new();
216 dr.record_full("src/main.rs", 500);
217 assert!(dr.novel_ranges("src/main.rs", 1, 500).is_empty());
218 assert_eq!(dr.overlap_fraction("src/main.rs", 1, 500), 1.0);
219 }
220
221 #[test]
222 fn delivered_ranges_invalidate() {
223 let mut dr = DeliveredRanges::new();
224 dr.record_full("src/lib.rs", 100);
225 dr.invalidate("src/lib.rs");
226 assert_eq!(dr.novel_ranges("src/lib.rs", 1, 100), vec![(1, 100)]);
227 }
228
229 #[test]
230 fn delivered_ranges_evaluation_scenario() {
231 let mut dr = DeliveredRanges::new();
232
233 dr.record("src/db.py", 1, 100);
234 let novel = dr.novel_ranges("src/db.py", 50, 200);
235 assert_eq!(novel, vec![(101, 200)]);
236 dr.record("src/db.py", 101, 200);
237
238 let novel = dr.novel_ranges("src/db.py", 80, 180);
239 assert!(novel.is_empty(), "third read should be fully deduplicated");
240 }
241}