headless_lms_utils/
merge_edits.rspub fn merge(ancestor: &str, incoming_edit: &str, current: &str) -> Option<String> {
if ancestor == current {
return Some(incoming_edit.to_string());
}
let mut incoming = diff::chars(ancestor, incoming_edit).into_iter().peekable();
let mut existing = diff::chars(ancestor, current).into_iter().peekable();
let mut result = String::new();
'outer: loop {
match incoming.next() {
Some(diff::Result::Both(inc_left, _)) => loop {
match existing.next() {
Some(diff::Result::Both(..)) => {
result.push(inc_left);
break;
}
Some(diff::Result::Left(_)) => break,
Some(diff::Result::Right(right)) => result.push(right),
None => return None,
}
},
Some(diff::Result::Left(_)) => match existing.next() {
Some(diff::Result::Both(..)) => continue,
Some(diff::Result::Left(_)) => continue,
Some(diff::Result::Right(_)) => continue,
None => return None,
},
Some(diff::Result::Right(inc_right)) => {
if let Some(diff::Result::Right(next_existing)) = existing.peek() {
if next_existing == &inc_right {
existing.next();
}
}
result.push(inc_right)
}
None => loop {
match existing.next() {
Some(diff::Result::Both(..)) => continue,
Some(diff::Result::Left(_)) => continue,
Some(diff::Result::Right(right)) => result.push(right),
None => break 'outer,
}
},
}
}
Some(result)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn typo_and_append() {
let ancestor = "This is the original, uneditd text.";
let incoming =
"This is the original, uneditd text, with more information written at the end.";
let current = "This is the original, unedited text.";
assert_eq!(
merge(ancestor, incoming, current).unwrap(),
"This is the original, unedited text, with more information written at the end."
);
}
#[test]
fn typo_and_prepend() {
let ancestor = "This is the original, uneditd text.";
let incoming = "I added some things, but this is the original, uneditd text.";
let current = "This is the original, unedited text.";
assert_eq!(
merge(ancestor, incoming, current).unwrap(),
"I added some things, but this is the original, unedited text."
);
}
#[test]
fn typo_and_middle() {
let ancestor = "This is the original, uneditd text.";
let incoming = "This is the original, completely uneditd text.";
let current = "This is the original, unedited text.";
assert_eq!(
merge(ancestor, incoming, current).unwrap(),
"This is the original, completely unedited text."
);
}
#[test]
fn rewrite() {
let ancestor = "This is the original, uneditd text.";
let incoming = "I decided to rewrite this paragraph!";
let current = "This is the original, unedited text.";
assert_eq!(
merge(ancestor, incoming, current).unwrap(),
"I decided to rewrite this paragraph!"
);
}
#[test]
fn same_edit_applied_twice() {
let ancestor = "This is the original, uneditd text.";
let incoming = "This is the original, unedited text.";
let current = "This is the original, unedited text.";
assert_eq!(
merge(ancestor, incoming, current).unwrap(),
"This is the original, unedited text."
);
}
#[test]
fn regression_test() {
let ancestor = "paragraphs.";
let incoming = "pgraphs.";
let current = "paragraphs!";
assert_eq!(merge(ancestor, incoming, current).unwrap(), "pgraphs!");
}
}