Introduce transactional edits and allow snapshotting of selections

Antonio Scandurra created

Change summary

zed/src/editor/buffer/mod.rs  | 225 ++++++++++++++++++++++++++++++------
zed/src/editor/buffer_view.rs |  22 +++
2 files changed, 203 insertions(+), 44 deletions(-)

Detailed changes

zed/src/editor/buffer/mod.rs 🔗

@@ -66,7 +66,8 @@ pub struct Buffer {
     last_edit: time::Local,
     undo_map: UndoMap,
     history: History,
-    selections: HashMap<SelectionSetId, Vec<Selection>>,
+    pending_transaction: Option<PendingTransaction>,
+    selections: HashMap<SelectionSetId, Arc<[Selection]>>,
     pub selections_last_update: SelectionsVersion,
     deferred_ops: OperationQueue<Operation>,
     deferred_replicas: HashSet<ReplicaId>,
@@ -80,8 +81,22 @@ pub struct Snapshot {
 }
 
 #[derive(Clone)]
-struct EditGroup {
+struct SelectionSetSnapshot {
+    set_id: SelectionSetId,
+    before_transaction: Arc<[Selection]>,
+    after_transaction: Arc<[Selection]>,
+}
+
+#[derive(Clone)]
+struct PendingTransaction {
+    start: time::Global,
+    buffer_was_dirty: bool,
+}
+
+#[derive(Clone)]
+struct Transaction {
     edits: Vec<time::Local>,
+    selections: Option<SelectionSetSnapshot>,
     last_edit_at: Instant,
 }
 
@@ -89,8 +104,9 @@ struct EditGroup {
 pub struct History {
     pub base_text: Arc<str>,
     ops: HashMap<time::Local, EditOperation>,
-    undo_stack: Vec<EditGroup>,
-    redo_stack: Vec<EditGroup>,
+    undo_stack: Vec<Transaction>,
+    redo_stack: Vec<Transaction>,
+    transaction_depth: usize,
     group_interval: Duration,
 }
 
@@ -101,6 +117,7 @@ impl History {
             ops: Default::default(),
             undo_stack: Vec::new(),
             redo_stack: Vec::new(),
+            transaction_depth: 0,
             group_interval: UNDO_GROUP_INTERVAL,
         }
     }
@@ -109,37 +126,85 @@ impl History {
         self.ops.insert(op.id, op);
     }
 
-    fn push_undo(&mut self, edit_id: time::Local, now: Instant) {
-        if let Some(edit_group) = self.undo_stack.last_mut() {
-            if now - edit_group.last_edit_at <= self.group_interval {
-                edit_group.edits.push(edit_id);
-                edit_group.last_edit_at = now;
+    fn start_transaction(
+        &mut self,
+        selections: Option<(SelectionSetId, Arc<[Selection]>)>,
+        now: Instant,
+    ) -> bool {
+        self.transaction_depth += 1;
+        if self.transaction_depth == 1 {
+            if let Some(transaction) = self.undo_stack.last_mut() {
+                if now - transaction.last_edit_at <= self.group_interval {
+                    transaction.last_edit_at = now;
+                } else {
+                    self.undo_stack.push(Transaction {
+                        edits: Vec::new(),
+                        selections: selections.map(|(set_id, selections)| SelectionSetSnapshot {
+                            set_id,
+                            before_transaction: selections.clone(),
+                            after_transaction: selections,
+                        }),
+                        last_edit_at: now,
+                    });
+                }
             } else {
-                self.undo_stack.push(EditGroup {
-                    edits: vec![edit_id],
+                self.undo_stack.push(Transaction {
+                    edits: Vec::new(),
+                    selections: selections.map(|(set_id, selections)| SelectionSetSnapshot {
+                        set_id,
+                        before_transaction: selections.clone(),
+                        after_transaction: selections,
+                    }),
                     last_edit_at: now,
                 });
             }
+            true
         } else {
-            self.undo_stack.push(EditGroup {
-                edits: vec![edit_id],
-                last_edit_at: now,
-            });
+            false
+        }
+    }
+
+    fn end_transaction(
+        &mut self,
+        selections: Option<(SelectionSetId, Arc<[Selection]>)>,
+        now: Instant,
+    ) -> bool {
+        assert_ne!(self.transaction_depth, 0);
+        self.transaction_depth -= 1;
+        if self.transaction_depth == 0 {
+            let transaction = self.undo_stack.last_mut().unwrap();
+            if let Some((set_id, selections)) = selections {
+                if let Some(transaction_selections) = &mut transaction.selections {
+                    assert_eq!(set_id, transaction_selections.set_id);
+                    transaction_selections.after_transaction = selections;
+                }
+            }
+            transaction.last_edit_at = now;
+            true
+        } else {
+            false
         }
     }
 
-    fn pop_undo(&mut self) -> Option<&EditGroup> {
-        if let Some(edit_group) = self.undo_stack.pop() {
-            self.redo_stack.push(edit_group);
+    fn push_undo(&mut self, edit_id: time::Local) {
+        assert_ne!(self.transaction_depth, 0);
+        self.undo_stack.last_mut().unwrap().edits.push(edit_id);
+    }
+
+    fn pop_undo(&mut self) -> Option<&Transaction> {
+        assert_eq!(self.transaction_depth, 0);
+        if let Some(transaction) = self.undo_stack.pop() {
+            self.redo_stack.push(transaction);
             self.redo_stack.last()
         } else {
             None
         }
     }
 
-    fn pop_redo(&mut self) -> Option<&EditGroup> {
-        if let Some(edit_group) = self.redo_stack.pop() {
-            self.undo_stack.push(edit_group);
+    fn pop_redo(&mut self) -> Option<&Transaction> {
+        assert_eq!(self.transaction_depth, 0);
+        if let Some(transaction) = self.redo_stack.pop() {
+            self.undo_stack.push(transaction);
             self.undo_stack.last()
         } else {
             None
@@ -274,7 +339,7 @@ pub enum Operation {
     },
     UpdateSelections {
         set_id: SelectionSetId,
-        selections: Option<Vec<Selection>>,
+        selections: Option<Arc<[Selection]>>,
         lamport_timestamp: time::Lamport,
     },
 }
@@ -364,6 +429,7 @@ impl Buffer {
             last_edit: time::Local::default(),
             undo_map: Default::default(),
             history,
+            pending_transaction: None,
             selections: HashMap::default(),
             selections_last_update: 0,
             deferred_ops: OperationQueue::new(),
@@ -545,6 +611,68 @@ impl Buffer {
         self.deferred_ops.len()
     }
 
+    pub fn start_transaction(&mut self, set_id: Option<SelectionSetId>) -> Result<()> {
+        self.start_transaction_at(set_id, Instant::now())
+    }
+
+    fn start_transaction_at(&mut self, set_id: Option<SelectionSetId>, now: Instant) -> Result<()> {
+        let selections = if let Some(set_id) = set_id {
+            let selections = self
+                .selections
+                .get(&set_id)
+                .ok_or_else(|| anyhow!("invalid selection set {:?}", set_id))?;
+            Some((set_id, selections.clone()))
+        } else {
+            None
+        };
+        if self.history.start_transaction(selections, now) {
+            self.pending_transaction = Some(PendingTransaction {
+                start: self.version.clone(),
+                buffer_was_dirty: self.is_dirty(),
+            });
+        }
+        Ok(())
+    }
+
+    pub fn end_transaction(
+        &mut self,
+        set_id: Option<SelectionSetId>,
+        ctx: Option<&mut ModelContext<Self>>,
+    ) -> Result<()> {
+        self.end_transaction_at(set_id, Instant::now(), ctx)
+    }
+
+    fn end_transaction_at(
+        &mut self,
+        set_id: Option<SelectionSetId>,
+        now: Instant,
+        ctx: Option<&mut ModelContext<Self>>,
+    ) -> Result<()> {
+        let selections = if let Some(set_id) = set_id {
+            let selections = self
+                .selections
+                .get(&set_id)
+                .ok_or_else(|| anyhow!("invalid selection set {:?}", set_id))?;
+            Some((set_id, selections.clone()))
+        } else {
+            None
+        };
+
+        if self.history.end_transaction(selections, now) {
+            if let Some(ctx) = ctx {
+                ctx.notify();
+
+                let transaction = self.pending_transaction.take().unwrap();
+                let changes = self.edits_since(transaction.start).collect::<Vec<_>>();
+                if !changes.is_empty() {
+                    self.did_edit(changes, transaction.buffer_was_dirty, ctx);
+                }
+            }
+        }
+
+        Ok(())
+    }
+
     pub fn edit<I, S, T>(
         &mut self,
         old_ranges: I,
@@ -571,6 +699,8 @@ impl Buffer {
         S: ToOffset,
         T: Into<Text>,
     {
+        self.start_transaction_at(None, now)?;
+
         let new_text = new_text.into();
         let new_text = if new_text.len() > 0 {
             Some(new_text)
@@ -578,8 +708,6 @@ impl Buffer {
             None
         };
 
-        let was_dirty = self.is_dirty();
-        let old_version = self.version.clone();
         let old_ranges = old_ranges
             .into_iter()
             .map(|range| Ok(range.start.to_offset(self)?..range.end.to_offset(self)?))
@@ -595,19 +723,11 @@ impl Buffer {
         for op in &ops {
             if let Operation::Edit { edit, .. } = op {
                 self.history.push(edit.clone());
-                self.history.push_undo(edit.id, now);
+                self.history.push_undo(edit.id);
             }
         }
 
         if let Some(op) = ops.last() {
-            if let Some(ctx) = ctx {
-                ctx.notify();
-                let changes = self.edits_since(old_version).collect::<Vec<_>>();
-                if !changes.is_empty() {
-                    self.did_edit(changes, was_dirty, ctx);
-                }
-            }
-
             if let Operation::Edit { edit, .. } = op {
                 self.last_edit = edit.id;
                 self.version.observe(edit.id);
@@ -616,6 +736,8 @@ impl Buffer {
             }
         }
 
+        self.end_transaction_at(None, now, ctx)?;
+
         Ok(ops)
     }
 
@@ -675,9 +797,10 @@ impl Buffer {
         selections: Vec<Selection>,
         ctx: Option<&mut ModelContext<Self>>,
     ) -> (SelectionSetId, Operation) {
+        let selections = Arc::from(selections);
         let lamport_timestamp = self.lamport_clock.tick();
         self.selections
-            .insert(lamport_timestamp, selections.clone());
+            .insert(lamport_timestamp, Arc::clone(&selections));
         self.selections_last_update += 1;
 
         if let Some(ctx) = ctx {
@@ -701,7 +824,8 @@ impl Buffer {
         ctx: Option<&mut ModelContext<Self>>,
     ) -> Result<Operation> {
         self.merge_selections(&mut selections);
-        self.selections.insert(set_id, selections.clone());
+        let selections = Arc::from(selections);
+        self.selections.insert(set_id, Arc::clone(&selections));
 
         let lamport_timestamp = self.lamport_clock.tick();
         self.selections_last_update += 1;
@@ -742,7 +866,7 @@ impl Buffer {
     pub fn selections(&self, set_id: SelectionSetId) -> Result<&[Selection]> {
         self.selections
             .get(&set_id)
-            .map(|s| s.as_slice())
+            .map(|s| s.as_ref())
             .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))
     }
 
@@ -761,8 +885,10 @@ impl Buffer {
         }))
     }
 
-    pub fn all_selections(&self) -> impl Iterator<Item = (&SelectionSetId, &Vec<Selection>)> {
-        self.selections.iter()
+    pub fn all_selections(&self) -> impl Iterator<Item = (&SelectionSetId, &[Selection])> {
+        self.selections
+            .iter()
+            .map(|(set_id, selections)| (set_id, selections.as_ref()))
     }
 
     pub fn all_selection_ranges<'a>(
@@ -998,10 +1124,17 @@ impl Buffer {
         let old_version = self.version.clone();
 
         let mut ops = Vec::new();
-        if let Some(edit_group) = self.history.pop_undo() {
-            for edit_id in edit_group.edits.clone() {
+        if let Some(transaction) = self.history.pop_undo() {
+            let transaction_selections = transaction.selections.clone();
+            for edit_id in transaction.edits.clone() {
                 ops.push(self.undo_or_redo(edit_id).unwrap());
             }
+
+            if let Some(transaction_selections) = transaction_selections {
+                if let Some(selections) = self.selections.get_mut(&transaction_selections.set_id) {
+                    *selections = transaction_selections.before_transaction;
+                }
+            }
         }
 
         if let Some(ctx) = ctx {
@@ -1020,10 +1153,17 @@ impl Buffer {
         let old_version = self.version.clone();
 
         let mut ops = Vec::new();
-        if let Some(edit_group) = self.history.pop_redo() {
-            for edit_id in edit_group.edits.clone() {
+        if let Some(transaction) = self.history.pop_redo() {
+            let transaction_selections = transaction.selections.clone();
+            for edit_id in transaction.edits.clone() {
                 ops.push(self.undo_or_redo(edit_id).unwrap());
             }
+
+            if let Some(transaction_selections) = transaction_selections {
+                if let Some(selections) = self.selections.get_mut(&transaction_selections.set_id) {
+                    *selections = transaction_selections.after_transaction;
+                }
+            }
         }
 
         if let Some(ctx) = ctx {
@@ -1686,6 +1826,7 @@ impl Clone for Buffer {
             last_edit: self.last_edit.clone(),
             undo_map: self.undo_map.clone(),
             history: self.history.clone(),
+            pending_transaction: self.pending_transaction.clone(),
             selections: self.selections.clone(),
             selections_last_update: self.selections_last_update.clone(),
             deferred_ops: self.deferred_ops.clone(),

zed/src/editor/buffer_view.rs 🔗

@@ -379,6 +379,7 @@ impl BufferView {
             }
         }
 
+        self.start_transaction(ctx);
         let mut new_selections = Vec::new();
         self.buffer.update(ctx, |buffer, ctx| {
             if let Err(error) = buffer.edit(offset_ranges.iter().cloned(), text.as_str(), Some(ctx))
@@ -408,8 +409,7 @@ impl BufferView {
         });
 
         self.update_selections(new_selections, ctx);
-        self.pause_cursor_blinking(ctx);
-        *self.autoscroll_requested.lock() = true;
+        self.end_transaction(ctx);
     }
 
     fn newline(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
@@ -421,6 +421,7 @@ impl BufferView {
     }
 
     pub fn backspace(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
+        self.start_transaction(ctx);
         let mut selections = self.selections(ctx.app()).to_vec();
         {
             let buffer = self.buffer.as_ref(ctx);
@@ -444,6 +445,7 @@ impl BufferView {
         self.update_selections(selections, ctx);
         self.changed_selections(ctx);
         self.insert(&String::new(), ctx);
+        self.end_transaction(ctx);
     }
 
     pub fn undo(&mut self, _: &(), ctx: &mut ViewContext<Self>) {
@@ -727,6 +729,22 @@ impl BufferView {
         });
     }
 
+    fn start_transaction(&self, ctx: &mut ViewContext<Self>) {
+        self.buffer.update(ctx, |buffer, _| {
+            buffer
+                .start_transaction(Some(self.selection_set_id))
+                .unwrap()
+        });
+    }
+
+    fn end_transaction(&self, ctx: &mut ViewContext<Self>) {
+        self.buffer.update(ctx, |buffer, ctx| {
+            buffer
+                .end_transaction(Some(self.selection_set_id), Some(ctx))
+                .unwrap()
+        });
+    }
+
     pub fn page_up(&mut self, _: &(), _: &mut ViewContext<Self>) {
         log::info!("BufferView::page_up");
     }