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 <antonio@zed.dev>
This commit is contained in:
Max Brunsfeld 2022-07-22 16:05:30 -07:00
parent 65fd943509
commit 7c3421e041
3 changed files with 137 additions and 178 deletions

View File

@ -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<Operation> {
)
})
.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<Transa
.collect(),
start: deserialize_version(transaction.start.into()),
end: deserialize_version(transaction.end),
ranges: transaction
.ranges
.into_iter()
.map(deserialize_range)
.collect(),
})
}

View File

@ -535,7 +535,6 @@ message Transaction {
repeated LocalTimestamp edit_ids = 2;
repeated VectorClockEntry start = 3;
repeated VectorClockEntry end = 4;
repeated Range ranges = 5;
}
message LocalTimestamp {
@ -890,7 +889,6 @@ message Operation {
uint32 local_timestamp = 2;
uint32 lamport_timestamp = 3;
repeated VectorClockEntry version = 4;
repeated Range transaction_ranges = 5;
repeated VectorClockEntry transaction_version = 6;
repeated UndoCount counts = 7;
}

View File

@ -35,7 +35,7 @@ pub use rope::{Chunks, Rope, TextSummary};
pub use selection::*;
use std::{
borrow::Cow,
cmp::{self, Ordering},
cmp::{self, Ordering, Reverse},
future::Future,
iter::Iterator,
ops::{self, Deref, Range, Sub},
@ -93,7 +93,6 @@ pub struct Transaction {
pub edit_ids: Vec<clock::Local>,
pub start: clock::Global,
pub end: clock::Global,
pub ranges: Vec<Range<FullOffset>>,
}
#[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<str>,
operations: HashMap<clock::Local, Operation>,
insertion_slices: HashMap<clock::Local, Vec<InsertionSlice>>,
undo_stack: Vec<HistoryEntry>,
redo_stack: Vec<HistoryEntry>,
transaction_depth: usize,
group_interval: Duration,
}
#[derive(Clone, Debug)]
struct InsertionSlice {
insertion_id: clock::Local,
range: Range<usize>,
}
impl History {
pub fn new(base_text: Arc<str>) -> 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<clock::Local, u32>,
pub transaction_ranges: Vec<Range<FullOffset>>,
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);
let mut cx = undo.transaction_version.clone();
for edit_id in undo.counts.keys().copied() {
cx.observe(edit_id);
}
let cx = Some(cx);
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,
);
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();
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);
fn fragment_ids_for_edits<'a>(
&'a self,
edit_ids: impl Iterator<Item = &'a clock::Local>,
) -> 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)));
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);
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 {
// Get all of the fragments corresponding to these insertion slices.
let mut fragment_ids = Vec::new();
let mut insertions_cursor = self.insertions.cursor::<InsertionFragmentKey>();
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(&());
}
}
fragment_ids.sort_unstable();
fragment_ids
}
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));
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 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);
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))