Skip to main content

pijul_core/diff/
algorithm.rs

1use super::Line;
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
4/// Algorithm used to compute the diff.
5pub enum Algorithm {
6    #[default]
7    Myers,
8    Patience,
9    ImaraHistogram,
10}
11
12pub(super) fn diff(
13    lines_a: &[Line],
14    lines_b: &[Line],
15    algorithm: Algorithm,
16    stop_early: bool,
17) -> D {
18    let result = D {
19        r: Vec::with_capacity(lines_a.len() + lines_b.len()),
20        stop_early,
21    };
22    match algorithm {
23        Algorithm::Patience => {
24            let mut dd = diffs::Replace::new(result);
25            diffs::patience::diff(
26                &mut dd,
27                lines_a,
28                0,
29                lines_a.len(),
30                lines_b,
31                0,
32                lines_b.len(),
33            )
34            .unwrap_or(());
35            dd.into_inner()
36        }
37        Algorithm::Myers => {
38            let mut dd = diffs::Replace::new(result);
39            diffs::myers::diff(
40                &mut dd,
41                lines_a,
42                0,
43                lines_a.len(),
44                lines_b,
45                0,
46                lines_b.len(),
47            )
48            .unwrap_or(());
49            dd.into_inner()
50        }
51        Algorithm::ImaraHistogram => {
52            let source = imara_diff::intern::InternedInput::new(&Lines(lines_a), &Lines(lines_b));
53            imara_diff::diff(imara_diff::Algorithm::Histogram, &source, result)
54        }
55    }
56}
57#[derive(Debug)]
58pub struct D {
59    pub r: Vec<Replacement>,
60    pub stop_early: bool,
61}
62
63impl D {
64    pub fn len(&self) -> usize {
65        self.r.len()
66    }
67}
68
69impl std::ops::Index<usize> for D {
70    type Output = Replacement;
71    fn index(&self, i: usize) -> &Replacement {
72        self.r.index(i)
73    }
74}
75
76impl std::ops::IndexMut<usize> for D {
77    fn index_mut(&mut self, i: usize) -> &mut Replacement {
78        self.r.index_mut(i)
79    }
80}
81
82#[derive(Debug)]
83pub struct Replacement {
84    pub old: usize,
85    pub old_len: usize,
86    pub new: usize,
87    pub new_len: usize,
88    // pub is_cyclic: bool,
89}
90
91impl diffs::Diff for D {
92    type Error = ();
93    fn delete(&mut self, old: usize, old_len: usize, new: usize) -> std::result::Result<(), ()> {
94        debug!("Diff::delete {:?} {:?} {:?}", old, old_len, new);
95        self.r.push(Replacement {
96            old,
97            old_len,
98            new,
99            new_len: 0,
100            // is_cyclic: false,
101        });
102        if self.stop_early { Err(()) } else { Ok(()) }
103    }
104    fn insert(&mut self, old: usize, new: usize, new_len: usize) -> std::result::Result<(), ()> {
105        debug!("Diff::insert {:?} {:?} {:?}", old, new, new_len);
106        self.r.push(Replacement {
107            old,
108            old_len: 0,
109            new,
110            new_len,
111            // is_cyclic: false,
112        });
113        if self.stop_early { Err(()) } else { Ok(()) }
114    }
115    fn replace(
116        &mut self,
117        old: usize,
118        old_len: usize,
119        new: usize,
120        new_len: usize,
121    ) -> std::result::Result<(), ()> {
122        debug!(
123            "Diff::replace {:?} {:?} {:?} {:?}",
124            old, old_len, new, new_len
125        );
126        self.r.push(Replacement {
127            old,
128            old_len,
129            new,
130            new_len,
131            // is_cyclic: false,
132        });
133        if self.stop_early { Err(()) } else { Ok(()) }
134    }
135}
136
137/// struct used for imara interning see InternedInput
138struct Lines<'a>(&'a [Line<'a>]);
139
140impl<'a> imara_diff::intern::TokenSource for &Lines<'a> {
141    type Token = &'a Line<'a>;
142    type Tokenizer = core::slice::Iter<'a, Line<'a>>;
143
144    fn tokenize(&self) -> Self::Tokenizer {
145        self.0.iter()
146    }
147    fn estimate_tokens(&self) -> u32 {
148        self.0.len() as u32
149    }
150}
151
152impl imara_diff::Sink for D {
153    type Out = D;
154
155    fn process_change(&mut self, before: std::ops::Range<u32>, after: std::ops::Range<u32>) {
156        debug!(
157            "Process change: old:{:?}-{:?} new:{:?}-{:?}",
158            before.start, before.end, after.start, after.end
159        );
160        self.r.push(Replacement {
161            old: before.start as usize,
162            old_len: before.len(),
163            new: after.start as usize,
164            new_len: after.len(),
165            // is_cyclic: false,
166        });
167    }
168
169    fn finish(self) -> Self::Out {
170        self
171    }
172}
173
174fn line_index(lines_a: &[Line], pos_bytes: usize) -> usize {
175    // `pos_bytes` is normally exactly a line-start offset (Ok). But it can fall
176    // in the middle of / past a line (e.g. a context position inside a
177    // conflict); binary_search then returns Err(insertion_point). The line that
178    // *contains* `pos_bytes` is the one just before the insertion point, so
179    // clamp to it instead of unwrapping (which panicked — seed 364).
180    match lines_a.binary_search_by(|line| {
181        (line.l.as_ptr() as usize - lines_a[0].l.as_ptr() as usize).cmp(&pos_bytes)
182    }) {
183        Ok(i) => i,
184        Err(i) => i.saturating_sub(1),
185    }
186}
187
188pub struct Deleted {
189    pub replaced: bool,
190    pub next: usize,
191}
192
193impl D {
194    pub(super) fn is_deleted(&self, lines_a: &[Line], pos: usize) -> Option<Deleted> {
195        let line = line_index(lines_a, pos);
196        match self.r.binary_search_by(|repl| repl.old.cmp(&line)) {
197            Ok(i) if self.r[i].old_len > 0 => Some(Deleted {
198                replaced: self.r[i].new_len > 0,
199                next: pos + lines_a[line].l.len(),
200            }),
201            Err(0) => None,
202            Err(i) if line < self.r[i - 1].old + self.r[i - 1].old_len => Some(Deleted {
203                replaced: self.r[i - 1].new_len > 0,
204                next: pos + lines_a[line].l.len(),
205            }),
206            _ => None,
207        }
208    }
209}