use crate::algorithms::DiffHook; /// A [`DiffHook`] that combines deletions and insertions to give blocks /// of maximal length, and replacements when appropriate. /// /// It will replace [`DiffHook::insert`] and [`DiffHook::delete`] events when /// possible with [`DiffHook::replace`] events. Note that even though the /// text processing in the crate does not use replace events and always resolves /// then back to delete and insert, it's useful to always use the replacer to /// ensure a consistent order of inserts and deletes. This is why for instance /// the text diffing automatically uses this hook internally. pub struct Replace { d: D, del: Option<(usize, usize, usize)>, ins: Option<(usize, usize, usize)>, eq: Option<(usize, usize, usize)>, } impl Replace { /// Creates a new replace hook wrapping another hook. pub fn new(d: D) -> Self { Replace { d, del: None, ins: None, eq: None, } } /// Extracts the inner hook. pub fn into_inner(self) -> D { self.d } fn flush_eq(&mut self) -> Result<(), D::Error> { if let Some((eq_old_index, eq_new_index, eq_len)) = self.eq.take() { self.d.equal(eq_old_index, eq_new_index, eq_len)? } Ok(()) } fn flush_del_ins(&mut self) -> Result<(), D::Error> { if let Some((del_old_index, del_old_len, del_new_index)) = self.del.take() { if let Some((_, ins_new_index, ins_new_len)) = self.ins.take() { self.d .replace(del_old_index, del_old_len, ins_new_index, ins_new_len)?; } else { self.d.delete(del_old_index, del_old_len, del_new_index)?; } } else if let Some((ins_old_index, ins_new_index, ins_new_len)) = self.ins.take() { self.d.insert(ins_old_index, ins_new_index, ins_new_len)?; } Ok(()) } } impl AsRef for Replace { fn as_ref(&self) -> &D { &self.d } } impl AsMut for Replace { fn as_mut(&mut self) -> &mut D { &mut self.d } } impl DiffHook for Replace { type Error = D::Error; fn equal(&mut self, old_index: usize, new_index: usize, len: usize) -> Result<(), D::Error> { self.flush_del_ins()?; self.eq = if let Some((eq_old_index, eq_new_index, eq_len)) = self.eq.take() { Some((eq_old_index, eq_new_index, eq_len + len)) } else { Some((old_index, new_index, len)) }; Ok(()) } fn delete( &mut self, old_index: usize, old_len: usize, new_index: usize, ) -> Result<(), D::Error> { if let Some((a, b, c)) = self.eq.take() { self.d.equal(a, b, c)?; } if let Some((del_old_index, del_old_len, del_new_index)) = self.del.take() { assert_eq!(old_index, del_old_index + del_old_len); self.del = Some((del_old_index, del_old_len + old_len, del_new_index)); } else { self.del = Some((old_index, old_len, new_index)); } Ok(()) } fn insert( &mut self, old_index: usize, new_index: usize, new_len: usize, ) -> Result<(), D::Error> { self.flush_eq()?; self.ins = if let Some((ins_old_index, ins_new_index, ins_new_len)) = self.ins.take() { debug_assert_eq!(ins_new_index + ins_new_len, new_index); Some((ins_old_index, ins_new_index, new_len + ins_new_len)) } else { Some((old_index, new_index, new_len)) }; Ok(()) } fn replace( &mut self, old_index: usize, old_len: usize, new_index: usize, new_len: usize, ) -> Result<(), D::Error> { self.flush_eq()?; self.d.replace(old_index, old_len, new_index, new_len) } fn finish(&mut self) -> Result<(), D::Error> { self.flush_eq()?; self.flush_del_ins()?; self.d.finish() } } #[test] fn test_mayers_replace() { use crate::algorithms::myers; let a: &[&str] = &[ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", "a\n", "b\n", "c\n", "================================\n", "d\n", "e\n", "f\n", "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", ]; let b: &[&str] = &[ ">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n", "x\n", "b\n", "c\n", "================================\n", "y\n", "e\n", "f\n", "<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", ]; let mut d = Replace::new(crate::algorithms::Capture::new()); myers::diff_slices(&mut d, a, b).unwrap(); insta::assert_debug_snapshot!(&d.into_inner().ops(), @r###" [ Equal { old_index: 0, new_index: 0, len: 1, }, Replace { old_index: 1, old_len: 1, new_index: 1, new_len: 1, }, Equal { old_index: 2, new_index: 2, len: 3, }, Replace { old_index: 5, old_len: 1, new_index: 5, new_len: 1, }, Equal { old_index: 6, new_index: 6, len: 3, }, ] "###); } #[test] fn test_replace() { let a: &[usize] = &[0, 1, 2, 3, 4]; let b: &[usize] = &[0, 1, 2, 7, 8, 9]; let mut d = Replace::new(crate::algorithms::Capture::new()); crate::algorithms::myers::diff_slices(&mut d, a, b).unwrap(); insta::assert_debug_snapshot!(d.into_inner().ops(), @r###" [ Equal { old_index: 0, new_index: 0, len: 3, }, Replace { old_index: 3, old_len: 2, new_index: 3, new_len: 3, }, ] "###); }