From 5ad3f0ce8cc7407bdbf1caf5d4c9efe2984361f8 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Fri, 4 Oct 2024 20:42:15 +0200 Subject: [PATCH] Use emacs-style undo. Rather than truncating the undo stack when making an edit after undoing, the undo steps themselves are pushed onto the undo stack before the edit. This means that the undo stack never loses any previous states of the document, and those states are always accessible by just undoing enough. --- sub_crates/backend/src/history.rs | 17 ++++++++++++++++- sub_crates/backend/src/transaction.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) 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;