Skip to main content

_diffctx/
interval.rs

1use std::sync::Arc;
2
3use rustc_hash::{FxHashMap, FxHashSet};
4
5use crate::types::{Fragment, FragmentId};
6
7pub struct IntervalIndex {
8    by_path: FxHashMap<Arc<str>, Vec<(u32, u32)>>,
9    ids: FxHashSet<FragmentId>,
10}
11
12impl IntervalIndex {
13    pub fn new() -> Self {
14        Self {
15            by_path: FxHashMap::default(),
16            ids: FxHashSet::default(),
17        }
18    }
19
20    pub fn add(&mut self, frag: &Fragment) {
21        self.add_id(&frag.id);
22    }
23
24    pub fn add_id(&mut self, frag_id: &FragmentId) {
25        self.ids.insert(frag_id.clone());
26        let intervals = self.by_path.entry(frag_id.path.clone()).or_default();
27        let item = (frag_id.start_line, frag_id.end_line);
28        let pos = intervals.binary_search(&item).unwrap_or_else(|e| e);
29        intervals.insert(pos, item);
30    }
31
32    pub fn contains(&self, frag_id: &FragmentId) -> bool {
33        self.ids.contains(frag_id)
34    }
35
36    pub fn overlaps(&self, frag: &Fragment) -> bool {
37        let intervals = match self.by_path.get(&frag.id.path) {
38            Some(v) => v,
39            None => return false,
40        };
41        let upper = intervals.partition_point(|&(s, _)| s <= frag.end_line());
42        for i in 0..upper {
43            let (start, end) = intervals[i];
44            if start == frag.start_line() && end == frag.end_line() {
45                continue;
46            }
47            // Strict `>`: a fragment starting on the very last line of an
48            // already-selected fragment shares exactly one boundary line. We
49            // deliberately tolerate that one-line overlap rather than drop the
50            // candidate, because compact languages (Rust/Go/Scala one-liners,
51            // Lisp `}{` chains) routinely produce back-to-back fragments sharing
52            // that boundary line; rejecting them would silently discard the
53            // next fragment's unique content for the sake of one duplicated line.
54            if end > frag.start_line() {
55                return true;
56            }
57        }
58        false
59    }
60
61    pub fn is_superset_of(&self, frag: &Fragment) -> bool {
62        let intervals = match self.by_path.get(&frag.id.path) {
63            Some(v) => v,
64            None => return false,
65        };
66        let upper = intervals.partition_point(|&(s, _)| s <= frag.start_line());
67        for i in 0..upper {
68            let (start, end) = intervals[i];
69            if start == frag.start_line() && end == frag.end_line() {
70                continue;
71            }
72            if start <= frag.start_line() && frag.end_line() <= end {
73                return true;
74            }
75        }
76        false
77    }
78}