use ropey::Rope; use crate::marks::MarkSet; #[derive(Debug, Clone, Copy)] enum Op { Retain(usize), // In bytes. Replace { // These both represent strings, and are byte-index ranges into // the Transaction's `buffer` where their actual string data is // stored. `(0, 0)` can be used to indicate no string data. old: (usize, usize), new: (usize, usize), }, } impl Op { /// The length of the string segment this Op represents *before* /// its application. In bytes. fn len_pre(&self) -> usize { match self { Op::Retain(byte_count) => *byte_count, Op::Replace { old, .. } => old.1 - old.0, } } /// The length of the string segment this Op represents *after* /// its application. In bytes. fn len_post(&self) -> usize { match self { Op::Retain(byte_count) => *byte_count, Op::Replace { new, .. } => new.1 - new.0, } } } /// A reversable set of edits treated as an atomic unit. #[derive(Clone)] pub struct Transaction { ops: Vec, buffer: String, } impl Transaction { pub fn new() -> Transaction { Transaction { ops: Vec::new(), buffer: String::new(), } } /// Creates a Transaction from a single edit. pub fn from_edit(byte_idx: usize, old: &str, new: &str) -> Transaction { let mut buffer = String::new(); buffer.push_str(old); buffer.push_str(new); let ops = vec![ Op::Retain(byte_idx), Op::Replace { old: (0, old.len()), new: (old.len(), old.len() + new.len()), }, ]; Transaction { ops: ops, buffer: buffer, } } /// Creates a Transaction from a sorted, non-overlapping set of /// simultaneous edits. /// /// Takes an iterator that yields `(byte_index, old_text, replacement_text)` /// tuples, representing the edits. The items are expected to be /// yielded in byte-index order, and the byte indices are relative to /// the original text--this function does _not_ treat them as a /// sequence of progressively applied edits, but rather as a set of /// edits applied simultaneously. pub fn from_ordered_edit_set<'a, I>(edit_iter: I) -> Transaction where I: Iterator + 'a, { let mut trans = Transaction::new(); let mut i = 0; let mut len_delta = 0isize; for (byte_idx, old, new) in edit_iter { let adjusted_byte_idx = (byte_idx as isize + len_delta) as usize; let retained = adjusted_byte_idx - i; let old_range = (trans.buffer.len(), trans.buffer.len() + old.len()); trans.buffer.push_str(old); let new_range = (trans.buffer.len(), trans.buffer.len() + new.len()); trans.buffer.push_str(new); if retained > 0 { trans.ops.push(Op::Retain(retained)); } trans.ops.push(Op::Replace { old: old_range, new: new_range, }); i += retained + new.len(); len_delta += new.len() as isize - old.len() as isize; } trans } /// Build a Transaction that is functionally identical to applying /// first `self` and then `other` sequentially. #[must_use] pub fn compose(&self, other: &Transaction) -> Transaction { use Op::*; let mut trans = Transaction::new(); let mut ops1 = self.ops.iter().copied(); let mut ops2 = other.ops.iter().copied(); let mut next_op1 = ops1.next(); let mut next_op2 = ops2.next(); // We progress through both transaction's ops such that their // heads stay in sync. To do this, we have to split ops // sometimes, fragmenting the transaction. loop { match (next_op1, next_op2) { // Done! (None, None) => break, // One of the transactions is empty, so just append the // remaining ops from the other one. (Some(op), None) => { trans.push_op(op, &self.buffer); next_op1 = ops1.next(); } (None, Some(op)) => { trans.push_op(op, &other.buffer); next_op2 = ops2.next(); } //---------------- // Retain, Retain (Some(Retain(bc1)), Some(Retain(bc2))) => { if bc1 < bc2 { trans.retain(bc1); next_op1 = ops1.next(); next_op2 = Some(Retain(bc2 - bc1)); } else if bc2 < bc1 { trans.retain(bc2); next_op1 = Some(Retain(bc1 - bc2)); next_op2 = ops2.next(); } else { trans.retain(bc1); next_op1 = ops1.next(); next_op2 = ops2.next(); } } //----------------- // Replace, Retain ( Some( op1 @ Replace { old: old1, new: new1, }, ), Some(Retain(bc2)), ) => { if op1.len_post() < bc2 { trans.push_op(op1, &self.buffer); next_op1 = ops1.next(); next_op2 = Some(Retain(bc2 - op1.len_post())); } else if op1.len_post() > bc2 { let op1a = Op::Replace { old: old1, new: (new1.0, new1.0 + bc2), }; let op1b = Op::Replace { old: (0, 0), new: (new1.0 + bc2, new1.1), }; trans.push_op(op1a, &self.buffer); next_op1 = Some(op1b); next_op2 = ops2.next(); } else { trans.push_op(op1, &self.buffer); next_op1 = ops1.next(); next_op2 = ops2.next(); } } //----------------- // Retain, Replace ( Some(Retain(bc1)), Some(Replace { old: old2, new: new2, }), ) => { let op2 = next_op2.unwrap(); if bc1 < op2.len_pre() { trans.push_op( Replace { old: (old2.0, old2.0 + bc1), new: (0, 0), }, &other.buffer, ); next_op1 = ops1.next(); next_op2 = Some(Replace { old: (old2.0 + bc1, old2.1), new: new2, }); } else if bc1 > op2.len_pre() { trans.push_op(op2, &other.buffer); next_op1 = Some(Retain(bc1 - op2.len_pre())); next_op2 = ops2.next(); } else { trans.push_op(op2, &other.buffer); next_op1 = ops1.next(); next_op2 = ops2.next(); } } //------------------ // Replace, Replace ( Some(Replace { old: old1, new: new1, }), Some(Replace { old: old2, new: new2, }), ) => { let op1 = next_op1.unwrap(); let op2 = next_op2.unwrap(); if op1.len_post() < op2.len_pre() { trans.push_op( Replace { old: old1, new: (0, 0), }, &self.buffer, ); next_op1 = ops1.next(); next_op2 = Some(Replace { old: (old2.0 + op1.len_post(), old2.1), new: new2, }); } else if op1.len_post() > op2.len_pre() { trans.push_op( Replace { old: old1, new: (0, 0), }, &self.buffer, ); trans.push_op( Replace { old: (0, 0), new: new2, }, &other.buffer, ); next_op1 = Some(Replace { old: (0, 0), new: (new1.0 + op2.len_pre(), new1.1), }); next_op2 = ops2.next(); } else { trans.push_op_separate_buf( Replace { old: old1, new: new2, }, &self.buffer, &other.buffer, ); next_op1 = ops1.next(); next_op2 = ops2.next(); } } } } trans } /// Applies the Transaction to a Rope. pub fn apply(&self, text: &mut Rope) { let mut i = 0; for op in self.ops.iter() { match op { Op::Retain(byte_count) => { i += byte_count; } Op::Replace { old, new } => { let old = &self.buffer[old.0..old.1]; let new = &self.buffer[new.0..new.1]; if !old.is_empty() { debug_assert_eq!(text.slice(i..(i + old.len())), old); text.remove(i..(i + old.len())); } if !new.is_empty() { text.insert(i, new); } i = i + new.len(); } } } debug_assert!(i <= text.len()); } /// Applies the inverse of the Transaction to a Rope. /// /// This is an "undo" of the Transaction. pub fn apply_inverse(&self, text: &mut Rope) { let mut i = 0; for op in self.ops.iter() { match op { Op::Retain(byte_count) => { i += byte_count; } Op::Replace { old: old_range, new: new_range, } => { // Swap old and new, to do the inverse. let old = &self.buffer[new_range.0..new_range.1]; let new = &self.buffer[old_range.0..old_range.1]; if !old.is_empty() { debug_assert_eq!(text.slice(i..(i + old.len())), old); text.remove(i..(i + old.len())); } if !new.is_empty() { text.insert(i, new); } i = i + new.len(); } } } debug_assert!(i <= text.len()); } /// Applies the Transaction to a set of Marks. pub fn apply_to_marks(&self, marks: &mut MarkSet) { marks.make_consistent(); // We do this in two passes: first heads, then tails. for tail in 0..=1 { let mut mark_idx = 0; let mut byte_idx_old = 0; let mut byte_idx_new = 0; for op in self.ops.iter() { let old_len = op.len_pre(); let new_len = op.len_post(); while mark_idx < marks.len() { let idx = if tail == 1 { &mut marks[mark_idx].tail } else { &mut marks[mark_idx].head }; if *idx < byte_idx_old { mark_idx += 1; } else if *idx < (byte_idx_old + old_len) { *idx = *idx + byte_idx_new - byte_idx_old; *idx = (*idx).min(byte_idx_new + new_len); mark_idx += 1; } else { break; } } byte_idx_old += old_len; byte_idx_new += new_len; } while mark_idx < marks.len() { let idx = if tail == 1 { &mut marks[mark_idx].tail } else { &mut marks[mark_idx].head }; *idx = *idx + byte_idx_new - byte_idx_old; mark_idx += 1; } } marks.make_consistent(); } /// Applies the inverse of the Transaction to a set of Marks. /// /// This is essentially an "undo" of the Transaction. However, on /// marks this is a lossy operation and is not guaranteed to return /// the marks to their original count and position. pub fn apply_inverse_to_marks(&self, marks: &mut MarkSet) { marks.make_consistent(); // We do this in two passes: first heads, then tails. for tail in 0..=1 { let mut mark_idx = 0; let mut byte_idx_old = 0; let mut byte_idx_new = 0; for op in self.ops.iter() { let old_len = op.len_post(); let new_len = op.len_pre(); while mark_idx < marks.len() { let idx = if tail == 1 { &mut marks[mark_idx].tail } else { &mut marks[mark_idx].head }; if *idx < byte_idx_old { mark_idx += 1; } else if *idx < (byte_idx_old + old_len) { *idx = *idx + byte_idx_new - byte_idx_old; *idx = (*idx).min(byte_idx_new + new_len); mark_idx += 1; } else { break; } } byte_idx_old += old_len; byte_idx_new += new_len; } while mark_idx < marks.len() { let idx = if tail == 1 { &mut marks[mark_idx].tail } else { &mut marks[mark_idx].head }; *idx = *idx + byte_idx_new - byte_idx_old; mark_idx += 1; } } marks.make_consistent(); } //--------------------------------------------------------- /// Pushes a retain op onto the transaction operations. #[inline(always)] fn retain(&mut self, byte_count: usize) { if let Some(Op::Retain(ref mut last_byte_count)) = self.ops.last_mut() { *last_byte_count += byte_count; } else if byte_count > 0 { self.ops.push(Op::Retain(byte_count)); } } /// Pushes a replace op onto the transaction operations. #[inline(always)] fn replace(&mut self, old: &str, new: &str) { if !old.is_empty() || !new.is_empty() { let ti = self.buffer.len(); self.buffer.push_str(old); let old_range = (ti, ti + old.len()); let ti = self.buffer.len(); self.buffer.push_str(new); let new_range = (ti, ti + new.len()); self.ops.push(Op::Replace { old: old_range, new: new_range, }); } } /// Pushes an op onto the transaction operations. /// /// - op: the operation. /// - buffer: the string buffer the op points into if it's a `Replace`. #[inline(always)] fn push_op(&mut self, op: Op, buffer: &str) { self.push_op_separate_buf(op, buffer, buffer); } /// Pushes an op onto the transaction operations. /// /// - op: the operation. /// - buf_old: the string buffer that `Replace { old }` points into. /// - buf_new: the string buffer that `Replace { new }` points into. #[inline(always)] fn push_op_separate_buf(&mut self, op: Op, buf_old: &str, buf_new: &str) { match op { Op::Retain(byte_count) => self.retain(byte_count), Op::Replace { old, new } => { self.replace(&buf_old[old.0..old.1], &buf_new[new.0..new.1]) } } } } impl std::fmt::Debug for Transaction { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { f.write_str("Transaction {\n")?; f.write_str(" ops: [\n")?; for op in self.ops.iter() { match op { Op::Retain(byte_count) => f.write_str(&format!(" Retain({}),\n", byte_count))?, Op::Replace { old, new } => f.write_str(&format!( " Replace(\n old({}): \"{}\",\n new({}): \"{}\"\n ),\n", old.1 - old.0, &self.buffer[old.0..old.1], new.1 - new.0, &self.buffer[new.0..new.1], ))?, } } f.write_str(" ],\n")?; f.write_str(&format!( " buffer({}): \"{}\",\n", self.buffer.len(), &self.buffer ))?; f.write_str("}\n")?; Ok(()) } } #[cfg(test)] mod tests { use super::*; use crate::marks::{Mark, MarkSet}; #[test] fn apply_to_marks_01() { let trans = Transaction::from_edit(5, "", "hi"); let mut mark_set = MarkSet::new(); mark_set.add_mark(Mark::new(4, 4)); mark_set.add_mark(Mark::new(5, 5)); mark_set.add_mark(Mark::new(6, 6)); trans.apply_to_marks(&mut mark_set); assert_eq!(mark_set.marks[0].head, 4); assert_eq!(mark_set.marks[0].tail, 4); assert_eq!(mark_set.marks[1].head, 7); assert_eq!(mark_set.marks[1].tail, 7); assert_eq!(mark_set.marks[2].head, 8); assert_eq!(mark_set.marks[2].tail, 8); } #[test] fn apply_to_marks_02() { let trans = Transaction { ops: vec![ Op::Retain(5), Op::Replace { old: (0, 0), new: (0, 1), }, Op::Replace { old: (0, 0), new: (1, 2), }, ], buffer: "hi".into(), }; let mut mark_set = MarkSet::new(); mark_set.add_mark(Mark::new(4, 4)); mark_set.add_mark(Mark::new(5, 5)); mark_set.add_mark(Mark::new(6, 6)); trans.apply_to_marks(&mut mark_set); assert_eq!(mark_set.marks[0].head, 4); assert_eq!(mark_set.marks[0].tail, 4); assert_eq!(mark_set.marks[1].head, 7); assert_eq!(mark_set.marks[1].tail, 7); assert_eq!(mark_set.marks[2].head, 8); assert_eq!(mark_set.marks[2].tail, 8); } #[test] fn apply_to_marks_03() { let trans = Transaction::from_edit(5, "hi", ""); let mut mark_set = MarkSet::new(); mark_set.add_mark(Mark::new(4, 4)); mark_set.add_mark(Mark::new(6, 6)); mark_set.add_mark(Mark::new(8, 8)); trans.apply_to_marks(&mut mark_set); assert_eq!(mark_set.marks[0].head, 4); assert_eq!(mark_set.marks[0].tail, 4); assert_eq!(mark_set.marks[1].head, 5); assert_eq!(mark_set.marks[1].tail, 5); assert_eq!(mark_set.marks[2].head, 6); assert_eq!(mark_set.marks[2].tail, 6); } #[test] fn apply_to_marks_04() { let trans = Transaction { ops: vec![ Op::Retain(5), Op::Replace { old: (0, 1), new: (0, 0), }, Op::Replace { old: (1, 2), new: (0, 0), }, ], buffer: "hi".into(), }; let mut mark_set = MarkSet::new(); mark_set.add_mark(Mark::new(4, 4)); mark_set.add_mark(Mark::new(6, 6)); mark_set.add_mark(Mark::new(8, 8)); trans.apply_to_marks(&mut mark_set); assert_eq!(mark_set.marks[0].head, 4); assert_eq!(mark_set.marks[0].tail, 4); assert_eq!(mark_set.marks[1].head, 5); assert_eq!(mark_set.marks[1].tail, 5); assert_eq!(mark_set.marks[2].head, 6); assert_eq!(mark_set.marks[2].tail, 6); } #[test] fn apply_inverse_to_marks_01() { let trans = Transaction::from_edit(5, "hi", ""); let mut mark_set = MarkSet::new(); mark_set.add_mark(Mark::new(4, 4)); mark_set.add_mark(Mark::new(5, 5)); mark_set.add_mark(Mark::new(6, 6)); trans.apply_inverse_to_marks(&mut mark_set); assert_eq!(mark_set.marks[0].head, 4); assert_eq!(mark_set.marks[0].tail, 4); assert_eq!(mark_set.marks[1].head, 7); assert_eq!(mark_set.marks[1].tail, 7); assert_eq!(mark_set.marks[2].head, 8); assert_eq!(mark_set.marks[2].tail, 8); } #[test] fn apply_inverse_to_marks_02() { let trans = Transaction::from_edit(5, "", "hi"); let mut mark_set = MarkSet::new(); mark_set.add_mark(Mark::new(4, 4)); mark_set.add_mark(Mark::new(6, 6)); mark_set.add_mark(Mark::new(8, 8)); trans.apply_inverse_to_marks(&mut mark_set); assert_eq!(mark_set.marks[0].head, 4); assert_eq!(mark_set.marks[0].tail, 4); assert_eq!(mark_set.marks[1].head, 5); assert_eq!(mark_set.marks[1].tail, 5); assert_eq!(mark_set.marks[2].head, 6); assert_eq!(mark_set.marks[2].tail, 6); } }