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..ff1b241e73 --- /dev/null +++ b/crates/text/src/undo_map.rs @@ -0,0 +1,112 @@ +use crate::UndoOperation; +use std::cmp; +use sum_tree::{Bias, SumTree}; + +#[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 + } +}