on aa00a47 help sign in
64 lines 2.5 kB history raw
1use crate::{Rope, Transaction};
3/// Compares `old` and `new` to generate a [`Transaction`] describing
4/// the steps required to get from `old` to `new`.
5pub fn compare_ropes(old: &Rope, new: &Rope) -> Transaction {
6 // `similar` only works on contiguous data, so a `Rope` has
7 // to be temporarily converted into a `String`.
8 let old_converted = old.to_string();
9 let new_converted = new.to_string();
11 // A timeout is set so after 1 seconds, the algorithm will start
12 // approximating. This is especially important for big `Rope`s or
13 // `Rope`s that are extremely dissimilar to each other.
14 let mut config = similar::TextDiff::configure();
15 config.timeout(std::time::Duration::from_secs(1));
17 let diff = config.diff_chars(&old_converted, &new_converted);
19 // The current position of the change needs to be tracked to
20 // construct the `Change`s.
21 let mut pos = 0;
22 Transaction::change(
23 old,
24 diff.ops()
25 .iter()
26 .map(|op| op.as_tag_tuple())
27 .filter_map(|(tag, old_range, new_range)| {
28 // `old_pos..pos` is equivalent to `start..end` for where
29 // the change should be applied.
30 let old_pos = pos;
31 pos += old_range.end - old_range.start;
33 match tag {
34 // Semantically, inserts and replacements are the same thing.
35 similar::DiffTag::Insert | similar::DiffTag::Replace => {
36 // This is the text from the `new` rope that should be
37 // inserted into `old`.
38 let text: &str = {
39 let start = new.char_to_byte(new_range.start);
40 let end = new.char_to_byte(new_range.end);
41 &new_converted[start..end]
42 };
43 Some((old_pos, pos, Some(text.into())))
44 }
45 similar::DiffTag::Delete => Some((old_pos, pos, None)),
46 similar::DiffTag::Equal => None,
47 }
48 }),
49 )
52#[cfg(test)]
53mod tests {
54 use super::*;
56 quickcheck::quickcheck! {
57 fn test_compare_ropes(a: String, b: String) -> bool {
58 let mut old = Rope::from(a);
59 let new = Rope::from(b);
60 compare_ropes(&old, &new).apply(&mut old);
61 old == new
62 }
63 }