@@ -810,3 +810,188 @@ fn test_random_concurrent_edits(mut rng: StdRng) {
buffer.check_invariants();
}
}
+
+#[test]
+fn test_new_normalized_splits_large_base_text() {
+ // ASCII text that exceeds max_insertion_len
+ let text = "abcdefghij".repeat(10); // 100 bytes
+ let rope = Rope::from(text.as_str());
+ let buffer = Buffer::new_normalized(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ LineEnding::Unix,
+ rope,
+ );
+ assert_eq!(buffer.text(), text);
+ buffer.check_invariants();
+
+ // Verify anchors at various positions, including across chunk boundaries
+ for offset in [0, 1, 15, 16, 17, 50, 99] {
+ let anchor = buffer.anchor_before(offset);
+ assert_eq!(
+ anchor.to_offset(&buffer),
+ offset,
+ "anchor_before({offset}) round-tripped incorrectly"
+ );
+ let anchor = buffer.anchor_after(offset);
+ assert_eq!(
+ anchor.to_offset(&buffer),
+ offset,
+ "anchor_after({offset}) round-tripped incorrectly"
+ );
+ }
+
+ // Verify editing works after a split initialization
+ let mut buffer = buffer;
+ buffer.edit([(50..60, "XYZ")]);
+ let mut expected = text;
+ expected.replace_range(50..60, "XYZ");
+ assert_eq!(buffer.text(), expected);
+ buffer.check_invariants();
+}
+
+#[test]
+fn test_new_normalized_splits_large_base_text_with_multibyte_chars() {
+ // Use multi-byte chars (é is 2 bytes in UTF-8) so that a naive byte-level
+ // split would land in the middle of a character.
+ let unit = "ééééééééé"; // 9 chars × 2 bytes = 18 bytes
+ let text = unit.repeat(6); // 108 bytes
+ let rope = Rope::from(text.as_str());
+ let buffer = Buffer::new_normalized(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ LineEnding::Unix,
+ rope,
+ );
+ assert_eq!(buffer.text(), text);
+ buffer.check_invariants();
+
+ // Every anchor should resolve correctly even though chunks had to be
+ // rounded down to a char boundary.
+ let snapshot = buffer.snapshot();
+ for offset in (0..text.len()).filter(|o| text.is_char_boundary(*o)) {
+ let anchor = snapshot.anchor_before(offset);
+ assert_eq!(
+ anchor.to_offset(snapshot),
+ offset,
+ "anchor round-trip failed at byte offset {offset}"
+ );
+ }
+}
+
+#[test]
+fn test_new_normalized_small_text_unchanged() {
+ // Text that fits in a single chunk should produce exactly one fragment,
+ // matching the original single-fragment behaviour.
+ let text = "hello world";
+ let rope = Rope::from(text);
+ let buffer = Buffer::new_normalized(
+ ReplicaId::LOCAL,
+ BufferId::new(1).unwrap(),
+ LineEnding::Unix,
+ rope,
+ );
+ assert_eq!(buffer.text(), text);
+ buffer.check_invariants();
+ assert_eq!(buffer.snapshot().fragments.items(&None).len(), 1);
+}
+
+#[test]
+fn test_edit_splits_large_insertion() {
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "abcdefghij");
+
+ let large_text: Arc<str> = "X".repeat(100).into();
+ let edits = vec![(3..7, large_text.clone())];
+
+ buffer.edit(edits);
+
+ let expected = format!("abc{}hij", large_text);
+ assert_eq!(buffer.text(), expected);
+ buffer.check_invariants();
+
+ // Anchors should resolve correctly throughout the buffer.
+ for offset in [0, 3, 50, 103, expected.len()] {
+ let anchor = buffer.anchor_before(offset);
+ assert_eq!(
+ anchor.to_offset(&buffer),
+ offset,
+ "anchor_before({offset}) round-tripped incorrectly"
+ );
+ }
+}
+
+#[test]
+fn test_edit_splits_large_insertion_with_multibyte_chars() {
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "abcdefghij");
+
+ // 4-byte chars so that naive byte splits would land mid-character.
+ let large_text: Arc<str> = "😀".repeat(30).into(); // 30 × 4 = 120 bytes
+ let edits = vec![(5..5, large_text.clone())];
+
+ buffer.edit(edits);
+
+ let expected = format!("abcde{}fghij", large_text);
+ assert_eq!(buffer.text(), expected);
+ buffer.check_invariants();
+}
+
+#[test]
+fn test_edit_splits_large_insertion_among_multiple_edits() {
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "ABCDEFGHIJ");
+
+ let large_text: Arc<str> = "x".repeat(60).into();
+ // Three edits: small, large, small. The large one must be split while
+ // preserving the correct positions of the surrounding edits.
+ let edits = vec![
+ (1..2, Arc::from("y")), // replace "B" with "y"
+ (4..6, large_text.clone()), // replace "EF" with 60 x's
+ (9..9, Arc::from("z")), // insert "z" before "J"
+ ];
+
+ buffer.edit(edits);
+
+ // Original: A B C D E F G H I J
+ // After (1..2, "y"): A y C D E F G H I J
+ // After (4..6, large): A y C D <60 x's> G H I J
+ // After (9..9, "z"): A y C D <60 x's> G H I z J
+ let expected = format!("AyCD{}GHIzJ", large_text);
+ assert_eq!(buffer.text(), expected);
+ buffer.check_invariants();
+}
+
+#[test]
+fn test_edit_splits_multiple_large_insertions() {
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "ABCDE");
+
+ let text1: Arc<str> = "a".repeat(40).into();
+ let text2: Arc<str> = "b".repeat(40).into();
+ let edits = vec![
+ (1..2, text1.clone()), // replace "B" with 40 a's
+ (3..4, text2.clone()), // replace "D" with 40 b's
+ ];
+
+ buffer.edit(edits);
+
+ let expected = format!("A{}C{}E", text1, text2);
+ assert_eq!(buffer.text(), expected);
+ buffer.check_invariants();
+}
+
+#[test]
+fn test_edit_undo_after_split() {
+ let mut buffer = Buffer::new(ReplicaId::LOCAL, BufferId::new(1).unwrap(), "hello world");
+ buffer.set_group_interval(Duration::from_secs(0));
+ let original = buffer.text();
+
+ let large_text: Arc<str> = "Z".repeat(50).into();
+ let edits = vec![(5..6, large_text)];
+ buffer.edit(edits);
+ assert_ne!(buffer.text(), original);
+ buffer.check_invariants();
+
+ // Undo should restore the original text even though the edit was split
+ // into multiple internal operations grouped in one transaction.
+ buffer.undo();
+ assert_eq!(buffer.text(), original);
+ buffer.check_invariants();
+}
@@ -48,6 +48,12 @@ use util::RandomCharIter;
static LINE_SEPARATORS_REGEX: LazyLock<Regex> =
LazyLock::new(|| Regex::new(r"\r\n|\r").expect("Failed to create LINE_SEPARATORS_REGEX"));
+/// The maximum length of a single insertion operation.
+/// Fragments larger than this will be split into multiple smaller
+/// fragments. This allows us to use relative `u32` offsets instead of `usize`,
+/// reducing memory usage.
+const MAX_INSERTION_LEN: usize = if cfg!(test) { 16 } else { u32::MAX as usize };
+
pub type TransactionId = clock::Lamport;
pub struct Buffer {
@@ -155,18 +161,38 @@ struct History {
#[derive(Clone, Debug, Eq, PartialEq)]
struct InsertionSlice {
- edit_id: clock::Lamport,
- insertion_id: clock::Lamport,
- range: Range<usize>,
+ // Inline the lamports to allow the replica ids to share the same alignment
+ // saving 4 bytes space edit_id: clock::Lamport,
+ edit_id_value: clock::Seq,
+ edit_id_replica_id: ReplicaId,
+ // insertion_id: clock::Lamport,
+ insertion_id_value: clock::Seq,
+ insertion_id_replica_id: ReplicaId,
+ range: Range<u32>,
}
impl Ord for InsertionSlice {
fn cmp(&self, other: &Self) -> Ordering {
- self.edit_id
- .cmp(&other.edit_id)
- .then_with(|| self.insertion_id.cmp(&other.insertion_id))
- .then_with(|| self.range.start.cmp(&other.range.start))
- .then_with(|| self.range.end.cmp(&other.range.end))
+ Lamport {
+ value: self.edit_id_value,
+ replica_id: self.edit_id_replica_id,
+ }
+ .cmp(&Lamport {
+ value: other.edit_id_value,
+ replica_id: other.edit_id_replica_id,
+ })
+ .then_with(|| {
+ Lamport {
+ value: self.insertion_id_value,
+ replica_id: self.insertion_id_replica_id,
+ }
+ .cmp(&Lamport {
+ value: other.insertion_id_value,
+ replica_id: other.insertion_id_replica_id,
+ })
+ })
+ .then_with(|| self.range.start.cmp(&other.range.start))
+ .then_with(|| self.range.end.cmp(&other.range.end))
}
}
@@ -179,8 +205,10 @@ impl PartialOrd for InsertionSlice {
impl InsertionSlice {
fn from_fragment(edit_id: clock::Lamport, fragment: &Fragment) -> Self {
Self {
- edit_id,
- insertion_id: fragment.timestamp,
+ edit_id_value: edit_id.value,
+ edit_id_replica_id: edit_id.replica_id,
+ insertion_id_value: fragment.timestamp.value,
+ insertion_id_replica_id: fragment.timestamp.replica_id,
range: fragment.insertion_offset..fragment.insertion_offset + fragment.len,
}
}
@@ -309,6 +337,7 @@ impl History {
fn finalize_last_transaction(&mut self) -> Option<&Transaction> {
self.undo_stack.last_mut().map(|entry| {
+ entry.transaction.edit_ids.shrink_to_fit();
entry.suppress_grouping = true;
&entry.transaction
})
@@ -489,7 +518,7 @@ struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> {
since: &'a clock::Global,
old_end: D,
new_end: D,
- range: Range<(&'a Locator, usize)>,
+ range: Range<(&'a Locator, u32)>,
buffer_id: BufferId,
}
@@ -536,18 +565,18 @@ impl<D1, D2> Edit<(D1, D2)> {
}
#[derive(Eq, PartialEq, Clone, Debug)]
-pub struct Fragment {
- pub id: Locator,
- pub timestamp: clock::Lamport,
- pub insertion_offset: usize,
- pub len: usize,
- pub visible: bool,
- pub deletions: SmallVec<[clock::Lamport; 2]>,
- pub max_undos: clock::Global,
+struct Fragment {
+ id: Locator,
+ timestamp: clock::Lamport,
+ insertion_offset: u32,
+ len: u32,
+ visible: bool,
+ deletions: SmallVec<[clock::Lamport; 2]>,
+ max_undos: clock::Global,
}
#[derive(Eq, PartialEq, Clone, Debug)]
-pub struct FragmentSummary {
+struct FragmentSummary {
text: FragmentTextSummary,
max_id: Locator,
max_version: clock::Global,
@@ -575,14 +604,14 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary {
#[derive(Eq, PartialEq, Clone, Debug)]
struct InsertionFragment {
timestamp: clock::Lamport,
- split_offset: usize,
+ split_offset: u32,
fragment_id: Locator,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
struct InsertionFragmentKey {
timestamp: clock::Lamport,
- split_offset: usize,
+ split_offset: u32,
}
#[derive(Clone, Debug, Eq, PartialEq)]
@@ -740,18 +769,37 @@ impl Buffer {
let insertion_timestamp = clock::Lamport::new(ReplicaId::LOCAL);
lamport_clock.observe(insertion_timestamp);
version.observe(insertion_timestamp);
- let fragment_id = Locator::between(&Locator::min(), &Locator::max());
- let fragment = Fragment {
- id: fragment_id,
- timestamp: insertion_timestamp,
- insertion_offset: 0,
- len: visible_text.len(),
- visible: true,
- deletions: Default::default(),
- max_undos: Default::default(),
- };
- insertions.push(InsertionFragment::new(&fragment), ());
- fragments.push(fragment, &None);
+
+ let mut insertion_offset: u32 = 0;
+ let mut text_offset: usize = 0;
+ let mut prev_locator = Locator::min();
+
+ while text_offset < visible_text.len() {
+ let target_end = visible_text.len().min(text_offset + MAX_INSERTION_LEN);
+ let chunk_end = if target_end == visible_text.len() {
+ target_end
+ } else {
+ visible_text.floor_char_boundary(target_end)
+ };
+ let chunk_len = chunk_end - text_offset;
+
+ let fragment_id = Locator::between(&prev_locator, &Locator::max());
+ let fragment = Fragment {
+ id: fragment_id.clone(),
+ timestamp: insertion_timestamp,
+ insertion_offset,
+ len: chunk_len as u32,
+ visible: true,
+ deletions: Default::default(),
+ max_undos: Default::default(),
+ };
+ insertions.push(InsertionFragment::new(&fragment), ());
+ fragments.push(fragment, &None);
+
+ prev_locator = fragment_id;
+ insertion_offset += chunk_len as u32;
+ text_offset = chunk_end;
+ }
}
Buffer {
@@ -853,7 +901,7 @@ impl Buffer {
new_text: Vec::with_capacity(edits.len()),
};
let mut new_insertions = Vec::new();
- let mut insertion_offset = 0;
+ let mut insertion_offset: u32 = 0;
let mut insertion_slices = Vec::new();
let mut edits = edits
@@ -879,8 +927,9 @@ impl Buffer {
if fragment_start > old_fragments.start().visible {
if fragment_end > fragment_start {
let mut suffix = old_fragments.item().unwrap().clone();
- suffix.len = fragment_end - fragment_start;
- suffix.insertion_offset += fragment_start - old_fragments.start().visible;
+ suffix.len = (fragment_end - fragment_start) as u32;
+ suffix.insertion_offset +=
+ (fragment_start - old_fragments.start().visible) as u32;
new_insertions.push(InsertionFragment::insert_new(&suffix));
new_ropes.push_fragment(&suffix, suffix.visible);
new_fragments.push(suffix, &None);
@@ -899,8 +948,8 @@ impl Buffer {
// Preserve any portion of the current fragment that precedes this range.
if fragment_start < range.start {
let mut prefix = old_fragments.item().unwrap().clone();
- prefix.len = range.start - fragment_start;
- prefix.insertion_offset += fragment_start - old_fragments.start().visible;
+ prefix.len = (range.start - fragment_start) as u32;
+ prefix.insertion_offset += (fragment_start - old_fragments.start().visible) as u32;
prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id);
new_insertions.push(InsertionFragment::insert_new(&prefix));
new_ropes.push_fragment(&prefix, prefix.visible);
@@ -912,29 +961,24 @@ impl Buffer {
if !new_text.is_empty() {
let new_start = new_fragments.summary().text.visible;
- let fragment = Fragment {
- id: Locator::between(
- &new_fragments.summary().max_id,
- old_fragments
- .item()
- .map_or(&Locator::max(), |old_fragment| &old_fragment.id),
- ),
+ let next_fragment_id = old_fragments
+ .item()
+ .map_or(Locator::max_ref(), |old_fragment| &old_fragment.id);
+ Self::push_fragments_for_insertion(
+ new_text.as_ref(),
timestamp,
- insertion_offset,
- len: new_text.len(),
- deletions: Default::default(),
- max_undos: Default::default(),
- visible: true,
- };
+ &mut insertion_offset,
+ &mut new_fragments,
+ &mut new_insertions,
+ &mut insertion_slices,
+ &mut new_ropes,
+ next_fragment_id,
+ timestamp,
+ );
edits_patch.push(Edit {
old: fragment_start..fragment_start,
new: new_start..new_start + new_text.len(),
});
- insertion_slices.push(InsertionSlice::from_fragment(timestamp, &fragment));
- new_insertions.push(InsertionFragment::insert_new(&fragment));
- new_ropes.push_str(new_text.as_ref());
- new_fragments.push(fragment, &None);
- insertion_offset += new_text.len();
}
// Advance through every fragment that intersects this range, marking the intersecting
@@ -945,8 +989,9 @@ impl Buffer {
let mut intersection = fragment.clone();
let intersection_end = cmp::min(range.end, fragment_end);
if fragment.visible {
- intersection.len = intersection_end - fragment_start;
- intersection.insertion_offset += fragment_start - old_fragments.start().visible;
+ intersection.len = (intersection_end - fragment_start) as u32;
+ intersection.insertion_offset +=
+ (fragment_start - old_fragments.start().visible) as u32;
intersection.id =
Locator::between(&new_fragments.summary().max_id, &intersection.id);
intersection.deletions.push(timestamp);
@@ -983,8 +1028,8 @@ impl Buffer {
let fragment_end = old_fragments.end().visible;
if fragment_end > fragment_start {
let mut suffix = old_fragments.item().unwrap().clone();
- suffix.len = fragment_end - fragment_start;
- suffix.insertion_offset += fragment_start - old_fragments.start().visible;
+ suffix.len = (fragment_end - fragment_start) as u32;
+ suffix.insertion_offset += (fragment_start - old_fragments.start().visible) as u32;
new_insertions.push(InsertionFragment::insert_new(&suffix));
new_ropes.push_fragment(&suffix, suffix.visible);
new_fragments.push(suffix, &None);
@@ -1075,7 +1120,7 @@ impl Buffer {
let mut insertion_slices = Vec::new();
let cx = Some(version.clone());
let mut new_insertions = Vec::new();
- let mut insertion_offset = 0;
+ let mut insertion_offset: u32 = 0;
let mut new_ropes =
RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
let mut old_fragments = self
@@ -1097,9 +1142,9 @@ impl Buffer {
if fragment_start > old_fragments.start().0.full_offset() {
if fragment_end > fragment_start {
let mut suffix = old_fragments.item().unwrap().clone();
- suffix.len = fragment_end.0 - fragment_start.0;
+ suffix.len = (fragment_end.0 - fragment_start.0) as u32;
suffix.insertion_offset +=
- fragment_start - old_fragments.start().0.full_offset();
+ (fragment_start - old_fragments.start().0.full_offset()) as u32;
new_insertions.push(InsertionFragment::insert_new(&suffix));
new_ropes.push_fragment(&suffix, suffix.visible);
new_fragments.push(suffix, &None);
@@ -1118,8 +1163,9 @@ impl Buffer {
let fragment_end = old_fragments.end().0.full_offset();
if fragment_end == range.start && fragment_end > fragment_start {
let mut fragment = old_fragments.item().unwrap().clone();
- fragment.len = fragment_end.0 - fragment_start.0;
- fragment.insertion_offset += fragment_start - old_fragments.start().0.full_offset();
+ fragment.len = (fragment_end.0 - fragment_start.0) as u32;
+ fragment.insertion_offset +=
+ (fragment_start - old_fragments.start().0.full_offset()) as u32;
new_insertions.push(InsertionFragment::insert_new(&fragment));
new_ropes.push_fragment(&fragment, fragment.visible);
new_fragments.push(fragment, &None);
@@ -1144,8 +1190,9 @@ impl Buffer {
// Preserve any portion of the current fragment that precedes this range.
if fragment_start < range.start {
let mut prefix = old_fragments.item().unwrap().clone();
- prefix.len = range.start.0 - fragment_start.0;
- prefix.insertion_offset += fragment_start - old_fragments.start().0.full_offset();
+ prefix.len = (range.start.0 - fragment_start.0) as u32;
+ prefix.insertion_offset +=
+ (fragment_start - old_fragments.start().0.full_offset()) as u32;
prefix.id = Locator::between(&new_fragments.summary().max_id, &prefix.id);
new_insertions.push(InsertionFragment::insert_new(&prefix));
fragment_start = range.start;
@@ -1160,29 +1207,24 @@ impl Buffer {
old_start += fragment_start.0 - old_fragments.start().0.full_offset().0;
}
let new_start = new_fragments.summary().text.visible;
- let fragment = Fragment {
- id: Locator::between(
- &new_fragments.summary().max_id,
- old_fragments
- .item()
- .map_or(&Locator::max(), |old_fragment| &old_fragment.id),
- ),
+ let next_fragment_id = old_fragments
+ .item()
+ .map_or(Locator::max_ref(), |old_fragment| &old_fragment.id);
+ Self::push_fragments_for_insertion(
+ new_text,
timestamp,
- insertion_offset,
- len: new_text.len(),
- deletions: Default::default(),
- max_undos: Default::default(),
- visible: true,
- };
+ &mut insertion_offset,
+ &mut new_fragments,
+ &mut new_insertions,
+ &mut insertion_slices,
+ &mut new_ropes,
+ next_fragment_id,
+ timestamp,
+ );
edits_patch.push(Edit {
old: old_start..old_start,
new: new_start..new_start + new_text.len(),
});
- insertion_slices.push(InsertionSlice::from_fragment(timestamp, &fragment));
- new_insertions.push(InsertionFragment::insert_new(&fragment));
- new_ropes.push_str(new_text);
- new_fragments.push(fragment, &None);
- insertion_offset += new_text.len();
}
// Advance through every fragment that intersects this range, marking the intersecting
@@ -1193,9 +1235,9 @@ impl Buffer {
let mut intersection = fragment.clone();
let intersection_end = cmp::min(range.end, fragment_end);
if fragment.was_visible(version, &self.undo_map) {
- intersection.len = intersection_end.0 - fragment_start.0;
+ intersection.len = (intersection_end.0 - fragment_start.0) as u32;
intersection.insertion_offset +=
- fragment_start - old_fragments.start().0.full_offset();
+ (fragment_start - old_fragments.start().0.full_offset()) as u32;
intersection.id =
Locator::between(&new_fragments.summary().max_id, &intersection.id);
intersection.deletions.push(timestamp);
@@ -1208,7 +1250,7 @@ impl Buffer {
+ (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 + intersection.len,
+ old: old_start..old_start + intersection.len as usize,
new: new_start..new_start,
});
}
@@ -1229,8 +1271,9 @@ impl Buffer {
let fragment_end = old_fragments.end().0.full_offset();
if fragment_end > fragment_start {
let mut suffix = old_fragments.item().unwrap().clone();
- suffix.len = fragment_end.0 - fragment_start.0;
- suffix.insertion_offset += fragment_start - old_fragments.start().0.full_offset();
+ suffix.len = (fragment_end.0 - fragment_start.0) as u32;
+ suffix.insertion_offset +=
+ (fragment_start - old_fragments.start().0.full_offset()) as u32;
new_insertions.push(InsertionFragment::insert_new(&suffix));
new_ropes.push_fragment(&suffix, suffix.visible);
new_fragments.push(suffix, &None);
@@ -1252,6 +1295,49 @@ impl Buffer {
self.subscriptions.publish_mut(&edits_patch)
}
+ fn push_fragments_for_insertion(
+ new_text: &str,
+ timestamp: clock::Lamport,
+ insertion_offset: &mut u32,
+ new_fragments: &mut SumTree<Fragment>,
+ new_insertions: &mut Vec<sum_tree::Edit<InsertionFragment>>,
+ insertion_slices: &mut Vec<InsertionSlice>,
+ new_ropes: &mut RopeBuilder,
+ next_fragment_id: &Locator,
+ edit_timestamp: clock::Lamport,
+ ) {
+ let mut text_offset = 0;
+ while text_offset < new_text.len() {
+ let target_end = new_text.len().min(text_offset + MAX_INSERTION_LEN);
+ let chunk_end = if target_end == new_text.len() {
+ target_end
+ } else {
+ new_text.floor_char_boundary(target_end)
+ };
+ if chunk_end == text_offset {
+ break;
+ }
+ let chunk_len = chunk_end - text_offset;
+
+ let fragment = Fragment {
+ id: Locator::between(&new_fragments.summary().max_id, next_fragment_id),
+ timestamp,
+ insertion_offset: *insertion_offset,
+ len: chunk_len as u32,
+ deletions: Default::default(),
+ max_undos: Default::default(),
+ visible: true,
+ };
+ insertion_slices.push(InsertionSlice::from_fragment(edit_timestamp, &fragment));
+ new_insertions.push(InsertionFragment::insert_new(&fragment));
+ new_fragments.push(fragment, &None);
+
+ *insertion_offset += chunk_len as u32;
+ text_offset = chunk_end;
+ }
+ new_ropes.push_str(new_text);
+ }
+
fn fragment_ids_for_edits<'a>(
&'a self,
edit_ids: impl Iterator<Item = &'a clock::Lamport>,
@@ -1260,38 +1346,56 @@ impl Buffer {
let mut insertion_slices = Vec::new();
for edit_id in edit_ids {
let insertion_slice = InsertionSlice {
- edit_id: *edit_id,
- insertion_id: clock::Lamport::MIN,
+ edit_id_value: edit_id.value,
+ edit_id_replica_id: edit_id.replica_id,
+ insertion_id_value: Lamport::MIN.value,
+ insertion_id_replica_id: Lamport::MIN.replica_id,
range: 0..0,
};
let slices = self
.snapshot
.insertion_slices
.iter_from(&insertion_slice)
- .take_while(|slice| slice.edit_id == *edit_id);
+ .take_while(|slice| {
+ Lamport {
+ value: slice.edit_id_value,
+ replica_id: slice.edit_id_replica_id,
+ } == *edit_id
+ });
insertion_slices.extend(slices)
}
- insertion_slices
- .sort_unstable_by_key(|s| (s.insertion_id, s.range.start, Reverse(s.range.end)));
+ insertion_slices.sort_unstable_by_key(|s| {
+ (
+ Lamport {
+ value: s.insertion_id_value,
+ replica_id: s.insertion_id_replica_id,
+ },
+ s.range.start,
+ Reverse(s.range.end),
+ )
+ });
// 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
+ let insertion_id = Lamport {
+ value: insertion_slice.insertion_id_value,
+ replica_id: insertion_slice.insertion_id_replica_id,
+ };
+ if 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,
+ timestamp: 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
+ if item.timestamp != insertion_id || item.split_offset >= insertion_slice.range.end
{
break;
}
@@ -1330,13 +1434,13 @@ impl Buffer {
let new_start = new_fragments.summary().text.visible;
if fragment_was_visible && !fragment.visible {
edits.push(Edit {
- old: old_start..old_start + fragment.len,
+ old: old_start..old_start + fragment.len as usize,
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: new_start..new_start + fragment.len as usize,
});
}
new_ropes.push_fragment(&fragment, fragment_was_visible);
@@ -1582,7 +1686,12 @@ impl Buffer {
cursor.seek_forward(&Some(fragment_id), Bias::Left);
let fragment = cursor.item()?;
let start_offset = cursor.start().1;
- let end_offset = start_offset + if fragment.visible { fragment.len } else { 0 };
+ let end_offset = start_offset
+ + if fragment.visible {
+ fragment.len as usize
+ } else {
+ 0
+ };
Some(start_offset..end_offset)
});
@@ -2038,10 +2147,6 @@ impl BufferSnapshot {
self.deleted_text.to_string()
}
- pub fn fragments(&self) -> impl Iterator<Item = &Fragment> {
- self.fragments.iter()
- }
-
pub fn text_summary(&self) -> TextSummary {
self.visible_text.summary()
}
@@ -2287,7 +2392,7 @@ impl BufferSnapshot {
let fragment = fragment_cursor.item().unwrap();
let mut fragment_offset = fragment_cursor.start().1;
if fragment.visible {
- fragment_offset += anchor.offset - insertion.split_offset;
+ fragment_offset += (anchor.offset - insertion.split_offset) as usize;
}
position.add_assign(&text_cursor.summary(fragment_offset));
@@ -2332,7 +2437,7 @@ impl BufferSnapshot {
let fragment = item.unwrap();
let mut fragment_offset = start.1;
if fragment.visible {
- fragment_offset += anchor.offset - insertion.split_offset;
+ fragment_offset += (anchor.offset - insertion.split_offset) as usize;
}
fragment_offset
}
@@ -2457,7 +2562,7 @@ impl BufferSnapshot {
let overshoot = offset - start;
Anchor::new(
fragment.timestamp,
- fragment.insertion_offset + overshoot,
+ fragment.insertion_offset + overshoot as u32,
bias,
Some(self.remote_id),
)
@@ -2546,7 +2651,7 @@ impl BufferSnapshot {
let mut visible_start = start.1.visible;
let mut deleted_start = start.1.deleted;
if let Some(fragment) = item {
- let overshoot = range.start.offset - fragment.insertion_offset;
+ let overshoot = (range.start.offset - fragment.insertion_offset) as usize;
if fragment.visible {
visible_start += overshoot;
} else {
@@ -2706,7 +2811,7 @@ impl<'a> RopeBuilder<'a> {
fn push_fragment(&mut self, fragment: &Fragment, was_visible: bool) {
debug_assert!(fragment.len > 0);
- self.push(fragment.len, was_visible, fragment.visible)
+ self.push(fragment.len as usize, was_visible, fragment.visible)
}
fn push(&mut self, len: usize, was_visible: bool, is_visible: bool) {
@@ -2781,7 +2886,8 @@ impl<D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Ed
if fragment.id == *self.range.end.0 {
visible_end = cmp::min(
visible_end,
- cursor.start().visible + (self.range.end.1 - fragment.insertion_offset),
+ cursor.start().visible
+ + (self.range.end.1 - fragment.insertion_offset) as usize,
);
}
@@ -2807,7 +2913,8 @@ impl<D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Ed
if fragment.id == *self.range.end.0 {
deleted_end = cmp::min(
deleted_end,
- cursor.start().deleted + (self.range.end.1 - fragment.insertion_offset),
+ cursor.start().deleted
+ + (self.range.end.1 - fragment.insertion_offset) as usize,
);
}
@@ -2872,7 +2979,7 @@ impl sum_tree::Item for Fragment {
FragmentSummary {
max_id: self.id.clone(),
text: FragmentTextSummary {
- visible: self.len,
+ visible: self.len as usize,
deleted: 0,
},
max_version,
@@ -2884,7 +2991,7 @@ impl sum_tree::Item for Fragment {
max_id: self.id.clone(),
text: FragmentTextSummary {
visible: 0,
- deleted: self.len,
+ deleted: self.len as usize,
},
max_version,
min_insertion_version,