diff --git a/crates/text/src/locator.rs b/crates/text/src/locator.rs new file mode 100644 index 0000000000..487c8c2608 --- /dev/null +++ b/crates/text/src/locator.rs @@ -0,0 +1,74 @@ +use smallvec::{smallvec, SmallVec}; +use std::iter; + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Locator(SmallVec<[u32; 4]>); + +impl Locator { + pub fn min() -> Self { + Self(smallvec![u32::MIN]) + } + + pub fn max() -> Self { + Self(smallvec![u32::MAX]) + } + + pub fn between(lhs: &Self, rhs: &Self) -> Self { + let lhs = lhs.0.iter().copied().chain(iter::repeat(u32::MIN)); + let rhs = rhs.0.iter().copied().chain(iter::repeat(u32::MAX)); + let mut location = SmallVec::new(); + for (lhs, rhs) in lhs.zip(rhs) { + let mid = lhs + (rhs.saturating_sub(lhs)) / 2; + location.push(mid); + if mid > lhs { + break; + } + } + Self(location) + } +} + +impl Default for Locator { + fn default() -> Self { + Self::min() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::mem; + + #[gpui::test(iterations = 100)] + fn test_locators(mut rng: StdRng) { + let mut lhs = Default::default(); + let mut rhs = Default::default(); + while lhs == rhs { + lhs = Locator( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + rhs = Locator( + (0..rng.gen_range(1..=5)) + .map(|_| rng.gen_range(0..=100)) + .collect(), + ); + } + + if lhs > rhs { + mem::swap(&mut lhs, &mut rhs); + } + + let middle = Locator::between(&lhs, &rhs); + assert!(middle > lhs); + assert!(middle < rhs); + for ix in 0..middle.0.len() - 1 { + assert!( + middle.0[ix] == *lhs.0.get(ix).unwrap_or(&0) + || middle.0[ix] == *rhs.0.get(ix).unwrap_or(&0) + ); + } + } +} diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index a434e97e2e..3d2f95c169 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1,4 +1,5 @@ mod anchor; +mod locator; mod operation_queue; mod patch; mod point; @@ -14,6 +15,7 @@ pub use anchor::*; use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{HashMap, HashSet}; +use locator::Locator; use operation_queue::OperationQueue; use parking_lot::Mutex; pub use patch::Patch; @@ -55,6 +57,7 @@ pub struct Snapshot { deleted_text: Rope, undo_map: UndoMap, fragments: SumTree, + insertions: SumTree, pub version: clock::Global, } @@ -381,6 +384,7 @@ impl InsertionTimestamp { #[derive(Eq, PartialEq, Clone, Debug)] struct Fragment { + id: Locator, timestamp: InsertionTimestamp, len: usize, visible: bool, @@ -391,6 +395,7 @@ struct Fragment { #[derive(Eq, PartialEq, Clone, Debug)] pub struct FragmentSummary { text: FragmentTextSummary, + max_id: Locator, max_version: clock::Global, min_insertion_version: clock::Global, max_insertion_version: clock::Global, @@ -402,11 +407,17 @@ struct FragmentTextSummary { deleted: usize, } -impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary { - fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { - self.visible += summary.text.visible; - self.deleted += summary.text.deleted; - } +#[derive(Eq, PartialEq, Clone, Debug)] +struct InsertionFragment { + timestamp: InsertionTimestamp, + split_offset: usize, + fragment_id: Locator, +} + +#[derive(Clone, Debug, Default)] +struct InsertionSummary { + max_timestamp: InsertionTimestamp, + max_split_offset: usize, } #[derive(Clone, Debug, Eq, PartialEq)] @@ -452,6 +463,7 @@ pub struct UndoOperation { impl Buffer { pub fn new(replica_id: u16, remote_id: u64, history: History) -> Buffer { let mut fragments = SumTree::new(); + let mut insertions = SumTree::new(); let mut local_clock = clock::Local::new(replica_id); let mut lamport_clock = clock::Lamport::new(replica_id); @@ -466,8 +478,10 @@ impl Buffer { local_clock.observe(timestamp.local()); lamport_clock.observe(timestamp.lamport()); version.observe(timestamp.local()); + let fragment_id = Locator::between(&Locator::min(), &Locator::max()); fragments.push( Fragment { + id: fragment_id, timestamp, len: visible_text.len(), visible: true, @@ -476,6 +490,14 @@ impl Buffer { }, &None, ); + insertions.push( + InsertionFragment { + timestamp, + split_offset: 0, + fragment_id, + }, + &(), + ); } Buffer { @@ -483,6 +505,7 @@ impl Buffer { visible_text, deleted_text: Rope::new(), fragments, + insertions, version, undo_map: Default::default(), }, @@ -504,13 +527,7 @@ impl Buffer { } pub fn snapshot(&self) -> Snapshot { - Snapshot { - visible_text: self.visible_text.clone(), - deleted_text: self.deleted_text.clone(), - undo_map: self.undo_map.clone(), - fragments: self.fragments.clone(), - version: self.version.clone(), - } + self.snapshot.clone() } pub fn replica_id(&self) -> ReplicaId { @@ -569,6 +586,7 @@ impl Buffer { ranges: Vec::with_capacity(ranges.len()), new_text: None, }; + let mut insertions = Vec::new(); let mut ranges = ranges .map(|range| range.start.to_offset(&*self)..range.end.to_offset(&*self)) @@ -2040,6 +2058,7 @@ impl sum_tree::Item for Fragment { let max_insertion_version = min_insertion_version.clone(); if self.visible { FragmentSummary { + max_id: self.id.clone(), text: FragmentTextSummary { visible: self.len, deleted: 0, @@ -2050,6 +2069,7 @@ impl sum_tree::Item for Fragment { } } else { FragmentSummary { + max_id: self.id.clone(), text: FragmentTextSummary { visible: 0, deleted: self.len, @@ -2079,6 +2099,7 @@ impl sum_tree::Summary for FragmentSummary { impl Default for FragmentSummary { fn default() -> Self { FragmentSummary { + max_id: Locator::min(), text: FragmentTextSummary::default(), max_version: clock::Global::new(), min_insertion_version: clock::Global::new(), @@ -2087,6 +2108,33 @@ impl Default for FragmentSummary { } } +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentTextSummary { + fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { + self.visible += summary.text.visible; + self.deleted += summary.text.deleted; + } +} + +impl sum_tree::Item for InsertionFragment { + type Summary = InsertionSummary; + + fn summary(&self) -> Self::Summary { + InsertionSummary { + max_timestamp: self.timestamp, + max_split_offset: self.split_offset, + } + } +} + +impl sum_tree::Summary for InsertionSummary { + type Context = (); + + fn add_summary(&mut self, summary: &Self, cx: &()) { + self.max_timestamp = summary.max_timestamp; + self.max_split_offset = summary.max_split_offset; + } +} + #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FullOffset(pub usize);