Skip to main content

rootle_diff/
emphasis.rs

1use crate::{ChangedSpan, Hunk, LineOrigin};
2
3impl Hunk {
4    /// Pair adjacent deletion/addition runs once. Each span belongs to its
5    /// own line, including unequal replacement lengths and multibyte text.
6    pub fn changed_spans(&self) -> Vec<Option<ChangedSpan>> {
7        let mut spans = vec![None; self.lines.len()];
8        let mut position = 0;
9        while position < self.lines.len() {
10            if self.lines[position].origin != LineOrigin::Deletion {
11                position += 1;
12                continue;
13            }
14            let deleted_start = position;
15            while position < self.lines.len() && self.lines[position].origin == LineOrigin::Deletion
16            {
17                position += 1;
18            }
19            let added_start = position;
20            while position < self.lines.len() && self.lines[position].origin == LineOrigin::Addition
21            {
22                position += 1;
23            }
24            let pairs = (added_start - deleted_start).min(position - added_start);
25            for offset in 0..pairs {
26                let old_index = deleted_start + offset;
27                let new_index = added_start + offset;
28                let (old, new) =
29                    changed_pair(&self.lines[old_index].text, &self.lines[new_index].text);
30                spans[old_index] = (!old.is_empty()).then_some(ChangedSpan(old));
31                spans[new_index] = (!new.is_empty()).then_some(ChangedSpan(new));
32            }
33        }
34        spans
35    }
36}
37
38fn changed_pair(old: &str, new: &str) -> (std::ops::Range<usize>, std::ops::Range<usize>) {
39    let prefix: usize = old
40        .chars()
41        .zip(new.chars())
42        .take_while(|(a, b)| a == b)
43        .map(|(character, _)| character.len_utf8())
44        .sum();
45    // Restrict the suffix to what remains after the shared prefix: it
46    // cannot overlap it, including identical and all-prefix replacements.
47    let suffix: usize = old[prefix..]
48        .chars()
49        .rev()
50        .zip(new[prefix..].chars().rev())
51        .take_while(|(a, b)| a == b)
52        .map(|(character, _)| character.len_utf8())
53        .sum();
54    (prefix..old.len() - suffix, prefix..new.len() - suffix)
55}