Merge `UndoHistory` and `History`, storing also operations in the latter

Antonio Scandurra created

Change summary

zed/src/editor/buffer/mod.rs  | 144 ++++++++++++++++++------------------
zed/src/editor/buffer/text.rs |  20 +++-
zed/src/worktree/worktree.rs  |   4 
3 files changed, 87 insertions(+), 81 deletions(-)

Detailed changes

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

@@ -63,12 +63,11 @@ pub struct Buffer {
     file: Option<FileHandle>,
     fragments: SumTree<Fragment>,
     insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
-    edit_ops: HashMap<time::Local, EditOperation>,
     pub version: time::Global,
     saved_version: time::Global,
     last_edit: time::Local,
     undo_map: UndoMap,
-    undo_history: UndoHistory,
+    history: History,
     selections: HashMap<SelectionSetId, Vec<Selection>>,
     pub selections_last_update: SelectionsVersion,
     deferred_ops: OperationQueue<Operation>,
@@ -82,54 +81,6 @@ pub struct Snapshot {
     fragments: SumTree<Fragment>,
 }
 
-#[derive(Clone)]
-pub struct History {
-    pub base_text: String,
-}
-
-#[derive(Clone, Debug, Eq, PartialEq)]
-pub struct Selection {
-    pub start: Anchor,
-    pub end: Anchor,
-    pub reversed: bool,
-}
-
-#[derive(Clone, Default, Debug)]
-struct UndoMap(HashMap<time::Local, Vec<UndoOperation>>);
-
-impl UndoMap {
-    fn insert(&mut self, undo: UndoOperation) {
-        self.0.entry(undo.edit_id).or_default().push(undo);
-    }
-
-    fn is_undone(&self, edit_id: time::Local) -> bool {
-        self.undo_count(edit_id) % 2 == 1
-    }
-
-    fn was_undone(&self, edit_id: time::Local, version: &time::Global) -> bool {
-        let undo_count = self
-            .0
-            .get(&edit_id)
-            .unwrap_or(&Vec::new())
-            .iter()
-            .filter(|undo| version.observed(undo.id))
-            .map(|undo| undo.count)
-            .max()
-            .unwrap_or(0);
-        undo_count % 2 == 1
-    }
-
-    fn undo_count(&self, edit_id: time::Local) -> u32 {
-        self.0
-            .get(&edit_id)
-            .unwrap_or(&Vec::new())
-            .iter()
-            .map(|undo| undo.count)
-            .max()
-            .unwrap_or(0)
-    }
-}
-
 #[derive(Clone)]
 struct EditGroup {
     edits: Vec<time::Local>,
@@ -137,22 +88,30 @@ struct EditGroup {
 }
 
 #[derive(Clone)]
-struct UndoHistory {
-    group_interval: Duration,
+pub struct History {
+    pub base_text: Arc<str>,
+    ops: HashMap<time::Local, EditOperation>,
     undo_stack: Vec<EditGroup>,
     redo_stack: Vec<EditGroup>,
+    group_interval: Duration,
 }
 
-impl UndoHistory {
-    fn new(group_interval: Duration) -> Self {
+impl History {
+    pub fn new(base_text: Arc<str>) -> Self {
         Self {
-            group_interval,
+            base_text,
+            ops: Default::default(),
             undo_stack: Vec::new(),
             redo_stack: Vec::new(),
+            group_interval: UNDO_GROUP_INTERVAL,
         }
     }
 
-    fn push(&mut self, edit_id: time::Local, now: Instant) {
+    fn push(&mut self, op: EditOperation) {
+        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);
@@ -190,6 +149,49 @@ impl UndoHistory {
     }
 }
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct Selection {
+    pub start: Anchor,
+    pub end: Anchor,
+    pub reversed: bool,
+}
+
+#[derive(Clone, Default, Debug)]
+struct UndoMap(HashMap<time::Local, Vec<UndoOperation>>);
+
+impl UndoMap {
+    fn insert(&mut self, undo: UndoOperation) {
+        self.0.entry(undo.edit_id).or_default().push(undo);
+    }
+
+    fn is_undone(&self, edit_id: time::Local) -> bool {
+        self.undo_count(edit_id) % 2 == 1
+    }
+
+    fn was_undone(&self, edit_id: time::Local, version: &time::Global) -> bool {
+        let undo_count = self
+            .0
+            .get(&edit_id)
+            .unwrap_or(&Vec::new())
+            .iter()
+            .filter(|undo| version.observed(undo.id))
+            .map(|undo| undo.count)
+            .max()
+            .unwrap_or(0);
+        undo_count % 2 == 1
+    }
+
+    fn undo_count(&self, edit_id: time::Local) -> u32 {
+        self.0
+            .get(&edit_id)
+            .unwrap_or(&Vec::new())
+            .iter()
+            .map(|undo| undo.count)
+            .max()
+            .unwrap_or(0)
+    }
+}
+
 #[derive(Clone)]
 pub struct CharIter<'a> {
     fragments_cursor: Cursor<'a, Fragment, usize, usize>,
@@ -305,15 +307,15 @@ pub struct UndoOperation {
 }
 
 impl Buffer {
-    pub fn new<T: Into<String>>(replica_id: ReplicaId, base_text: T) -> Self {
-        Self::build(replica_id, None, base_text.into())
+    pub fn new<T: Into<Arc<str>>>(replica_id: ReplicaId, base_text: T) -> Self {
+        Self::build(replica_id, None, History::new(base_text.into()))
     }
 
     pub fn from_history(replica_id: ReplicaId, file: FileHandle, history: History) -> Self {
-        Self::build(replica_id, Some(file), history.base_text)
+        Self::build(replica_id, Some(file), history)
     }
 
-    fn build(replica_id: ReplicaId, file: Option<FileHandle>, base_text: String) -> Self {
+    fn build(replica_id: ReplicaId, file: Option<FileHandle>, history: History) -> Self {
         let mut insertion_splits = HashMap::default();
         let mut fragments = SumTree::new();
 
@@ -321,7 +323,7 @@ impl Buffer {
             id: time::Local::default(),
             parent_id: time::Local::default(),
             offset_in_parent: 0,
-            text: base_text.into(),
+            text: history.base_text.clone().into(),
             lamport_timestamp: time::Lamport::default(),
         };
 
@@ -366,12 +368,11 @@ impl Buffer {
             file,
             fragments,
             insertion_splits,
-            edit_ops: HashMap::default(),
             version: time::Global::new(),
             saved_version: time::Global::new(),
             last_edit: time::Local::default(),
             undo_map: Default::default(),
-            undo_history: UndoHistory::new(UNDO_GROUP_INTERVAL),
+            history,
             selections: HashMap::default(),
             selections_last_update: 0,
             deferred_ops: OperationQueue::new(),
@@ -602,8 +603,8 @@ impl Buffer {
 
         for op in &ops {
             if let Operation::Edit { edit, .. } = op {
-                self.edit_ops.insert(edit.id, edit.clone());
-                self.undo_history.push(edit.id, now);
+                self.history.push(edit.clone());
+                self.history.push_undo(edit.id, now);
             }
         }
 
@@ -864,7 +865,7 @@ impl Buffer {
                         lamport_timestamp,
                     )?;
                     self.version.observe(edit.id);
-                    self.edit_ops.insert(edit.id, edit);
+                    self.history.push(edit);
                 }
             }
             Operation::Undo {
@@ -1017,7 +1018,7 @@ impl Buffer {
         let old_version = self.version.clone();
 
         let mut ops = Vec::new();
-        if let Some(edit_group) = self.undo_history.pop_undo() {
+        if let Some(edit_group) = self.history.pop_undo() {
             for edit_id in edit_group.edits.clone() {
                 ops.push(self.undo_or_redo(edit_id).unwrap());
             }
@@ -1039,7 +1040,7 @@ impl Buffer {
         let old_version = self.version.clone();
 
         let mut ops = Vec::new();
-        if let Some(edit_group) = self.undo_history.pop_redo() {
+        if let Some(edit_group) = self.history.pop_redo() {
             for edit_id in edit_group.edits.clone() {
                 ops.push(self.undo_or_redo(edit_id).unwrap());
             }
@@ -1075,7 +1076,7 @@ impl Buffer {
         let mut new_fragments;
 
         self.undo_map.insert(undo);
-        let edit = &self.edit_ops[&undo.edit_id];
+        let edit = &self.history.ops[&undo.edit_id];
         let start_fragment_id = self.resolve_fragment_id(edit.start_id, edit.start_offset)?;
         let end_fragment_id = self.resolve_fragment_id(edit.end_id, edit.end_offset)?;
         let mut cursor = self.fragments.cursor::<FragmentIdRef, ()>();
@@ -1700,12 +1701,11 @@ impl Clone for Buffer {
             file: self.file.clone(),
             fragments: self.fragments.clone(),
             insertion_splits: self.insertion_splits.clone(),
-            edit_ops: self.edit_ops.clone(),
             version: self.version.clone(),
             saved_version: self.saved_version.clone(),
             last_edit: self.last_edit.clone(),
             undo_map: self.undo_map.clone(),
-            undo_history: self.undo_history.clone(),
+            history: self.history.clone(),
             selections: self.selections.clone(),
             selections_last_update: self.selections_last_update.clone(),
             deferred_ops: self.deferred_ops.clone(),
@@ -3076,7 +3076,7 @@ mod tests {
         pub fn randomly_undo_redo(&mut self, rng: &mut impl Rng) -> Vec<Operation> {
             let mut ops = Vec::new();
             for _ in 0..rng.gen_range(1..5) {
-                if let Some(edit_id) = self.edit_ops.keys().choose(rng).copied() {
+                if let Some(edit_id) = self.history.ops.keys().choose(rng).copied() {
                     ops.push(self.undo_or_redo(edit_id).unwrap());
                 }
             }

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

@@ -117,6 +117,18 @@ pub struct Text {
 
 impl From<String> for Text {
     fn from(text: String) -> Self {
+        Self::from(Arc::from(text))
+    }
+}
+
+impl<'a> From<&'a str> for Text {
+    fn from(text: &'a str) -> Self {
+        Self::from(Arc::from(text))
+    }
+}
+
+impl From<Arc<str>> for Text {
+    fn from(text: Arc<str>) -> Self {
         let mut runs = Vec::new();
 
         let mut chars_len = 0;
@@ -147,19 +159,13 @@ impl From<String> for Text {
         let mut tree = SumTree::new();
         tree.extend(runs);
         Text {
-            text: text.into(),
+            text,
             runs: tree,
             range: 0..chars_len,
         }
     }
 }
 
-impl<'a> From<&'a str> for Text {
-    fn from(text: &'a str) -> Self {
-        Self::from(String::from(text))
-    }
-}
-
 impl Debug for Text {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         f.debug_tuple("Text").field(&self.as_str()).finish()

zed/src/worktree/worktree.rs 🔗

@@ -345,7 +345,7 @@ impl Worktree {
             let mut file = smol::fs::File::open(&path).await?;
             let mut base_text = String::new();
             file.read_to_string(&mut base_text).await?;
-            let history = History { base_text };
+            let history = History::new(Arc::from(base_text));
             tree.0.write().histories.insert(entry_id, history.clone());
             Ok(history)
         }
@@ -717,7 +717,7 @@ mod test {
                 .await
                 .unwrap();
 
-            assert_eq!(history.base_text, buffer.text());
+            assert_eq!(history.base_text.as_ref(), buffer.text());
         })
     }
 }