headless_lms_utils/
merge_edits.rs1pub fn merge(ancestor: &str, incoming_edit: &str, current: &str) -> Option<String> {
2 if ancestor == current {
3 return Some(incoming_edit.to_string());
5 }
6
7 let mut incoming = diff::chars(ancestor, incoming_edit).into_iter().peekable();
8 let mut existing = diff::chars(ancestor, current).into_iter().peekable();
9
10 let mut result = String::new();
11 'outer: loop {
12 match incoming.next() {
13 Some(diff::Result::Both(inc_left, _)) => loop {
15 match existing.next() {
16 Some(diff::Result::Both(..)) => {
18 result.push(inc_left);
19 break;
20 }
21 Some(diff::Result::Left(_)) => break,
23 Some(diff::Result::Right(right)) => result.push(right),
25 None => return None,
27 }
28 },
29 Some(diff::Result::Left(_)) => match existing.next() {
31 Some(diff::Result::Both(..)) => continue,
33 Some(diff::Result::Left(_)) => continue,
35 Some(diff::Result::Right(_)) => continue,
38 None => return None,
40 },
41 Some(diff::Result::Right(inc_right)) => {
43 if let Some(diff::Result::Right(next_existing)) = existing.peek() {
45 if next_existing == &inc_right {
47 existing.next();
48 }
49 }
50 result.push(inc_right)
51 }
52 None => loop {
54 match existing.next() {
55 Some(diff::Result::Both(..)) => continue,
57 Some(diff::Result::Left(_)) => continue,
59 Some(diff::Result::Right(right)) => result.push(right),
61 None => break 'outer,
63 }
64 },
65 }
66 }
67 Some(result)
68}
69
70#[cfg(test)]
71mod test {
72 use super::*;
73
74 #[test]
75 fn typo_and_append() {
76 let ancestor = "This is the original, uneditd text.";
77 let incoming =
78 "This is the original, uneditd text, with more information written at the end.";
79 let current = "This is the original, unedited text.";
80 assert_eq!(
81 merge(ancestor, incoming, current).unwrap(),
82 "This is the original, unedited text, with more information written at the end."
83 );
84 }
85
86 #[test]
87 fn typo_and_prepend() {
88 let ancestor = "This is the original, uneditd text.";
89 let incoming = "I added some things, but this is the original, uneditd text.";
90 let current = "This is the original, unedited text.";
91 assert_eq!(
92 merge(ancestor, incoming, current).unwrap(),
93 "I added some things, but this is the original, unedited text."
94 );
95 }
96
97 #[test]
98 fn typo_and_middle() {
99 let ancestor = "This is the original, uneditd text.";
100 let incoming = "This is the original, completely uneditd text.";
101 let current = "This is the original, unedited text.";
102 assert_eq!(
103 merge(ancestor, incoming, current).unwrap(),
104 "This is the original, completely unedited text."
105 );
106 }
107
108 #[test]
109 fn rewrite() {
110 let ancestor = "This is the original, uneditd text.";
111 let incoming = "I decided to rewrite this paragraph!";
112 let current = "This is the original, unedited text.";
113 assert_eq!(
114 merge(ancestor, incoming, current).unwrap(),
115 "I decided to rewrite this paragraph!"
116 );
117 }
118
119 #[test]
120 fn same_edit_applied_twice() {
121 let ancestor = "This is the original, uneditd text.";
122 let incoming = "This is the original, unedited text.";
123 let current = "This is the original, unedited text.";
124 assert_eq!(
125 merge(ancestor, incoming, current).unwrap(),
126 "This is the original, unedited text."
127 );
128 }
129
130 #[test]
131 fn regression_test() {
132 let ancestor = "paragraphs.";
133 let incoming = "pgraphs.";
134 let current = "paragraphs!";
135 assert_eq!(merge(ancestor, incoming, current).unwrap(), "pgraphs!");
136 }
137}