From e17627b07d0d088e20bba6085ce7d4353844c3ae Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Sep 2022 11:24:45 +0200 Subject: [PATCH 1/2] Use a `SumTree` as the backing storage of `UndoMap` This makes it cheap to clone and avoids slowdowns when there the undo history is long. --- crates/text/src/text.rs | 49 +--------------- crates/text/src/undo_map.rs | 113 ++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 46 deletions(-) create mode 100644 crates/text/src/undo_map.rs diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 0648e6341a..d201f87443 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -14,6 +14,7 @@ mod selection; pub mod subscription; #[cfg(test)] mod tests; +mod undo_map; pub use anchor::*; use anyhow::Result; @@ -46,6 +47,7 @@ use std::{ pub use subscription::*; pub use sum_tree::Bias; use sum_tree::{FilterCursor, SumTree, TreeMap}; +use undo_map::UndoMap; lazy_static! { static ref CARRIAGE_RETURNS_REGEX: Regex = Regex::new("\r\n|\r").unwrap(); @@ -66,7 +68,7 @@ pub struct Buffer { version_barriers: Vec<(clock::Global, barrier::Sender)>, } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct BufferSnapshot { replica_id: ReplicaId, remote_id: u64, @@ -335,44 +337,6 @@ impl History { } } -#[derive(Clone, Default, Debug)] -struct UndoMap(HashMap>); - -impl UndoMap { - fn insert(&mut self, undo: &UndoOperation) { - for (edit_id, count) in &undo.counts { - self.0.entry(*edit_id).or_default().push((undo.id, *count)); - } - } - - fn is_undone(&self, edit_id: clock::Local) -> bool { - self.undo_count(edit_id) % 2 == 1 - } - - fn was_undone(&self, edit_id: clock::Local, version: &clock::Global) -> bool { - let undo_count = self - .0 - .get(&edit_id) - .unwrap_or(&Vec::new()) - .iter() - .filter(|(undo_id, _)| version.observed(*undo_id)) - .map(|(_, undo_count)| *undo_count) - .max() - .unwrap_or(0); - undo_count % 2 == 1 - } - - fn undo_count(&self, edit_id: clock::Local) -> u32 { - self.0 - .get(&edit_id) - .unwrap_or(&Vec::new()) - .iter() - .map(|(_, undo_count)| *undo_count) - .max() - .unwrap_or(0) - } -} - struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> { visible_cursor: rope::Cursor<'a>, deleted_cursor: rope::Cursor<'a>, @@ -1218,13 +1182,6 @@ impl Buffer { &self.history.operations } - pub fn undo_history(&self) -> impl Iterator { - self.undo_map - .0 - .iter() - .map(|(edit_id, undo_counts)| (edit_id, undo_counts.as_slice())) - } - pub fn undo(&mut self) -> Option<(TransactionId, Operation)> { if let Some(entry) = self.history.pop_undo() { let transaction = entry.transaction.clone(); diff --git a/crates/text/src/undo_map.rs b/crates/text/src/undo_map.rs new file mode 100644 index 0000000000..d015419667 --- /dev/null +++ b/crates/text/src/undo_map.rs @@ -0,0 +1,113 @@ +use std::cmp; +use sum_tree::{Bias, SumTree}; + +use crate::UndoOperation; + +#[derive(Copy, Clone, Debug)] +struct UndoMapEntry { + key: UndoMapKey, + undo_count: u32, +} + +impl sum_tree::Item for UndoMapEntry { + type Summary = UndoMapKey; + + fn summary(&self) -> Self::Summary { + self.key + } +} + +impl sum_tree::KeyedItem for UndoMapEntry { + type Key = UndoMapKey; + + fn key(&self) -> Self::Key { + self.key + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct UndoMapKey { + edit_id: clock::Local, + undo_id: clock::Local, +} + +impl sum_tree::Summary for UndoMapKey { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + *self = cmp::max(*self, *summary); + } +} + +#[derive(Clone, Default)] +pub struct UndoMap(SumTree); + +impl UndoMap { + pub fn insert(&mut self, undo: &UndoOperation) { + let edits = undo + .counts + .iter() + .map(|(edit_id, count)| { + sum_tree::Edit::Insert(UndoMapEntry { + key: UndoMapKey { + edit_id: *edit_id, + undo_id: undo.id, + }, + undo_count: *count, + }) + }) + .collect::>(); + self.0.edit(edits, &()); + } + + pub fn is_undone(&self, edit_id: clock::Local) -> bool { + self.undo_count(edit_id) % 2 == 1 + } + + pub fn was_undone(&self, edit_id: clock::Local, version: &clock::Global) -> bool { + let mut cursor = self.0.cursor::(); + cursor.seek( + &UndoMapKey { + edit_id, + undo_id: Default::default(), + }, + Bias::Left, + &(), + ); + + let mut undo_count = 0; + for entry in cursor { + if entry.key.edit_id != edit_id { + break; + } + + if version.observed(entry.key.undo_id) { + undo_count = cmp::max(undo_count, entry.undo_count); + } + } + + undo_count % 2 == 1 + } + + pub fn undo_count(&self, edit_id: clock::Local) -> u32 { + let mut cursor = self.0.cursor::(); + cursor.seek( + &UndoMapKey { + edit_id, + undo_id: Default::default(), + }, + Bias::Left, + &(), + ); + + let mut undo_count = 0; + for entry in cursor { + if entry.key.edit_id != edit_id { + break; + } + + undo_count = cmp::max(undo_count, entry.undo_count); + } + undo_count + } +} From f54c1f05c59ef5902be221095a4dafd800c7b985 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 28 Sep 2022 11:27:07 +0200 Subject: [PATCH 2/2] :lipstick: --- crates/text/src/undo_map.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/text/src/undo_map.rs b/crates/text/src/undo_map.rs index d015419667..ff1b241e73 100644 --- a/crates/text/src/undo_map.rs +++ b/crates/text/src/undo_map.rs @@ -1,8 +1,7 @@ +use crate::UndoOperation; use std::cmp; use sum_tree::{Bias, SumTree}; -use crate::UndoOperation; - #[derive(Copy, Clone, Debug)] struct UndoMapEntry { key: UndoMapKey,