Undo subsequent edits when undoing in multi-buffer

Nathan Sobo and Antonio Scandurra created

When undoing in the multi-buffer, don't preserve edits that occurred outside the multi-buffer after the edit being undone.

Co-Authored-By: Antonio Scandurra <me@as-cii.com>

Change summary

crates/editor/src/multi_buffer.rs |  4 +-
crates/language/src/buffer.rs     | 28 ++++++++------
crates/text/src/text.rs           | 64 ++++++++++++++++++--------------
3 files changed, 54 insertions(+), 42 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -560,7 +560,7 @@ impl MultiBuffer {
             for (buffer_id, buffer_transaction_id) in &transaction.buffer_transactions {
                 if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(&buffer_id) {
                     undone |= buffer.update(cx, |buf, cx| {
-                        buf.undo_transaction(*buffer_transaction_id, cx)
+                        buf.undo_to_transaction(*buffer_transaction_id, cx)
                     });
                 }
             }
@@ -583,7 +583,7 @@ impl MultiBuffer {
             for (buffer_id, buffer_transaction_id) in &transaction.buffer_transactions {
                 if let Some(BufferState { buffer, .. }) = self.buffers.borrow().get(&buffer_id) {
                     redone |= buffer.update(cx, |buf, cx| {
-                        buf.redo_transaction(*buffer_transaction_id, cx)
+                        buf.redo_to_transaction(*buffer_transaction_id, cx)
                     });
                 }
             }

crates/language/src/buffer.rs 🔗

@@ -1695,7 +1695,7 @@ impl Buffer {
         }
     }
 
-    pub fn undo_transaction(
+    pub fn undo_to_transaction(
         &mut self,
         transaction_id: TransactionId,
         cx: &mut ModelContext<Self>,
@@ -1703,13 +1703,15 @@ impl Buffer {
         let was_dirty = self.is_dirty();
         let old_version = self.version.clone();
 
-        if let Some(operation) = self.text.undo_transaction(transaction_id) {
+        let operations = self.text.undo_to_transaction(transaction_id);
+        let undone = !operations.is_empty();
+        for operation in operations {
             self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, cx);
-            true
-        } else {
-            false
         }
+        if undone {
+            self.did_edit(&old_version, was_dirty, cx)
+        }
+        undone
     }
 
     pub fn redo(&mut self, cx: &mut ModelContext<Self>) -> Option<TransactionId> {
@@ -1725,7 +1727,7 @@ impl Buffer {
         }
     }
 
-    pub fn redo_transaction(
+    pub fn redo_to_transaction(
         &mut self,
         transaction_id: TransactionId,
         cx: &mut ModelContext<Self>,
@@ -1733,13 +1735,15 @@ impl Buffer {
         let was_dirty = self.is_dirty();
         let old_version = self.version.clone();
 
-        if let Some(operation) = self.text.redo_transaction(transaction_id) {
+        let operations = self.text.redo_to_transaction(transaction_id);
+        let redone = !operations.is_empty();
+        for operation in operations {
             self.send_operation(Operation::Buffer(operation), cx);
-            self.did_edit(&old_version, was_dirty, cx);
-            true
-        } else {
-            false
         }
+        if redone {
+            self.did_edit(&old_version, was_dirty, cx)
+        }
+        redone
     }
 
     pub fn completion_triggers(&self) -> &[String] {

crates/text/src/text.rs 🔗

@@ -280,19 +280,19 @@ impl History {
         }
     }
 
-    fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> {
+    fn remove_from_undo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] {
         assert_eq!(self.transaction_depth, 0);
+
+        let redo_stack_start_len = self.redo_stack.len();
         if let Some(entry_ix) = self
             .undo_stack
             .iter()
             .rposition(|entry| entry.transaction.id == transaction_id)
         {
-            let entry = self.undo_stack.remove(entry_ix);
-            self.redo_stack.push(entry);
-            self.redo_stack.last()
-        } else {
-            None
+            self.redo_stack
+                .extend(self.undo_stack.drain(entry_ix..).rev());
         }
+        &self.redo_stack[redo_stack_start_len..]
     }
 
     fn forget(&mut self, transaction_id: TransactionId) {
@@ -322,19 +322,19 @@ impl History {
         }
     }
 
-    fn remove_from_redo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> {
+    fn remove_from_redo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] {
         assert_eq!(self.transaction_depth, 0);
+
+        let undo_stack_start_len = self.undo_stack.len();
         if let Some(entry_ix) = self
             .redo_stack
             .iter()
             .rposition(|entry| entry.transaction.id == transaction_id)
         {
-            let entry = self.redo_stack.remove(entry_ix);
-            self.undo_stack.push(entry);
-            self.undo_stack.last()
-        } else {
-            None
+            self.undo_stack
+                .extend(self.redo_stack.drain(entry_ix..).rev());
         }
+        &self.undo_stack[undo_stack_start_len..]
     }
 }
 
@@ -1203,14 +1203,18 @@ impl Buffer {
         }
     }
 
-    pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
-        if let Some(entry) = self.history.remove_from_undo(transaction_id) {
-            let transaction = entry.transaction.clone();
-            let op = self.undo_or_redo(transaction).unwrap();
-            Some(op)
-        } else {
-            None
-        }
+    pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec<Operation> {
+        let transactions = self
+            .history
+            .remove_from_undo(transaction_id)
+            .iter()
+            .map(|entry| entry.transaction.clone())
+            .collect::<Vec<_>>();
+
+        transactions
+            .into_iter()
+            .map(|transaction| self.undo_or_redo(transaction).unwrap())
+            .collect()
     }
 
     pub fn forget_transaction(&mut self, transaction_id: TransactionId) {
@@ -1228,14 +1232,18 @@ impl Buffer {
         }
     }
 
-    pub fn redo_transaction(&mut self, transaction_id: TransactionId) -> Option<Operation> {
-        if let Some(entry) = self.history.remove_from_redo(transaction_id) {
-            let transaction = entry.transaction.clone();
-            let op = self.undo_or_redo(transaction).unwrap();
-            Some(op)
-        } else {
-            None
-        }
+    pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec<Operation> {
+        let transactions = self
+            .history
+            .remove_from_redo(transaction_id)
+            .iter()
+            .map(|entry| entry.transaction.clone())
+            .collect::<Vec<_>>();
+
+        transactions
+            .into_iter()
+            .map(|transaction| self.undo_or_redo(transaction).unwrap())
+            .collect()
     }
 
     fn undo_or_redo(&mut self, transaction: Transaction) -> Result<Operation> {