diff --git a/sub_crates/backend/src/history.rs b/sub_crates/backend/src/history.rs index 21245d9..a723765 100644 --- a/sub_crates/backend/src/history.rs +++ b/sub_crates/backend/src/history.rs @@ -15,7 +15,7 @@ impl History { } pub fn push_edit(&mut self, edit: Transaction) { - self.edits.truncate(self.position); + self.commit(); self.edits.push(edit); self.position += 1; } @@ -38,4 +38,19 @@ impl History { None } } + + /// Takes the current undo state, and pushes that as another transaction on + /// top of the existing undo stack, without deleting any existing items. + pub fn commit(&mut self) { + if self.position == self.edits.len() { + return; + } + + let mut undo_trans = Transaction::new(); + for trans in self.edits[self.position..].iter().rev() { + undo_trans = undo_trans.compose(&trans.inverse()); + } + self.edits.push(undo_trans); + self.position = self.edits.len(); + } } diff --git a/sub_crates/backend/src/transaction.rs b/sub_crates/backend/src/transaction.rs index 40c5f77..6174896 100644 --- a/sub_crates/backend/src/transaction.rs +++ b/sub_crates/backend/src/transaction.rs @@ -294,6 +294,32 @@ impl Transaction { trans } + /// Creates a transaction that is the inverse of this one (i.e. the "undo" + /// of this transaction). + pub fn inverse(&self) -> Transaction { + let mut inv = Transaction::new(); + + for op in self.ops.iter() { + match op { + Op::Retain(byte_count) => { + inv.retain(*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]; + + inv.replace(old, new); + } + } + } + + inv + } + /// Applies the Transaction to a Rope. pub fn apply(&self, text: &mut Rope) { let mut i = 0;