From 7c3421e041195f26b00f9342616a7bdb97baffd5 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 22 Jul 2022 16:05:30 -0700 Subject: [PATCH] Remove versioned offset ranges from transactions and undo operations Now, instead of using these versioned offset ranges, we locate the fragments associated with a transaction using the transaction's edit ids. To make this possible, buffers now store a new map called `insertion_slices`, which lets you look up the ranges of insertions that were affected by a given edit. Co-authored-by: Antonio Scandurra --- crates/language/src/proto.rs | 16 -- crates/rpc/proto/zed.proto | 2 - crates/text/src/text.rs | 285 ++++++++++++++++------------------- 3 files changed, 131 insertions(+), 172 deletions(-) diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 5cb3feb2145bd8eba0e1308171bafaa14bbb13eb..c7c22c55c9a6ad731ee45644747b293b24d5ab06 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -39,11 +39,6 @@ pub fn serialize_operation(operation: &Operation) -> proto::Operation { local_timestamp: undo.id.value, lamport_timestamp: lamport_timestamp.value, version: serialize_version(&undo.version), - transaction_ranges: undo - .transaction_ranges - .iter() - .map(serialize_range) - .collect(), transaction_version: serialize_version(&undo.transaction_version), counts: undo .counts @@ -204,11 +199,6 @@ pub fn deserialize_operation(message: proto::Operation) -> Result { ) }) .collect(), - transaction_ranges: undo - .transaction_ranges - .into_iter() - .map(deserialize_range) - .collect(), transaction_version: deserialize_version(undo.transaction_version), }, }), @@ -461,7 +451,6 @@ pub fn serialize_transaction(transaction: &Transaction) -> proto::Transaction { .collect(), start: serialize_version(&transaction.start), end: serialize_version(&transaction.end), - ranges: transaction.ranges.iter().map(serialize_range).collect(), } } @@ -479,11 +468,6 @@ pub fn deserialize_transaction(transaction: proto::Transaction) -> Result, pub start: clock::Global, pub end: clock::Global, - pub ranges: Vec>, } #[derive(Clone, Copy, Debug, PartialEq)] @@ -114,71 +113,32 @@ impl HistoryEntry { self.transaction .end .observe(edit_operation.timestamp.local()); - - let mut edits = edit_operation - .ranges - .iter() - .zip(edit_operation.new_text.iter()) - .peekable(); - let mut new_ranges = Vec::new(); - let mut delta = 0; - - for mut self_range in self.transaction.ranges.iter().cloned() { - self_range.start += delta; - self_range.end += delta; - - while let Some((other_range, new_text)) = edits.peek() { - let insertion_len = new_text.len(); - let mut other_range = (*other_range).clone(); - other_range.start += delta; - other_range.end += delta; - - if other_range.start <= self_range.end { - edits.next().unwrap(); - delta += insertion_len; - - if other_range.end < self_range.start { - new_ranges.push(other_range.start..other_range.end + insertion_len); - self_range.start += insertion_len; - self_range.end += insertion_len; - } else { - self_range.start = cmp::min(self_range.start, other_range.start); - self_range.end = cmp::max(self_range.end, other_range.end) + insertion_len; - } - } else { - break; - } - } - - new_ranges.push(self_range); - } - - for (other_range, new_text) in edits { - let insertion_len = new_text.len(); - new_ranges.push(other_range.start + delta..other_range.end + delta + insertion_len); - delta += insertion_len; - } - - self.transaction.ranges = new_ranges; } } -#[derive(Clone)] struct History { // TODO: Turn this into a String or Rope, maybe. base_text: Arc, operations: HashMap, + insertion_slices: HashMap>, undo_stack: Vec, redo_stack: Vec, transaction_depth: usize, group_interval: Duration, } +#[derive(Clone, Debug)] +struct InsertionSlice { + insertion_id: clock::Local, + range: Range, +} + impl History { pub fn new(base_text: Arc) -> Self { Self { base_text, operations: Default::default(), + insertion_slices: Default::default(), undo_stack: Vec::new(), redo_stack: Vec::new(), transaction_depth: 0, @@ -205,7 +165,6 @@ impl History { start: start.clone(), end: start, edit_ids: Default::default(), - ranges: Default::default(), }, first_edit_at: now, last_edit_at: now, @@ -226,7 +185,7 @@ impl History { .last() .unwrap() .transaction - .ranges + .edit_ids .is_empty() { self.undo_stack.pop(); @@ -549,7 +508,6 @@ pub struct EditOperation { pub struct UndoOperation { pub id: clock::Local, pub counts: HashMap, - pub transaction_ranges: Vec>, pub transaction_version: clock::Global, pub version: clock::Global, } @@ -679,6 +637,7 @@ impl Buffer { }; let mut new_insertions = Vec::new(); let mut insertion_offset = 0; + let mut insertion_slices = Vec::new(); let mut edits = edits .map(|(range, new_text)| (range.to_offset(&*self), new_text)) @@ -737,10 +696,6 @@ impl Buffer { if !new_text.is_empty() { let new_start = new_fragments.summary().text.visible; - edits_patch.push(Edit { - old: fragment_start..fragment_start, - new: new_start..new_start + new_text.len(), - }); let fragment = Fragment { id: Locator::between( &new_fragments.summary().max_id, @@ -755,6 +710,11 @@ impl Buffer { max_undos: Default::default(), visible: true, }; + edits_patch.push(Edit { + old: fragment_start..fragment_start, + new: new_start..new_start + new_text.len(), + }); + insertion_slices.push(fragment.insertion_slice()); new_insertions.push(InsertionFragment::insert_new(&fragment)); new_ropes.push_str(new_text.as_ref()); new_fragments.push(fragment, &None); @@ -783,6 +743,7 @@ impl Buffer { old: fragment_start..intersection_end, new: new_start..new_start, }); + insertion_slices.push(intersection.insertion_slice()); } new_insertions.push(InsertionFragment::insert_new(&intersection)); new_ropes.push_fragment(&intersection, fragment.visible); @@ -825,6 +786,9 @@ impl Buffer { self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; self.subscriptions.publish_mut(&edits_patch); + self.history + .insertion_slices + .insert(timestamp.local(), insertion_slices); edit_op } @@ -894,6 +858,7 @@ impl Buffer { let edits = ranges.into_iter().zip(new_text.into_iter()); let mut edits_patch = Patch::default(); + let mut insertion_slices = Vec::new(); let cx = Some(version.clone()); let mut new_insertions = Vec::new(); let mut insertion_offset = 0; @@ -984,10 +949,6 @@ impl Buffer { old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; } let new_start = new_fragments.summary().text.visible; - edits_patch.push(Edit { - old: old_start..old_start, - new: new_start..new_start + new_text.len(), - }); let fragment = Fragment { id: Locator::between( &new_fragments.summary().max_id, @@ -1002,6 +963,11 @@ impl Buffer { max_undos: Default::default(), visible: true, }; + edits_patch.push(Edit { + old: old_start..old_start, + new: new_start..new_start + new_text.len(), + }); + insertion_slices.push(fragment.insertion_slice()); new_insertions.push(InsertionFragment::insert_new(&fragment)); new_ropes.push_str(new_text); new_fragments.push(fragment, &None); @@ -1023,6 +989,7 @@ impl Buffer { Locator::between(&new_fragments.summary().max_id, &intersection.id); intersection.deletions.insert(timestamp.local()); intersection.visible = false; + insertion_slices.push(intersection.insertion_slice()); } if intersection.len > 0 { if fragment.visible && !intersection.visible { @@ -1070,90 +1037,105 @@ impl Buffer { self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; self.snapshot.insertions.edit(new_insertions, &()); + self.history + .insertion_slices + .insert(timestamp.local(), insertion_slices); self.subscriptions.publish_mut(&edits_patch) } - fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { - let mut edits = Patch::default(); - self.snapshot.undo_map.insert(undo); + fn fragment_ids_for_edits<'a>( + &'a self, + edit_ids: impl Iterator, + ) -> Vec<&'a Locator> { + // Get all of the insertion slices changed by the given edits. + let mut insertion_slices = Vec::new(); + for edit_id in edit_ids { + if let Some(slices) = self.history.insertion_slices.get(edit_id) { + insertion_slices.extend_from_slice(slices) + } + } + insertion_slices + .sort_unstable_by_key(|s| (s.insertion_id, s.range.start, Reverse(s.range.end))); - let mut cx = undo.transaction_version.clone(); - for edit_id in undo.counts.keys().copied() { - cx.observe(edit_id); + // Get all of the fragments corresponding to these insertion slices. + let mut fragment_ids = Vec::new(); + let mut insertions_cursor = self.insertions.cursor::(); + for insertion_slice in &insertion_slices { + if insertion_slice.insertion_id != insertions_cursor.start().timestamp + || insertion_slice.range.start > insertions_cursor.start().split_offset + { + insertions_cursor.seek_forward( + &InsertionFragmentKey { + timestamp: insertion_slice.insertion_id, + split_offset: insertion_slice.range.start, + }, + Bias::Left, + &(), + ); + } + while let Some(item) = insertions_cursor.item() { + if item.timestamp != insertion_slice.insertion_id + || item.split_offset >= insertion_slice.range.end + { + break; + } + fragment_ids.push(&item.fragment_id); + insertions_cursor.next(&()); + } } - let cx = Some(cx); + fragment_ids.sort_unstable(); + fragment_ids + } - let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); - let mut new_fragments = old_fragments.slice( - &VersionedFullOffset::Offset(undo.transaction_ranges[0].start), - Bias::Right, - &cx, - ); + fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { + self.snapshot.undo_map.insert(undo); + + let mut edits = Patch::default(); + let mut old_fragments = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let mut new_fragments = SumTree::new(); let mut new_ropes = RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); - new_ropes.push_tree(new_fragments.summary().text); - for range in &undo.transaction_ranges { - let mut end_offset = old_fragments.end(&cx).0.full_offset(); + let fragment_ids = self.fragment_ids_for_edits(undo.counts.keys()); + for fragment_id in fragment_ids { + let preceding_fragments = old_fragments.slice(&Some(fragment_id), Bias::Left, &None); + new_ropes.push_tree(preceding_fragments.summary().text); + new_fragments.push_tree(preceding_fragments, &None); - if end_offset < range.start { - let preceding_fragments = old_fragments.slice( - &VersionedFullOffset::Offset(range.start), - Bias::Right, - &cx, - ); - new_ropes.push_tree(preceding_fragments.summary().text); - new_fragments.push_tree(preceding_fragments, &None); - } + if let Some(fragment) = old_fragments.item() { + let mut fragment = fragment.clone(); + let fragment_was_visible = fragment.visible; - while end_offset <= range.end { - if let Some(fragment) = old_fragments.item() { - let mut fragment = fragment.clone(); - let fragment_was_visible = fragment.visible; - - if fragment.was_visible(&undo.transaction_version, &self.undo_map) - || undo - .counts - .contains_key(&fragment.insertion_timestamp.local()) - { - fragment.visible = fragment.is_visible(&self.undo_map); - fragment.max_undos.observe(undo.id); - } - - let old_start = old_fragments.start().1; - let new_start = new_fragments.summary().text.visible; - if fragment_was_visible && !fragment.visible { - edits.push(Edit { - old: old_start..old_start + fragment.len, - new: new_start..new_start, - }); - } else if !fragment_was_visible && fragment.visible { - edits.push(Edit { - old: old_start..old_start, - new: new_start..new_start + fragment.len, - }); - } - new_ropes.push_fragment(&fragment, fragment_was_visible); - new_fragments.push(fragment, &None); + if fragment.was_visible(&undo.transaction_version, &self.undo_map) + || undo + .counts + .contains_key(&fragment.insertion_timestamp.local()) + { + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.id); + } - old_fragments.next(&cx); - if end_offset == old_fragments.end(&cx).0.full_offset() { - let unseen_fragments = old_fragments.slice( - &VersionedFullOffset::Offset(end_offset), - Bias::Right, - &cx, - ); - new_ropes.push_tree(unseen_fragments.summary().text); - new_fragments.push_tree(unseen_fragments, &None); - } - end_offset = old_fragments.end(&cx).0.full_offset(); - } else { - break; + let old_start = old_fragments.start().1; + let new_start = new_fragments.summary().text.visible; + if fragment_was_visible && !fragment.visible { + edits.push(Edit { + old: old_start..old_start + fragment.len, + new: new_start..new_start, + }); + } else if !fragment_was_visible && fragment.visible { + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + fragment.len, + }); } + new_ropes.push_fragment(&fragment, fragment_was_visible); + new_fragments.push(fragment, &None); + + old_fragments.next(&None); } } - let suffix = old_fragments.suffix(&cx); + let suffix = old_fragments.suffix(&None); new_ropes.push_tree(suffix.summary().text); new_fragments.push_tree(suffix, &None); @@ -1304,7 +1286,6 @@ impl Buffer { id: self.local_clock.tick(), version: self.version(), counts, - transaction_ranges: transaction.ranges, transaction_version: transaction.start.clone(), }; self.apply_undo(&undo)?; @@ -1329,33 +1310,22 @@ impl Buffer { where D: TextDimension, { - let mut cursor = self.fragments.cursor::<(VersionedFullOffset, usize)>(); + let mut cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); let mut rope_cursor = self.visible_text.cursor(0); - let cx = Some(transaction.end.clone()); let mut position = D::default(); - transaction.ranges.iter().map(move |range| { - cursor.seek_forward(&VersionedFullOffset::Offset(range.start), Bias::Right, &cx); - let mut start_offset = cursor.start().1; - if cursor - .item() - .map_or(false, |fragment| fragment.is_visible(&self.undo_map)) - { - start_offset += range.start - cursor.start().0.full_offset() - } - position.add_assign(&rope_cursor.summary(start_offset)); - let start = position.clone(); - - cursor.seek_forward(&VersionedFullOffset::Offset(range.end), Bias::Left, &cx); - let mut end_offset = cursor.start().1; - if cursor - .item() - .map_or(false, |fragment| fragment.is_visible(&self.undo_map)) - { - end_offset += range.end - cursor.start().0.full_offset(); - } - position.add_assign(&rope_cursor.summary(end_offset)); - start..position.clone() - }) + self.fragment_ids_for_edits(transaction.edit_ids.iter()) + .into_iter() + .filter_map(move |fragment_id| { + cursor.seek_forward(&Some(fragment_id), Bias::Left, &None); + let fragment = cursor.item()?; + let start_offset = cursor.start().1; + let end_offset = start_offset + if fragment.visible { fragment.len } else { 0 }; + position.add_assign(&rope_cursor.summary(start_offset)); + let start = position.clone(); + position.add_assign(&rope_cursor.summary(end_offset)); + let end = position.clone(); + Some(start..end) + }) } pub fn subscribe(&mut self) -> Subscription { @@ -2100,6 +2070,13 @@ impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator fo } impl Fragment { + fn insertion_slice(&self) -> InsertionSlice { + InsertionSlice { + insertion_id: self.insertion_timestamp.local(), + range: self.insertion_offset..self.insertion_offset + self.len, + } + } + fn is_visible(&self, undos: &UndoMap) -> bool { !undos.is_undone(self.insertion_timestamp.local()) && self.deletions.iter().all(|d| undos.is_undone(*d))