diff --git a/Cargo.lock b/Cargo.lock index 755f4440d9..5d6ce29b14 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8806,6 +8806,29 @@ dependencies = [ "util", ] +[[package]] +name = "text2" +version = "0.1.0" +dependencies = [ + "anyhow", + "clock", + "collections", + "ctor", + "digest 0.9.0", + "env_logger 0.9.3", + "gpui2", + "lazy_static", + "log", + "parking_lot 0.11.2", + "postage", + "rand 0.8.5", + "regex", + "rope", + "smallvec", + "sum_tree", + "util", +] + [[package]] name = "textwrap" version = "0.16.0" @@ -11052,7 +11075,7 @@ dependencies = [ "smol", "sum_tree", "tempdir", - "text", + "text2", "theme2", "thiserror", "tiny_http", diff --git a/crates/text2/Cargo.toml b/crates/text2/Cargo.toml new file mode 100644 index 0000000000..6891fef680 --- /dev/null +++ b/crates/text2/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "text2" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/text2.rs" +doctest = false + +[features] +test-support = ["rand"] + +[dependencies] +clock = { path = "../clock" } +collections = { path = "../collections" } +rope = { path = "../rope" } +sum_tree = { path = "../sum_tree" } +util = { path = "../util" } + +anyhow.workspace = true +digest = { version = "0.9", features = ["std"] } +lazy_static.workspace = true +log.workspace = true +parking_lot.workspace = true +postage.workspace = true +rand = { workspace = true, optional = true } +smallvec.workspace = true +regex.workspace = true + +[dev-dependencies] +collections = { path = "../collections", features = ["test-support"] } +gpui2 = { path = "../gpui2", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true diff --git a/crates/text2/src/anchor.rs b/crates/text2/src/anchor.rs new file mode 100644 index 0000000000..084be0e336 --- /dev/null +++ b/crates/text2/src/anchor.rs @@ -0,0 +1,144 @@ +use crate::{ + locator::Locator, BufferSnapshot, Point, PointUtf16, TextDimension, ToOffset, ToPoint, + ToPointUtf16, +}; +use anyhow::Result; +use std::{cmp::Ordering, fmt::Debug, ops::Range}; +use sum_tree::Bias; + +#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Default)] +pub struct Anchor { + pub timestamp: clock::Lamport, + pub offset: usize, + pub bias: Bias, + pub buffer_id: Option, +} + +impl Anchor { + pub const MIN: Self = Self { + timestamp: clock::Lamport::MIN, + offset: usize::MIN, + bias: Bias::Left, + buffer_id: None, + }; + + pub const MAX: Self = Self { + timestamp: clock::Lamport::MAX, + offset: usize::MAX, + bias: Bias::Right, + buffer_id: None, + }; + + pub fn cmp(&self, other: &Anchor, buffer: &BufferSnapshot) -> Ordering { + let fragment_id_comparison = if self.timestamp == other.timestamp { + Ordering::Equal + } else { + buffer + .fragment_id_for_anchor(self) + .cmp(buffer.fragment_id_for_anchor(other)) + }; + + fragment_id_comparison + .then_with(|| self.offset.cmp(&other.offset)) + .then_with(|| self.bias.cmp(&other.bias)) + } + + pub fn min(&self, other: &Self, buffer: &BufferSnapshot) -> Self { + if self.cmp(other, buffer).is_le() { + *self + } else { + *other + } + } + + pub fn max(&self, other: &Self, buffer: &BufferSnapshot) -> Self { + if self.cmp(other, buffer).is_ge() { + *self + } else { + *other + } + } + + pub fn bias(&self, bias: Bias, buffer: &BufferSnapshot) -> Anchor { + if bias == Bias::Left { + self.bias_left(buffer) + } else { + self.bias_right(buffer) + } + } + + pub fn bias_left(&self, buffer: &BufferSnapshot) -> Anchor { + if self.bias == Bias::Left { + *self + } else { + buffer.anchor_before(self) + } + } + + pub fn bias_right(&self, buffer: &BufferSnapshot) -> Anchor { + if self.bias == Bias::Right { + *self + } else { + buffer.anchor_after(self) + } + } + + pub fn summary(&self, content: &BufferSnapshot) -> D + where + D: TextDimension, + { + content.summary_for_anchor(self) + } + + /// Returns true when the [Anchor] is located inside a visible fragment. + pub fn is_valid(&self, buffer: &BufferSnapshot) -> bool { + if *self == Anchor::MIN || *self == Anchor::MAX { + true + } else { + let fragment_id = buffer.fragment_id_for_anchor(self); + let mut fragment_cursor = buffer.fragments.cursor::<(Option<&Locator>, usize)>(); + fragment_cursor.seek(&Some(fragment_id), Bias::Left, &None); + fragment_cursor + .item() + .map_or(false, |fragment| fragment.visible) + } + } +} + +pub trait OffsetRangeExt { + fn to_offset(&self, snapshot: &BufferSnapshot) -> Range; + fn to_point(&self, snapshot: &BufferSnapshot) -> Range; + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range; +} + +impl OffsetRangeExt for Range +where + T: ToOffset, +{ + fn to_offset(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot)..self.end.to_offset(snapshot) + } + + fn to_point(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot).to_point(snapshot) + ..self.end.to_offset(snapshot).to_point(snapshot) + } + + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> Range { + self.start.to_offset(snapshot).to_point_utf16(snapshot) + ..self.end.to_offset(snapshot).to_point_utf16(snapshot) + } +} + +pub trait AnchorRangeExt { + fn cmp(&self, b: &Range, buffer: &BufferSnapshot) -> Result; +} + +impl AnchorRangeExt for Range { + fn cmp(&self, other: &Range, buffer: &BufferSnapshot) -> Result { + Ok(match self.start.cmp(&other.start, buffer) { + Ordering::Equal => other.end.cmp(&self.end, buffer), + ord => ord, + }) + } +} diff --git a/crates/text2/src/locator.rs b/crates/text2/src/locator.rs new file mode 100644 index 0000000000..27fdb34cde --- /dev/null +++ b/crates/text2/src/locator.rs @@ -0,0 +1,125 @@ +use lazy_static::lazy_static; +use smallvec::{smallvec, SmallVec}; +use std::iter; + +lazy_static! { + static ref MIN: Locator = Locator::min(); + static ref MAX: Locator = Locator::max(); +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Locator(SmallVec<[u64; 4]>); + +impl Locator { + pub fn min() -> Self { + Self(smallvec![u64::MIN]) + } + + pub fn max() -> Self { + Self(smallvec![u64::MAX]) + } + + pub fn min_ref() -> &'static Self { + &*MIN + } + + pub fn max_ref() -> &'static Self { + &*MAX + } + + pub fn assign(&mut self, other: &Self) { + self.0.resize(other.0.len(), 0); + self.0.copy_from_slice(&other.0); + } + + pub fn between(lhs: &Self, rhs: &Self) -> Self { + let lhs = lhs.0.iter().copied().chain(iter::repeat(u64::MIN)); + let rhs = rhs.0.iter().copied().chain(iter::repeat(u64::MAX)); + let mut location = SmallVec::new(); + for (lhs, rhs) in lhs.zip(rhs) { + let mid = lhs + ((rhs.saturating_sub(lhs)) >> 48); + location.push(mid); + if mid > lhs { + break; + } + } + Self(location) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl Default for Locator { + fn default() -> Self { + Self::min() + } +} + +impl sum_tree::Item for Locator { + type Summary = Locator; + + fn summary(&self) -> Self::Summary { + self.clone() + } +} + +impl sum_tree::KeyedItem for Locator { + type Key = Locator; + + fn key(&self) -> Self::Key { + self.clone() + } +} + +impl sum_tree::Summary for Locator { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.assign(summary); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::mem; + + #[gpui2::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/text2/src/network.rs b/crates/text2/src/network.rs new file mode 100644 index 0000000000..2f49756ca3 --- /dev/null +++ b/crates/text2/src/network.rs @@ -0,0 +1,69 @@ +use clock::ReplicaId; + +pub struct Network { + inboxes: std::collections::BTreeMap>>, + all_messages: Vec, + rng: R, +} + +#[derive(Clone)] +struct Envelope { + message: T, +} + +impl Network { + pub fn new(rng: R) -> Self { + Network { + inboxes: Default::default(), + all_messages: Vec::new(), + rng, + } + } + + pub fn add_peer(&mut self, id: ReplicaId) { + self.inboxes.insert(id, Vec::new()); + } + + pub fn replicate(&mut self, old_replica_id: ReplicaId, new_replica_id: ReplicaId) { + self.inboxes + .insert(new_replica_id, self.inboxes[&old_replica_id].clone()); + } + + pub fn is_idle(&self) -> bool { + self.inboxes.values().all(|i| i.is_empty()) + } + + pub fn broadcast(&mut self, sender: ReplicaId, messages: Vec) { + for (replica, inbox) in self.inboxes.iter_mut() { + if *replica != sender { + for message in &messages { + // Insert one or more duplicates of this message, potentially *before* the previous + // message sent by this peer to simulate out-of-order delivery. + for _ in 0..self.rng.gen_range(1..4) { + let insertion_index = self.rng.gen_range(0..inbox.len() + 1); + inbox.insert( + insertion_index, + Envelope { + message: message.clone(), + }, + ); + } + } + } + } + self.all_messages.extend(messages); + } + + pub fn has_unreceived(&self, receiver: ReplicaId) -> bool { + !self.inboxes[&receiver].is_empty() + } + + pub fn receive(&mut self, receiver: ReplicaId) -> Vec { + let inbox = self.inboxes.get_mut(&receiver).unwrap(); + let count = self.rng.gen_range(0..inbox.len() + 1); + inbox + .drain(0..count) + .map(|envelope| envelope.message) + .collect() + } +} diff --git a/crates/text2/src/operation_queue.rs b/crates/text2/src/operation_queue.rs new file mode 100644 index 0000000000..063f050665 --- /dev/null +++ b/crates/text2/src/operation_queue.rs @@ -0,0 +1,153 @@ +use std::{fmt::Debug, ops::Add}; +use sum_tree::{Dimension, Edit, Item, KeyedItem, SumTree, Summary}; + +pub trait Operation: Clone + Debug { + fn lamport_timestamp(&self) -> clock::Lamport; +} + +#[derive(Clone, Debug)] +struct OperationItem(T); + +#[derive(Clone, Debug)] +pub struct OperationQueue(SumTree>); + +#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +pub struct OperationKey(clock::Lamport); + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +pub struct OperationSummary { + pub key: OperationKey, + pub len: usize, +} + +impl OperationKey { + pub fn new(timestamp: clock::Lamport) -> Self { + Self(timestamp) + } +} + +impl Default for OperationQueue { + fn default() -> Self { + OperationQueue::new() + } +} + +impl OperationQueue { + pub fn new() -> Self { + OperationQueue(SumTree::new()) + } + + pub fn len(&self) -> usize { + self.0.summary().len + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn insert(&mut self, mut ops: Vec) { + ops.sort_by_key(|op| op.lamport_timestamp()); + ops.dedup_by_key(|op| op.lamport_timestamp()); + self.0.edit( + ops.into_iter() + .map(|op| Edit::Insert(OperationItem(op))) + .collect(), + &(), + ); + } + + pub fn drain(&mut self) -> Self { + let clone = self.clone(); + self.0 = SumTree::new(); + clone + } + + pub fn iter(&self) -> impl Iterator { + self.0.iter().map(|i| &i.0) + } +} + +impl Summary for OperationSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + assert!(self.key < other.key); + self.key = other.key; + self.len += other.len; + } +} + +impl<'a> Add<&'a Self> for OperationSummary { + type Output = Self; + + fn add(self, other: &Self) -> Self { + assert!(self.key < other.key); + OperationSummary { + key: other.key, + len: self.len + other.len, + } + } +} + +impl<'a> Dimension<'a, OperationSummary> for OperationKey { + fn add_summary(&mut self, summary: &OperationSummary, _: &()) { + assert!(*self <= summary.key); + *self = summary.key; + } +} + +impl Item for OperationItem { + type Summary = OperationSummary; + + fn summary(&self) -> Self::Summary { + OperationSummary { + key: OperationKey::new(self.0.lamport_timestamp()), + len: 1, + } + } +} + +impl KeyedItem for OperationItem { + type Key = OperationKey; + + fn key(&self) -> Self::Key { + OperationKey::new(self.0.lamport_timestamp()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_len() { + let mut clock = clock::Lamport::new(0); + + let mut queue = OperationQueue::new(); + assert_eq!(queue.len(), 0); + + queue.insert(vec![ + TestOperation(clock.tick()), + TestOperation(clock.tick()), + ]); + assert_eq!(queue.len(), 2); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 3); + + drop(queue.drain()); + assert_eq!(queue.len(), 0); + + queue.insert(vec![TestOperation(clock.tick())]); + assert_eq!(queue.len(), 1); + } + + #[derive(Clone, Debug, Eq, PartialEq)] + struct TestOperation(clock::Lamport); + + impl Operation for TestOperation { + fn lamport_timestamp(&self) -> clock::Lamport { + self.0 + } + } +} diff --git a/crates/text2/src/patch.rs b/crates/text2/src/patch.rs new file mode 100644 index 0000000000..20e4a4d889 --- /dev/null +++ b/crates/text2/src/patch.rs @@ -0,0 +1,594 @@ +use crate::Edit; +use std::{ + cmp, mem, + ops::{Add, AddAssign, Sub}, +}; + +#[derive(Clone, Default, Debug, PartialEq, Eq)] +pub struct Patch(Vec>); + +impl Patch +where + T: 'static + + Clone + + Copy + + Ord + + Sub + + Add + + AddAssign + + Default + + PartialEq, +{ + pub fn new(edits: Vec>) -> Self { + #[cfg(debug_assertions)] + { + let mut last_edit: Option<&Edit> = None; + for edit in &edits { + if let Some(last_edit) = last_edit { + assert!(edit.old.start > last_edit.old.end); + assert!(edit.new.start > last_edit.new.end); + } + last_edit = Some(edit); + } + } + Self(edits) + } + + pub fn edits(&self) -> &[Edit] { + &self.0 + } + + pub fn into_inner(self) -> Vec> { + self.0 + } + + pub fn compose(&self, new_edits_iter: impl IntoIterator>) -> Self { + let mut old_edits_iter = self.0.iter().cloned().peekable(); + let mut new_edits_iter = new_edits_iter.into_iter().peekable(); + let mut composed = Patch(Vec::new()); + + let mut old_start = T::default(); + let mut new_start = T::default(); + loop { + let old_edit = old_edits_iter.peek_mut(); + let new_edit = new_edits_iter.peek_mut(); + + // Push the old edit if its new end is before the new edit's old start. + if let Some(old_edit) = old_edit.as_ref() { + let new_edit = new_edit.as_ref(); + if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + old_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + continue; + } + } + + // Push the new edit if its old end is before the old edit's new start. + if let Some(new_edit) = new_edit.as_ref() { + let old_edit = old_edit.as_ref(); + if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + new_edit.old_len(); + let new_end = new_start + new_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + continue; + } + } + + // If we still have edits by this point then they must intersect, so we compose them. + if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) { + if old_edit.new.start < new_edit.old.start { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let overshoot = new_edit.old.start - old_edit.new.start; + let old_end = cmp::min(old_start + overshoot, old_edit.old.end); + let new_end = new_start + overshoot; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; + } else { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let overshoot = old_edit.new.start - new_edit.old.start; + let old_end = old_start + overshoot; + let new_end = cmp::min(new_start + overshoot, new_edit.new.end); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start += overshoot; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + } + + if old_edit.new.end > new_edit.old.end { + let old_end = old_start + cmp::min(old_edit.old_len(), new_edit.old_len()); + let new_end = new_start + new_edit.new_len(); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start = new_edit.old.end; + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + } else { + let old_end = old_start + old_edit.old_len(); + let new_end = new_start + cmp::min(old_edit.new_len(), new_edit.new_len()); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start = old_edit.new.end; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + } + } else { + break; + } + } + + composed + } + + pub fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } + + pub fn clear(&mut self) { + self.0.clear(); + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn push(&mut self, edit: Edit) { + if edit.is_empty() { + return; + } + + if let Some(last) = self.0.last_mut() { + if last.old.end >= edit.old.start { + last.old.end = edit.old.end; + last.new.end = edit.new.end; + } else { + self.0.push(edit); + } + } else { + self.0.push(edit); + } + } + + pub fn old_to_new(&self, old: T) -> T { + let ix = match self.0.binary_search_by(|probe| probe.old.start.cmp(&old)) { + Ok(ix) => ix, + Err(ix) => { + if ix == 0 { + return old; + } else { + ix - 1 + } + } + }; + if let Some(edit) = self.0.get(ix) { + if old >= edit.old.end { + edit.new.end + (old - edit.old.end) + } else { + edit.new.start + } + } else { + old + } + } +} + +impl IntoIterator for Patch { + type Item = Edit; + type IntoIter = std::vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl<'a, T: Clone> IntoIterator for &'a Patch { + type Item = Edit; + type IntoIter = std::iter::Cloned>>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().cloned() + } +} + +impl<'a, T: Clone> IntoIterator for &'a mut Patch { + type Item = Edit; + type IntoIter = std::iter::Cloned>>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().cloned() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::env; + + #[gpui2::test] + fn test_one_disjoint_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 0..0, + new: 0..4, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..3, + new: 5..8, + }, + ]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 5..9, + new: 5..7, + }]), + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 4..8, + new: 5..7, + }, + ]), + ); + } + + #[gpui2::test] + fn test_one_overlapping_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 3..5, + new: 3..6, + }]), + Patch(vec![Edit { + old: 1..4, + new: 1..6, + }]), + ); + } + + #[gpui2::test] + fn test_two_disjoint_and_overlapping() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 8..12, + new: 9..11, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 3..10, + new: 7..9, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..12, + new: 5..10, + }, + ]), + ); + } + + #[gpui2::test] + fn test_two_new_edits_overlapping_one_old_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..1, + }, + Edit { + old: 1..2, + new: 2..2, + }, + ]), + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 2..3, + new: 2..4, + }]), + Patch(vec![ + Edit { + old: 0..2, + new: 0..1, + }, + Edit { + old: 3..3, + new: 2..5, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..6, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..2, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..2, + }, + Edit { + old: 2..5, + new: 4..4, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..4, + }]), + ); + } + + #[gpui2::test] + fn test_two_new_edits_touching_one_old_edit() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..2, + }, + Edit { + old: 4..4, + new: 3..4, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + ); + } + + #[gpui2::test] + fn test_old_to_new() { + let patch = Patch(vec![ + Edit { + old: 2..4, + new: 2..4, + }, + Edit { + old: 7..8, + new: 7..11, + }, + ]); + assert_eq!(patch.old_to_new(0), 0); + assert_eq!(patch.old_to_new(1), 1); + assert_eq!(patch.old_to_new(2), 2); + assert_eq!(patch.old_to_new(3), 2); + assert_eq!(patch.old_to_new(4), 4); + assert_eq!(patch.old_to_new(5), 5); + assert_eq!(patch.old_to_new(6), 6); + assert_eq!(patch.old_to_new(7), 7); + assert_eq!(patch.old_to_new(8), 11); + assert_eq!(patch.old_to_new(9), 12); + } + + #[gpui2::test(iterations = 100)] + fn test_random_patch_compositions(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(20); + + let initial_chars = (0..rng.gen_range(0..=100)) + .map(|_| rng.gen_range(b'a'..=b'z') as char) + .collect::>(); + log::info!("initial chars: {:?}", initial_chars); + + // Generate two sequential patches + let mut patches = Vec::new(); + let mut expected_chars = initial_chars.clone(); + for i in 0..2 { + log::info!("patch {}:", i); + + let mut delta = 0i32; + let mut last_edit_end = 0; + let mut edits = Vec::new(); + + for _ in 0..operations { + if last_edit_end >= expected_chars.len() { + break; + } + + let end = rng.gen_range(last_edit_end..=expected_chars.len()); + let start = rng.gen_range(last_edit_end..=end); + let old_len = end - start; + + let mut new_len = rng.gen_range(0..=3); + if start == end && new_len == 0 { + new_len += 1; + } + + last_edit_end = start + new_len + 1; + + let new_chars = (0..new_len) + .map(|_| rng.gen_range(b'A'..=b'Z') as char) + .collect::>(); + log::info!( + " editing {:?}: {:?}", + start..end, + new_chars.iter().collect::() + ); + edits.push(Edit { + old: (start as i32 - delta) as u32..(end as i32 - delta) as u32, + new: start as u32..(start + new_len) as u32, + }); + expected_chars.splice(start..end, new_chars); + + delta += new_len as i32 - old_len as i32; + } + + patches.push(Patch(edits)); + } + + log::info!("old patch: {:?}", &patches[0]); + log::info!("new patch: {:?}", &patches[1]); + log::info!("initial chars: {:?}", initial_chars); + log::info!("final chars: {:?}", expected_chars); + + // Compose the patches, and verify that it has the same effect as applying the + // two patches separately. + let composed = patches[0].compose(&patches[1]); + log::info!("composed patch: {:?}", &composed); + + let mut actual_chars = initial_chars; + for edit in composed.0 { + actual_chars.splice( + edit.new.start as usize..edit.new.start as usize + edit.old.len(), + expected_chars[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + + assert_eq!(actual_chars, expected_chars); + } + + #[track_caller] + fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) { + let original = ('a'..'z').collect::>(); + let inserted = ('A'..'Z').collect::>(); + + let mut expected = original.clone(); + apply_patch(&mut expected, &old, &inserted); + apply_patch(&mut expected, &new, &inserted); + + let mut actual = original; + apply_patch(&mut actual, &composed, &expected); + assert_eq!( + actual.into_iter().collect::(), + expected.into_iter().collect::(), + "expected patch is incorrect" + ); + + assert_eq!(old.compose(&new), composed); + } + + fn apply_patch(text: &mut Vec, patch: &Patch, new_text: &[char]) { + for edit in patch.0.iter().rev() { + text.splice( + edit.old.start as usize..edit.old.end as usize, + new_text[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + } +} diff --git a/crates/text2/src/selection.rs b/crates/text2/src/selection.rs new file mode 100644 index 0000000000..480cb99d74 --- /dev/null +++ b/crates/text2/src/selection.rs @@ -0,0 +1,123 @@ +use crate::{Anchor, BufferSnapshot, TextDimension}; +use std::cmp::Ordering; +use std::ops::Range; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SelectionGoal { + None, + HorizontalPosition(f32), + HorizontalRange { start: f32, end: f32 }, + WrappedHorizontalPosition((u32, f32)), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Selection { + pub id: usize, + pub start: T, + pub end: T, + pub reversed: bool, + pub goal: SelectionGoal, +} + +impl Default for SelectionGoal { + fn default() -> Self { + Self::None + } +} + +impl Selection { + pub fn head(&self) -> T { + if self.reversed { + self.start.clone() + } else { + self.end.clone() + } + } + + pub fn tail(&self) -> T { + if self.reversed { + self.end.clone() + } else { + self.start.clone() + } + } + + pub fn map(&self, f: F) -> Selection + where + F: Fn(T) -> S, + { + Selection:: { + id: self.id, + start: f(self.start.clone()), + end: f(self.end.clone()), + reversed: self.reversed, + goal: self.goal, + } + } + + pub fn collapse_to(&mut self, point: T, new_goal: SelectionGoal) { + self.start = point.clone(); + self.end = point; + self.goal = new_goal; + self.reversed = false; + } +} + +impl Selection { + pub fn is_empty(&self) -> bool { + self.start == self.end + } + + pub fn set_head(&mut self, head: T, new_goal: SelectionGoal) { + if head.cmp(&self.tail()) < Ordering::Equal { + if !self.reversed { + self.end = self.start; + self.reversed = true; + } + self.start = head; + } else { + if self.reversed { + self.start = self.end; + self.reversed = false; + } + self.end = head; + } + self.goal = new_goal; + } + + pub fn range(&self) -> Range { + self.start..self.end + } +} + +impl Selection { + #[cfg(feature = "test-support")] + pub fn from_offset(offset: usize) -> Self { + Selection { + id: 0, + start: offset, + end: offset, + goal: SelectionGoal::None, + reversed: false, + } + } + + pub fn equals(&self, offset_range: &Range) -> bool { + self.start == offset_range.start && self.end == offset_range.end + } +} + +impl Selection { + pub fn resolve<'a, D: 'a + TextDimension>( + &'a self, + snapshot: &'a BufferSnapshot, + ) -> Selection { + Selection { + id: self.id, + start: snapshot.summary_for_anchor(&self.start), + end: snapshot.summary_for_anchor(&self.end), + reversed: self.reversed, + goal: self.goal, + } + } +} diff --git a/crates/text2/src/subscription.rs b/crates/text2/src/subscription.rs new file mode 100644 index 0000000000..b636dfcc92 --- /dev/null +++ b/crates/text2/src/subscription.rs @@ -0,0 +1,48 @@ +use crate::{Edit, Patch}; +use parking_lot::Mutex; +use std::{ + mem, + sync::{Arc, Weak}, +}; + +#[derive(Default)] +pub struct Topic(Mutex>>>>); + +pub struct Subscription(Arc>>); + +impl Topic { + pub fn subscribe(&mut self) -> Subscription { + let subscription = Subscription(Default::default()); + self.0.get_mut().push(Arc::downgrade(&subscription.0)); + subscription + } + + pub fn publish(&self, edits: impl Clone + IntoIterator>) { + publish(&mut *self.0.lock(), edits); + } + + pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator>) { + publish(self.0.get_mut(), edits); + } +} + +impl Subscription { + pub fn consume(&self) -> Patch { + mem::take(&mut *self.0.lock()) + } +} + +fn publish( + subscriptions: &mut Vec>>>, + edits: impl Clone + IntoIterator>, +) { + subscriptions.retain(|subscription| { + if let Some(subscription) = subscription.upgrade() { + let mut patch = subscription.lock(); + *patch = patch.compose(edits.clone()); + true + } else { + false + } + }); +} diff --git a/crates/text2/src/tests.rs b/crates/text2/src/tests.rs new file mode 100644 index 0000000000..96248285ea --- /dev/null +++ b/crates/text2/src/tests.rs @@ -0,0 +1,764 @@ +use super::{network::Network, *}; +use clock::ReplicaId; +use rand::prelude::*; +use std::{ + cmp::Ordering, + env, + iter::Iterator, + time::{Duration, Instant}, +}; + +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + if std::env::var("RUST_LOG").is_ok() { + env_logger::init(); + } +} + +#[test] +fn test_edit() { + let mut buffer = Buffer::new(0, 0, "abc".into()); + assert_eq!(buffer.text(), "abc"); + buffer.edit([(3..3, "def")]); + assert_eq!(buffer.text(), "abcdef"); + buffer.edit([(0..0, "ghi")]); + assert_eq!(buffer.text(), "ghiabcdef"); + buffer.edit([(5..5, "jkl")]); + assert_eq!(buffer.text(), "ghiabjklcdef"); + buffer.edit([(6..7, "")]); + assert_eq!(buffer.text(), "ghiabjlcdef"); + buffer.edit([(4..9, "mno")]); + assert_eq!(buffer.text(), "ghiamnoef"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_edits(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let reference_string_len = rng.gen_range(0..3); + let mut reference_string = RandomCharIter::new(&mut rng) + .take(reference_string_len) + .collect::(); + let mut buffer = Buffer::new(0, 0, reference_string.clone()); + LineEnding::normalize(&mut reference_string); + + buffer.set_group_interval(Duration::from_millis(rng.gen_range(0..=200))); + let mut buffer_versions = Vec::new(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + for _i in 0..operations { + let (edits, _) = buffer.randomly_edit(&mut rng, 5); + for (old_range, new_text) in edits.iter().rev() { + reference_string.replace_range(old_range.clone(), new_text); + } + + assert_eq!(buffer.text(), reference_string); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + if rng.gen_bool(0.25) { + buffer.randomly_undo_redo(&mut rng); + reference_string = buffer.text(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + } + + let range = buffer.random_byte_range(0, &mut rng); + assert_eq!( + buffer.text_summary_for_range::(range.clone()), + TextSummary::from(&reference_string[range]) + ); + + buffer.check_invariants(); + + if rng.gen_bool(0.3) { + buffer_versions.push((buffer.clone(), buffer.subscribe())); + } + } + + for (old_buffer, subscription) in buffer_versions { + let edits = buffer + .edits_since::(&old_buffer.version) + .collect::>(); + + log::info!( + "applying edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in edits { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + + for _ in 0..5 { + let end_ix = old_buffer.clip_offset(rng.gen_range(0..=old_buffer.len()), Bias::Right); + let start_ix = old_buffer.clip_offset(rng.gen_range(0..=end_ix), Bias::Left); + let range = old_buffer.anchor_before(start_ix)..old_buffer.anchor_after(end_ix); + let mut old_text = old_buffer.text_for_range(range.clone()).collect::(); + let edits = buffer + .edits_since_in_range::(&old_buffer.version, range.clone()) + .collect::>(); + log::info!( + "applying edits since version {:?} to old text in range {:?}: {:?}: {:?}", + old_buffer.version(), + start_ix..end_ix, + old_text, + edits, + ); + + let new_text = buffer.text_for_range(range).collect::(); + for edit in edits { + old_text.replace_range( + edit.new.start..edit.new.start + edit.old_len(), + &new_text[edit.new], + ); + } + assert_eq!(old_text, new_text); + } + + let subscription_edits = subscription.consume(); + log::info!( + "applying subscription edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + subscription_edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in subscription_edits.into_inner() { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + } +} + +#[test] +fn test_line_endings() { + assert_eq!(LineEnding::detect(&"🍐✅\n".repeat(1000)), LineEnding::Unix); + assert_eq!(LineEnding::detect(&"abcd\n".repeat(1000)), LineEnding::Unix); + assert_eq!( + LineEnding::detect(&"🍐✅\r\n".repeat(1000)), + LineEnding::Windows + ); + assert_eq!( + LineEnding::detect(&"abcd\r\n".repeat(1000)), + LineEnding::Windows + ); + + let mut buffer = Buffer::new(0, 0, "one\r\ntwo\rthree".into()); + assert_eq!(buffer.text(), "one\ntwo\nthree"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); + + buffer.edit([(buffer.len()..buffer.len(), "\r\nfour")]); + buffer.edit([(0..0, "zero\r\n")]); + assert_eq!(buffer.text(), "zero\none\ntwo\nthree\nfour"); + assert_eq!(buffer.line_ending(), LineEnding::Windows); + buffer.check_invariants(); +} + +#[test] +fn test_line_len() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abcd\nefg\nhij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs\n")]); + buffer.edit([(18..21, "\nPQ")]); + + assert_eq!(buffer.line_len(0), 4); + assert_eq!(buffer.line_len(1), 3); + assert_eq!(buffer.line_len(2), 5); + assert_eq!(buffer.line_len(3), 3); + assert_eq!(buffer.line_len(4), 4); + assert_eq!(buffer.line_len(5), 0); +} + +#[test] +fn test_common_prefix_at_position() { + let text = "a = str; b = δα"; + let buffer = Buffer::new(0, 0, text.into()); + + let offset1 = offset_after(text, "str"); + let offset2 = offset_after(text, "δα"); + + // the preceding word is a prefix of the suggestion + assert_eq!( + buffer.common_prefix_at(offset1, "string"), + range_of(text, "str"), + ); + // a suffix of the preceding word is a prefix of the suggestion + assert_eq!( + buffer.common_prefix_at(offset1, "tree"), + range_of(text, "tr"), + ); + // the preceding word is a substring of the suggestion, but not a prefix + assert_eq!( + buffer.common_prefix_at(offset1, "astro"), + empty_range_after(text, "str"), + ); + + // prefix matching is case insensitive. + assert_eq!( + buffer.common_prefix_at(offset1, "Strαngε"), + range_of(text, "str"), + ); + assert_eq!( + buffer.common_prefix_at(offset2, "ΔΑΜΝ"), + range_of(text, "δα"), + ); + + fn offset_after(text: &str, part: &str) -> usize { + text.find(part).unwrap() + part.len() + } + + fn empty_range_after(text: &str, part: &str) -> Range { + let offset = offset_after(text, part); + offset..offset + } + + fn range_of(text: &str, part: &str) -> Range { + let start = text.find(part).unwrap(); + start..start + part.len() + } +} + +#[test] +fn test_text_summary_for_range() { + let buffer = Buffer::new(0, 0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()); + assert_eq!( + buffer.text_summary_for_range::(1..3), + TextSummary { + len: 2, + len_utf16: OffsetUtf16(2), + lines: Point::new(1, 0), + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 0, + longest_row_chars: 1, + } + ); + assert_eq!( + buffer.text_summary_for_range::(1..12), + TextSummary { + len: 11, + len_utf16: OffsetUtf16(11), + lines: Point::new(3, 0), + first_line_chars: 1, + last_line_chars: 0, + last_line_len_utf16: 0, + longest_row: 2, + longest_row_chars: 4, + } + ); + assert_eq!( + buffer.text_summary_for_range::(0..20), + TextSummary { + len: 20, + len_utf16: OffsetUtf16(20), + lines: Point::new(4, 1), + first_line_chars: 2, + last_line_chars: 1, + last_line_len_utf16: 1, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range::(0..22), + TextSummary { + len: 22, + len_utf16: OffsetUtf16(22), + lines: Point::new(4, 3), + first_line_chars: 2, + last_line_chars: 3, + last_line_len_utf16: 3, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range::(7..22), + TextSummary { + len: 15, + len_utf16: OffsetUtf16(15), + lines: Point::new(2, 3), + first_line_chars: 4, + last_line_chars: 3, + last_line_len_utf16: 3, + longest_row: 1, + longest_row_chars: 6, + } + ); +} + +#[test] +fn test_chars_at() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abcd\nefgh\nij")]); + buffer.edit([(12..12, "kl\nmno")]); + buffer.edit([(18..18, "\npqrs")]); + buffer.edit([(18..21, "\nPQ")]); + + let chars = buffer.chars_at(Point::new(0, 0)); + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(1, 0)); + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(2, 0)); + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + + let chars = buffer.chars_at(Point::new(3, 0)); + assert_eq!(chars.collect::(), "mno\nPQrs"); + + let chars = buffer.chars_at(Point::new(4, 0)); + assert_eq!(chars.collect::(), "PQrs"); + + // Regression test: + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n")]); + buffer.edit([(60..60, "\n")]); + + let chars = buffer.chars_at(Point::new(6, 0)); + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); +} + +#[test] +fn test_anchors() { + let mut buffer = Buffer::new(0, 0, "".into()); + buffer.edit([(0..0, "abc")]); + let left_anchor = buffer.anchor_before(2); + let right_anchor = buffer.anchor_after(2); + + buffer.edit([(1..1, "def\n")]); + assert_eq!(buffer.text(), "adef\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 6); + assert_eq!(right_anchor.to_offset(&buffer), 6); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit([(2..3, "")]); + assert_eq!(buffer.text(), "adf\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 5); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + + buffer.edit([(5..5, "ghi\n")]); + assert_eq!(buffer.text(), "adf\nbghi\nc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 9); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); + + buffer.edit([(7..9, "")]); + assert_eq!(buffer.text(), "adf\nbghc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 7); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); + + // Ensure anchoring to a point is equivalent to anchoring to an offset. + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 0 }), + buffer.anchor_before(0) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 1 }), + buffer.anchor_before(1) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 2 }), + buffer.anchor_before(2) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 3 }), + buffer.anchor_before(3) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 0 }), + buffer.anchor_before(4) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 1 }), + buffer.anchor_before(5) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 2 }), + buffer.anchor_before(6) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 3 }), + buffer.anchor_before(7) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 4 }), + buffer.anchor_before(8) + ); + + // Comparison between anchors. + let anchor_at_offset_0 = buffer.anchor_before(0); + let anchor_at_offset_1 = buffer.anchor_before(1); + let anchor_at_offset_2 = buffer.anchor_before(2); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_0, &buffer), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_1, &buffer), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_2, &buffer), + Ordering::Equal + ); + + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_1, &buffer), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_2, &buffer), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_0.cmp(&anchor_at_offset_2, &buffer), + Ordering::Less + ); + + assert_eq!( + anchor_at_offset_1.cmp(&anchor_at_offset_0, &buffer), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_1, &buffer), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2.cmp(&anchor_at_offset_0, &buffer), + Ordering::Greater + ); +} + +#[test] +fn test_anchors_at_start_and_end() { + let mut buffer = Buffer::new(0, 0, "".into()); + let before_start_anchor = buffer.anchor_before(0); + let after_end_anchor = buffer.anchor_after(0); + + buffer.edit([(0..0, "abc")]); + assert_eq!(buffer.text(), "abc"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_end_anchor.to_offset(&buffer), 3); + + let after_start_anchor = buffer.anchor_after(0); + let before_end_anchor = buffer.anchor_before(3); + + buffer.edit([(3..3, "def")]); + buffer.edit([(0..0, "ghi")]); + assert_eq!(buffer.text(), "ghiabcdef"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_start_anchor.to_offset(&buffer), 3); + assert_eq!(before_end_anchor.to_offset(&buffer), 6); + assert_eq!(after_end_anchor.to_offset(&buffer), 9); +} + +#[test] +fn test_undo_redo() { + let mut buffer = Buffer::new(0, 0, "1234".into()); + // Set group interval to zero so as to not group edits in the undo stack. + buffer.set_group_interval(Duration::from_secs(0)); + + buffer.edit([(1..1, "abx")]); + buffer.edit([(3..4, "yzef")]); + buffer.edit([(3..5, "cd")]); + assert_eq!(buffer.text(), "1abcdef234"); + + let entries = buffer.history.undo_stack.clone(); + assert_eq!(entries.len(), 3); + + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1cdef234"); + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdx234"); + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abx234"); + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(entries[2].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(entries[0].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1yzef234"); + buffer.undo_or_redo(entries[1].transaction.clone()).unwrap(); + assert_eq!(buffer.text(), "1234"); +} + +#[test] +fn test_history() { + let mut now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "123456".into()); + buffer.set_group_interval(Duration::from_millis(300)); + + let transaction_1 = buffer.start_transaction_at(now).unwrap(); + buffer.edit([(2..4, "cd")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56"); + + buffer.start_transaction_at(now); + buffer.edit([(4..5, "e")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + + now += buffer.transaction_group_interval() + Duration::from_millis(1); + buffer.start_transaction_at(now); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + + // Last transaction happened past the group interval, undo it on its own. + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + + // First two transactions happened within the group interval, undo them together. + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + // Redo the first two transactions together. + buffer.redo(); + assert_eq!(buffer.text(), "12cde6"); + + // Redo the last transaction on its own. + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); + + buffer.start_transaction_at(now); + assert!(buffer.end_transaction_at(now).is_none()); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + + // Redo stack gets cleared after performing an edit. + buffer.start_transaction_at(now); + buffer.edit([(0..0, "X")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "X12cde6"); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + // Transactions can be grouped manually. + buffer.redo(); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); + buffer.group_until_transaction(transaction_1); + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + buffer.redo(); + assert_eq!(buffer.text(), "X12cde6"); +} + +#[test] +fn test_finalize_last_transaction() { + let now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "123456".into()); + + buffer.start_transaction_at(now); + buffer.edit([(2..4, "cd")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56"); + + buffer.finalize_last_transaction(); + buffer.start_transaction_at(now); + buffer.edit([(4..5, "e")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + + buffer.start_transaction_at(now); + buffer.edit([(0..1, "a")]); + buffer.edit([(1..1, "b")]); + buffer.end_transaction_at(now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + + buffer.undo(); + assert_eq!(buffer.text(), "12cd56"); + + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + + buffer.redo(); + assert_eq!(buffer.text(), "12cd56"); + + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); +} + +#[test] +fn test_edited_ranges_for_transaction() { + let now = Instant::now(); + let mut buffer = Buffer::new(0, 0, "1234567".into()); + + buffer.start_transaction_at(now); + buffer.edit([(2..4, "cd")]); + buffer.edit([(6..6, "efg")]); + buffer.end_transaction_at(now); + assert_eq!(buffer.text(), "12cd56efg7"); + + let tx = buffer.finalize_last_transaction().unwrap().clone(); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 6..9] + ); + + buffer.edit([(5..5, "hijk")]); + assert_eq!(buffer.text(), "12cd5hijk6efg7"); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 10..13] + ); + + buffer.edit([(4..4, "l")]); + assert_eq!(buffer.text(), "12cdl5hijk6efg7"); + assert_eq!( + buffer + .edited_ranges_for_transaction::(&tx) + .collect::>(), + [2..4, 11..14] + ); +} + +#[test] +fn test_concurrent_edits() { + let text = "abcdef"; + + let mut buffer1 = Buffer::new(1, 0, text.into()); + let mut buffer2 = Buffer::new(2, 0, text.into()); + let mut buffer3 = Buffer::new(3, 0, text.into()); + + let buf1_op = buffer1.edit([(1..2, "12")]); + assert_eq!(buffer1.text(), "a12cdef"); + let buf2_op = buffer2.edit([(3..4, "34")]); + assert_eq!(buffer2.text(), "abc34ef"); + let buf3_op = buffer3.edit([(5..6, "56")]); + assert_eq!(buffer3.text(), "abcde56"); + + buffer1.apply_op(buf2_op.clone()).unwrap(); + buffer1.apply_op(buf3_op.clone()).unwrap(); + buffer2.apply_op(buf1_op.clone()).unwrap(); + buffer2.apply_op(buf3_op).unwrap(); + buffer3.apply_op(buf1_op).unwrap(); + buffer3.apply_op(buf2_op).unwrap(); + + assert_eq!(buffer1.text(), "a12c34e56"); + assert_eq!(buffer2.text(), "a12c34e56"); + assert_eq!(buffer3.text(), "a12c34e56"); +} + +#[gpui2::test(iterations = 100)] +fn test_random_concurrent_edits(mut rng: StdRng) { + let peers = env::var("PEERS") + .map(|i| i.parse().expect("invalid `PEERS` variable")) + .unwrap_or(5); + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let base_text_len = rng.gen_range(0..10); + let base_text = RandomCharIter::new(&mut rng) + .take(base_text_len) + .collect::(); + let mut replica_ids = Vec::new(); + let mut buffers = Vec::new(); + let mut network = Network::new(rng.clone()); + + for i in 0..peers { + let mut buffer = Buffer::new(i as ReplicaId, 0, base_text.clone()); + buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + buffers.push(buffer); + replica_ids.push(i as u16); + network.add_peer(i as u16); + } + + log::info!("initial text: {:?}", base_text); + + let mut mutation_count = operations; + loop { + let replica_index = rng.gen_range(0..peers); + let replica_id = replica_ids[replica_index]; + let buffer = &mut buffers[replica_index]; + match rng.gen_range(0..=100) { + 0..=50 if mutation_count != 0 => { + let op = buffer.randomly_edit(&mut rng, 5).1; + network.broadcast(buffer.replica_id, vec![op]); + log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); + mutation_count -= 1; + } + 51..=70 if mutation_count != 0 => { + let ops = buffer.randomly_undo_redo(&mut rng); + network.broadcast(buffer.replica_id, ops); + mutation_count -= 1; + } + 71..=100 if network.has_unreceived(replica_id) => { + let ops = network.receive(replica_id); + if !ops.is_empty() { + log::info!( + "peer {} applying {} ops from the network.", + replica_id, + ops.len() + ); + buffer.apply_ops(ops).unwrap(); + } + } + _ => {} + } + buffer.check_invariants(); + + if mutation_count == 0 && network.is_idle() { + break; + } + } + + let first_buffer = &buffers[0]; + for buffer in &buffers[1..] { + assert_eq!( + buffer.text(), + first_buffer.text(), + "Replica {} text != Replica 0 text", + buffer.replica_id + ); + buffer.check_invariants(); + } +} diff --git a/crates/text2/src/text2.rs b/crates/text2/src/text2.rs new file mode 100644 index 0000000000..c05ea1109c --- /dev/null +++ b/crates/text2/src/text2.rs @@ -0,0 +1,2682 @@ +mod anchor; +pub mod locator; +#[cfg(any(test, feature = "test-support"))] +pub mod network; +pub mod operation_queue; +mod patch; +mod selection; +pub mod subscription; +#[cfg(test)] +mod tests; +mod undo_map; + +pub use anchor::*; +use anyhow::{anyhow, Result}; +pub use clock::ReplicaId; +use collections::{HashMap, HashSet}; +use locator::Locator; +use operation_queue::OperationQueue; +pub use patch::Patch; +use postage::{oneshot, prelude::*}; + +use lazy_static::lazy_static; +use regex::Regex; +pub use rope::*; +pub use selection::*; +use std::{ + borrow::Cow, + cmp::{self, Ordering, Reverse}, + future::Future, + iter::Iterator, + ops::{self, Deref, Range, Sub}, + str, + sync::Arc, + time::{Duration, Instant}, +}; +pub use subscription::*; +pub use sum_tree::Bias; +use sum_tree::{FilterCursor, SumTree, TreeMap}; +use undo_map::UndoMap; +use util::ResultExt; + +#[cfg(any(test, feature = "test-support"))] +use util::RandomCharIter; + +lazy_static! { + static ref LINE_SEPARATORS_REGEX: Regex = Regex::new("\r\n|\r|\u{2028}|\u{2029}").unwrap(); +} + +pub type TransactionId = clock::Lamport; + +pub struct Buffer { + snapshot: BufferSnapshot, + history: History, + deferred_ops: OperationQueue, + deferred_replicas: HashSet, + pub lamport_clock: clock::Lamport, + subscriptions: Topic, + edit_id_resolvers: HashMap>>, + wait_for_version_txs: Vec<(clock::Global, oneshot::Sender<()>)>, +} + +#[derive(Clone)] +pub struct BufferSnapshot { + replica_id: ReplicaId, + remote_id: u64, + visible_text: Rope, + deleted_text: Rope, + line_ending: LineEnding, + undo_map: UndoMap, + fragments: SumTree, + insertions: SumTree, + pub version: clock::Global, +} + +#[derive(Clone, Debug)] +pub struct HistoryEntry { + transaction: Transaction, + first_edit_at: Instant, + last_edit_at: Instant, + suppress_grouping: bool, +} + +#[derive(Clone, Debug)] +pub struct Transaction { + pub id: TransactionId, + pub edit_ids: Vec, + pub start: clock::Global, +} + +impl HistoryEntry { + pub fn transaction_id(&self) -> TransactionId { + self.transaction.id + } +} + +struct History { + base_text: Rope, + operations: TreeMap, + insertion_slices: HashMap>, + undo_stack: Vec, + redo_stack: Vec, + transaction_depth: usize, + group_interval: Duration, +} + +#[derive(Clone, Debug)] +struct InsertionSlice { + insertion_id: clock::Lamport, + range: Range, +} + +impl History { + pub fn new(base_text: Rope) -> Self { + Self { + base_text, + operations: Default::default(), + insertion_slices: Default::default(), + undo_stack: Vec::new(), + redo_stack: Vec::new(), + transaction_depth: 0, + // Don't group transactions in tests unless we opt in, because it's a footgun. + #[cfg(any(test, feature = "test-support"))] + group_interval: Duration::ZERO, + #[cfg(not(any(test, feature = "test-support")))] + group_interval: Duration::from_millis(300), + } + } + + fn push(&mut self, op: Operation) { + self.operations.insert(op.timestamp(), op); + } + + fn start_transaction( + &mut self, + start: clock::Global, + now: Instant, + clock: &mut clock::Lamport, + ) -> Option { + self.transaction_depth += 1; + if self.transaction_depth == 1 { + let id = clock.tick(); + self.undo_stack.push(HistoryEntry { + transaction: Transaction { + id, + start, + edit_ids: Default::default(), + }, + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + Some(id) + } else { + None + } + } + + fn end_transaction(&mut self, now: Instant) -> Option<&HistoryEntry> { + assert_ne!(self.transaction_depth, 0); + self.transaction_depth -= 1; + if self.transaction_depth == 0 { + if self + .undo_stack + .last() + .unwrap() + .transaction + .edit_ids + .is_empty() + { + self.undo_stack.pop(); + None + } else { + self.redo_stack.clear(); + let entry = self.undo_stack.last_mut().unwrap(); + entry.last_edit_at = now; + Some(entry) + } + } else { + None + } + } + + fn group(&mut self) -> Option { + let mut count = 0; + let mut entries = self.undo_stack.iter(); + if let Some(mut entry) = entries.next_back() { + while let Some(prev_entry) = entries.next_back() { + if !prev_entry.suppress_grouping + && entry.first_edit_at - prev_entry.last_edit_at <= self.group_interval + { + entry = prev_entry; + count += 1; + } else { + break; + } + } + } + self.group_trailing(count) + } + + fn group_until(&mut self, transaction_id: TransactionId) { + let mut count = 0; + for entry in self.undo_stack.iter().rev() { + if entry.transaction_id() == transaction_id { + self.group_trailing(count); + break; + } else if entry.suppress_grouping { + break; + } else { + count += 1; + } + } + } + + fn group_trailing(&mut self, n: usize) -> Option { + let new_len = self.undo_stack.len() - n; + let (entries_to_keep, entries_to_merge) = self.undo_stack.split_at_mut(new_len); + if let Some(last_entry) = entries_to_keep.last_mut() { + for entry in &*entries_to_merge { + for edit_id in &entry.transaction.edit_ids { + last_entry.transaction.edit_ids.push(*edit_id); + } + } + + if let Some(entry) = entries_to_merge.last_mut() { + last_entry.last_edit_at = entry.last_edit_at; + } + } + + self.undo_stack.truncate(new_len); + self.undo_stack.last().map(|e| e.transaction.id) + } + + fn finalize_last_transaction(&mut self) -> Option<&Transaction> { + self.undo_stack.last_mut().map(|entry| { + entry.suppress_grouping = true; + &entry.transaction + }) + } + + fn push_transaction(&mut self, transaction: Transaction, now: Instant) { + assert_eq!(self.transaction_depth, 0); + self.undo_stack.push(HistoryEntry { + transaction, + first_edit_at: now, + last_edit_at: now, + suppress_grouping: false, + }); + self.redo_stack.clear(); + } + + fn push_undo(&mut self, op_id: clock::Lamport) { + assert_ne!(self.transaction_depth, 0); + if let Some(Operation::Edit(_)) = self.operations.get(&op_id) { + let last_transaction = self.undo_stack.last_mut().unwrap(); + last_transaction.transaction.edit_ids.push(op_id); + } + } + + fn pop_undo(&mut self) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + if let Some(entry) = self.undo_stack.pop() { + self.redo_stack.push(entry); + self.redo_stack.last() + } else { + None + } + } + + fn remove_from_undo(&mut self, transaction_id: TransactionId) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + + let entry_ix = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id)?; + let entry = self.undo_stack.remove(entry_ix); + self.redo_stack.push(entry); + self.redo_stack.last() + } + + fn remove_from_undo_until(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + assert_eq!(self.transaction_depth, 0); + + let redo_stack_start_len = self.redo_stack.len(); + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + self.redo_stack + .extend(self.undo_stack.drain(entry_ix..).rev()); + } + &self.redo_stack[redo_stack_start_len..] + } + + fn forget(&mut self, transaction_id: TransactionId) -> Option { + assert_eq!(self.transaction_depth, 0); + if let Some(entry_ix) = self + .undo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + Some(self.undo_stack.remove(entry_ix).transaction) + } else if let Some(entry_ix) = self + .redo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + Some(self.redo_stack.remove(entry_ix).transaction) + } else { + None + } + } + + fn transaction_mut(&mut self, transaction_id: TransactionId) -> Option<&mut Transaction> { + let entry = self + .undo_stack + .iter_mut() + .rfind(|entry| entry.transaction.id == transaction_id) + .or_else(|| { + self.redo_stack + .iter_mut() + .rfind(|entry| entry.transaction.id == transaction_id) + })?; + Some(&mut entry.transaction) + } + + fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { + if let Some(transaction) = self.forget(transaction) { + if let Some(destination) = self.transaction_mut(destination) { + destination.edit_ids.extend(transaction.edit_ids); + } + } + } + + fn pop_redo(&mut self) -> Option<&HistoryEntry> { + assert_eq!(self.transaction_depth, 0); + if let Some(entry) = self.redo_stack.pop() { + self.undo_stack.push(entry); + self.undo_stack.last() + } else { + None + } + } + + fn remove_from_redo(&mut self, transaction_id: TransactionId) -> &[HistoryEntry] { + assert_eq!(self.transaction_depth, 0); + + let undo_stack_start_len = self.undo_stack.len(); + if let Some(entry_ix) = self + .redo_stack + .iter() + .rposition(|entry| entry.transaction.id == transaction_id) + { + self.undo_stack + .extend(self.redo_stack.drain(entry_ix..).rev()); + } + &self.undo_stack[undo_stack_start_len..] + } +} + +struct Edits<'a, D: TextDimension, F: FnMut(&FragmentSummary) -> bool> { + visible_cursor: rope::Cursor<'a>, + deleted_cursor: rope::Cursor<'a>, + fragments_cursor: Option>, + undos: &'a UndoMap, + since: &'a clock::Global, + old_end: D, + new_end: D, + range: Range<(&'a Locator, usize)>, + buffer_id: u64, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Edit { + pub old: Range, + pub new: Range, +} + +impl Edit +where + D: Sub + PartialEq + Copy, +{ + pub fn old_len(&self) -> D { + self.old.end - self.old.start + } + + pub fn new_len(&self) -> D { + self.new.end - self.new.start + } + + pub fn is_empty(&self) -> bool { + self.old.start == self.old.end && self.new.start == self.new.end + } +} + +impl Edit<(D1, D2)> { + pub fn flatten(self) -> (Edit, Edit) { + ( + Edit { + old: self.old.start.0..self.old.end.0, + new: self.new.start.0..self.new.end.0, + }, + Edit { + old: self.old.start.1..self.old.end.1, + new: self.new.start.1..self.new.end.1, + }, + ) + } +} + +#[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: HashSet, + pub max_undos: clock::Global, +} + +#[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, +} + +#[derive(Copy, Default, Clone, Debug, PartialEq, Eq)] +struct FragmentTextSummary { + visible: usize, + 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: clock::Lamport, + split_offset: usize, + fragment_id: Locator, +} + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord)] +struct InsertionFragmentKey { + timestamp: clock::Lamport, + split_offset: usize, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum Operation { + Edit(EditOperation), + Undo(UndoOperation), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EditOperation { + pub timestamp: clock::Lamport, + pub version: clock::Global, + pub ranges: Vec>, + pub new_text: Vec>, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct UndoOperation { + pub timestamp: clock::Lamport, + pub version: clock::Global, + pub counts: HashMap, +} + +impl Buffer { + pub fn new(replica_id: u16, remote_id: u64, mut base_text: String) -> Buffer { + let line_ending = LineEnding::detect(&base_text); + LineEnding::normalize(&mut base_text); + + let history = History::new(Rope::from(base_text.as_ref())); + let mut fragments = SumTree::new(); + let mut insertions = SumTree::new(); + + let mut lamport_clock = clock::Lamport::new(replica_id); + let mut version = clock::Global::new(); + + let visible_text = history.base_text.clone(); + if !visible_text.is_empty() { + let insertion_timestamp = clock::Lamport { + replica_id: 0, + value: 1, + }; + 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); + } + + Buffer { + snapshot: BufferSnapshot { + replica_id, + remote_id, + visible_text, + deleted_text: Rope::new(), + line_ending, + fragments, + insertions, + version, + undo_map: Default::default(), + }, + history, + deferred_ops: OperationQueue::new(), + deferred_replicas: HashSet::default(), + lamport_clock, + subscriptions: Default::default(), + edit_id_resolvers: Default::default(), + wait_for_version_txs: Default::default(), + } + } + + pub fn version(&self) -> clock::Global { + self.version.clone() + } + + pub fn snapshot(&self) -> BufferSnapshot { + self.snapshot.clone() + } + + pub fn replica_id(&self) -> ReplicaId { + self.lamport_clock.replica_id + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn deferred_ops_len(&self) -> usize { + self.deferred_ops.len() + } + + pub fn transaction_group_interval(&self) -> Duration { + self.history.group_interval + } + + pub fn edit(&mut self, edits: R) -> Operation + where + R: IntoIterator, + I: ExactSizeIterator, T)>, + S: ToOffset, + T: Into>, + { + let edits = edits + .into_iter() + .map(|(range, new_text)| (range, new_text.into())); + + self.start_transaction(); + let timestamp = self.lamport_clock.tick(); + let operation = Operation::Edit(self.apply_local_edit(edits, timestamp)); + + self.history.push(operation.clone()); + self.history.push_undo(operation.timestamp()); + self.snapshot.version.observe(operation.timestamp()); + self.end_transaction(); + operation + } + + fn apply_local_edit>>( + &mut self, + edits: impl ExactSizeIterator, T)>, + timestamp: clock::Lamport, + ) -> EditOperation { + let mut edits_patch = Patch::default(); + let mut edit_op = EditOperation { + timestamp, + version: self.version(), + ranges: Vec::with_capacity(edits.len()), + new_text: Vec::with_capacity(edits.len()), + }; + 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)) + .peekable(); + + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::(); + let mut new_fragments = + old_fragments.slice(&edits.peek().unwrap().0.start, Bias::Right, &None); + new_ropes.append(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().visible; + for (range, new_text) in edits { + let new_text = LineEnding::normalize_arc(new_text.into()); + let fragment_end = old_fragments.end(&None).visible; + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + 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; + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let slice = old_fragments.slice(&range.start, Bias::Right, &None); + new_ropes.append(slice.summary().text); + new_fragments.append(slice, &None); + fragment_start = old_fragments.start().visible; + } + + let full_range_start = FullOffset(range.start + old_fragments.start().deleted); + + // 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.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + fragment_start = range.start; + } + + // Insert the new text before any existing fragments within the range. + 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), + ), + timestamp, + insertion_offset, + len: new_text.len(), + deletions: Default::default(), + 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); + insertion_offset += new_text.len(); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&None).visible; + 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.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); + intersection.deletions.insert(timestamp); + intersection.visible = false; + } + if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let new_start = new_fragments.summary().text.visible; + edits_patch.push(Edit { + 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); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&None); + } + } + + let full_range_end = FullOffset(range.end + old_fragments.start().deleted); + edit_op.ranges.push(full_range_start..full_range_end); + edit_op.new_text.push(new_text); + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + let fragment_end = old_fragments.end(&None).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; + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let suffix = old_fragments.suffix(&None); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.snapshot.fragments = new_fragments; + self.snapshot.insertions.edit(new_insertions, &()); + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.subscriptions.publish_mut(&edits_patch); + self.history + .insertion_slices + .insert(timestamp, insertion_slices); + edit_op + } + + pub fn set_line_ending(&mut self, line_ending: LineEnding) { + self.snapshot.line_ending = line_ending; + } + + pub fn apply_ops>(&mut self, ops: I) -> Result<()> { + let mut deferred_ops = Vec::new(); + for op in ops { + self.history.push(op.clone()); + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + self.flush_deferred_ops()?; + Ok(()) + } + + fn apply_op(&mut self, op: Operation) -> Result<()> { + match op { + Operation::Edit(edit) => { + if !self.version.observed(edit.timestamp) { + self.apply_remote_edit( + &edit.version, + &edit.ranges, + &edit.new_text, + edit.timestamp, + ); + self.snapshot.version.observe(edit.timestamp); + self.lamport_clock.observe(edit.timestamp); + self.resolve_edit(edit.timestamp); + } + } + Operation::Undo(undo) => { + if !self.version.observed(undo.timestamp) { + self.apply_undo(&undo)?; + self.snapshot.version.observe(undo.timestamp); + self.lamport_clock.observe(undo.timestamp); + } + } + } + self.wait_for_version_txs.retain_mut(|(version, tx)| { + if self.snapshot.version().observed_all(version) { + tx.try_send(()).ok(); + false + } else { + true + } + }); + Ok(()) + } + + fn apply_remote_edit( + &mut self, + version: &clock::Global, + ranges: &[Range], + new_text: &[Arc], + timestamp: clock::Lamport, + ) { + if ranges.is_empty() { + return; + } + + let edits = ranges.iter().zip(new_text.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; + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); + let mut new_fragments = old_fragments.slice( + &VersionedFullOffset::Offset(ranges[0].start), + Bias::Left, + &cx, + ); + new_ropes.append(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().0.full_offset(); + for (range, new_text) in edits { + let fragment_end = old_fragments.end(&cx).0.full_offset(); + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + 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.insertion_offset += + fragment_start - old_fragments.start().0.full_offset(); + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let slice = + old_fragments.slice(&VersionedFullOffset::Offset(range.start), Bias::Left, &cx); + new_ropes.append(slice.summary().text); + new_fragments.append(slice, &None); + fragment_start = old_fragments.start().0.full_offset(); + } + + // If we are at the end of a non-concurrent fragment, advance to the next one. + let fragment_end = old_fragments.end(&cx).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(); + new_insertions.push(InsertionFragment::insert_new(&fragment)); + new_ropes.push_fragment(&fragment, fragment.visible); + new_fragments.push(fragment, &None); + old_fragments.next(&cx); + fragment_start = old_fragments.start().0.full_offset(); + } + + // Skip over insertions that are concurrent to this edit, but have a lower lamport + // timestamp. + while let Some(fragment) = old_fragments.item() { + if fragment_start == range.start && fragment.timestamp > timestamp { + new_ropes.push_fragment(fragment, fragment.visible); + new_fragments.push(fragment.clone(), &None); + old_fragments.next(&cx); + debug_assert_eq!(fragment_start, range.start); + } else { + break; + } + } + debug_assert!(fragment_start <= range.start); + + // 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.id = Locator::between(&new_fragments.summary().max_id, &prefix.id); + new_insertions.push(InsertionFragment::insert_new(&prefix)); + fragment_start = range.start; + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + } + + // Insert the new text before any existing fragments within the range. + if !new_text.is_empty() { + let mut old_start = old_fragments.start().1; + if old_fragments.item().map_or(false, |f| f.visible) { + 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), + ), + timestamp, + insertion_offset, + len: new_text.len(), + deletions: Default::default(), + 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); + insertion_offset += new_text.len(); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); + 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.insertion_offset += + fragment_start - old_fragments.start().0.full_offset(); + intersection.id = + Locator::between(&new_fragments.summary().max_id, &intersection.id); + intersection.deletions.insert(timestamp); + intersection.visible = false; + insertion_slices.push(intersection.insertion_slice()); + } + if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let old_start = old_fragments.start().1 + + (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, + new: new_start..new_start, + }); + } + new_insertions.push(InsertionFragment::insert_new(&intersection)); + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&cx); + } + } + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().0.full_offset() { + let fragment_end = old_fragments.end(&cx).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(); + new_insertions.push(InsertionFragment::insert_new(&suffix)); + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let suffix = old_fragments.suffix(&cx); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.snapshot.fragments = new_fragments; + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.snapshot.insertions.edit(new_insertions, &()); + self.history + .insertion_slices + .insert(timestamp, insertion_slices); + self.subscriptions.publish_mut(&edits_patch) + } + + 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))); + + // 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(&()); + } + } + 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)); + + for fragment_id in self.fragment_ids_for_edits(undo.counts.keys()) { + let preceding_fragments = old_fragments.slice(&Some(fragment_id), Bias::Left, &None); + new_ropes.append(preceding_fragments.summary().text); + new_fragments.append(preceding_fragments, &None); + + if let Some(fragment) = old_fragments.item() { + let mut fragment = fragment.clone(); + let fragment_was_visible = fragment.visible; + + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.timestamp); + + 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(&None); + new_ropes.append(suffix.summary().text); + new_fragments.append(suffix, &None); + + drop(old_fragments); + let (visible_text, deleted_text) = new_ropes.finish(); + self.snapshot.fragments = new_fragments; + self.snapshot.visible_text = visible_text; + self.snapshot.deleted_text = deleted_text; + self.subscriptions.publish_mut(&edits); + Ok(()) + } + + fn flush_deferred_ops(&mut self) -> Result<()> { + self.deferred_replicas.clear(); + let mut deferred_ops = Vec::new(); + for op in self.deferred_ops.drain().iter().cloned() { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + Ok(()) + } + + fn can_apply_op(&self, op: &Operation) -> bool { + if self.deferred_replicas.contains(&op.replica_id()) { + false + } else { + self.version.observed_all(match op { + Operation::Edit(edit) => &edit.version, + Operation::Undo(undo) => &undo.version, + }) + } + } + + pub fn peek_undo_stack(&self) -> Option<&HistoryEntry> { + self.history.undo_stack.last() + } + + pub fn peek_redo_stack(&self) -> Option<&HistoryEntry> { + self.history.redo_stack.last() + } + + pub fn start_transaction(&mut self) -> Option { + self.start_transaction_at(Instant::now()) + } + + pub fn start_transaction_at(&mut self, now: Instant) -> Option { + self.history + .start_transaction(self.version.clone(), now, &mut self.lamport_clock) + } + + pub fn end_transaction(&mut self) -> Option<(TransactionId, clock::Global)> { + self.end_transaction_at(Instant::now()) + } + + pub fn end_transaction_at(&mut self, now: Instant) -> Option<(TransactionId, clock::Global)> { + if let Some(entry) = self.history.end_transaction(now) { + let since = entry.transaction.start.clone(); + let id = self.history.group().unwrap(); + Some((id, since)) + } else { + None + } + } + + pub fn finalize_last_transaction(&mut self) -> Option<&Transaction> { + self.history.finalize_last_transaction() + } + + pub fn group_until_transaction(&mut self, transaction_id: TransactionId) { + self.history.group_until(transaction_id); + } + + pub fn base_text(&self) -> &Rope { + &self.history.base_text + } + + pub fn operations(&self) -> &TreeMap { + &self.history.operations + } + + pub fn undo(&mut self) -> Option<(TransactionId, Operation)> { + if let Some(entry) = self.history.pop_undo() { + let transaction = entry.transaction.clone(); + let transaction_id = transaction.id; + let op = self.undo_or_redo(transaction).unwrap(); + Some((transaction_id, op)) + } else { + None + } + } + + pub fn undo_transaction(&mut self, transaction_id: TransactionId) -> Option { + let transaction = self + .history + .remove_from_undo(transaction_id)? + .transaction + .clone(); + self.undo_or_redo(transaction).log_err() + } + + #[allow(clippy::needless_collect)] + pub fn undo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { + let transactions = self + .history + .remove_from_undo_until(transaction_id) + .iter() + .map(|entry| entry.transaction.clone()) + .collect::>(); + + transactions + .into_iter() + .map(|transaction| self.undo_or_redo(transaction).unwrap()) + .collect() + } + + pub fn forget_transaction(&mut self, transaction_id: TransactionId) { + self.history.forget(transaction_id); + } + + pub fn merge_transactions(&mut self, transaction: TransactionId, destination: TransactionId) { + self.history.merge_transactions(transaction, destination); + } + + pub fn redo(&mut self) -> Option<(TransactionId, Operation)> { + if let Some(entry) = self.history.pop_redo() { + let transaction = entry.transaction.clone(); + let transaction_id = transaction.id; + let op = self.undo_or_redo(transaction).unwrap(); + Some((transaction_id, op)) + } else { + None + } + } + + #[allow(clippy::needless_collect)] + pub fn redo_to_transaction(&mut self, transaction_id: TransactionId) -> Vec { + let transactions = self + .history + .remove_from_redo(transaction_id) + .iter() + .map(|entry| entry.transaction.clone()) + .collect::>(); + + transactions + .into_iter() + .map(|transaction| self.undo_or_redo(transaction).unwrap()) + .collect() + } + + fn undo_or_redo(&mut self, transaction: Transaction) -> Result { + let mut counts = HashMap::default(); + for edit_id in transaction.edit_ids { + counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); + } + + let undo = UndoOperation { + timestamp: self.lamport_clock.tick(), + version: self.version(), + counts, + }; + self.apply_undo(&undo)?; + self.snapshot.version.observe(undo.timestamp); + let operation = Operation::Undo(undo); + self.history.push(operation.clone()); + Ok(operation) + } + + pub fn push_transaction(&mut self, transaction: Transaction, now: Instant) { + self.history.push_transaction(transaction, now); + self.history.finalize_last_transaction(); + } + + pub fn edited_ranges_for_transaction<'a, D>( + &'a self, + transaction: &'a Transaction, + ) -> impl 'a + Iterator> + where + D: TextDimension, + { + // get fragment ranges + let mut cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let offset_ranges = 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 }; + Some(start_offset..end_offset) + }); + + // combine adjacent ranges + let mut prev_range: Option> = None; + let disjoint_ranges = offset_ranges + .map(Some) + .chain([None]) + .filter_map(move |range| { + if let Some((range, prev_range)) = range.as_ref().zip(prev_range.as_mut()) { + if prev_range.end == range.start { + prev_range.end = range.end; + return None; + } + } + let result = prev_range.clone(); + prev_range = range; + result + }); + + // convert to the desired text dimension. + let mut position = D::default(); + let mut rope_cursor = self.visible_text.cursor(0); + disjoint_ranges.map(move |range| { + position.add_assign(&rope_cursor.summary(range.start)); + let start = position.clone(); + position.add_assign(&rope_cursor.summary(range.end)); + let end = position.clone(); + start..end + }) + } + + pub fn subscribe(&mut self) -> Subscription { + self.subscriptions.subscribe() + } + + pub fn wait_for_edits( + &mut self, + edit_ids: impl IntoIterator, + ) -> impl 'static + Future> { + let mut futures = Vec::new(); + for edit_id in edit_ids { + if !self.version.observed(edit_id) { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers.entry(edit_id).or_default().push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + if future.recv().await.is_none() { + Err(anyhow!("gave up waiting for edits"))?; + } + } + Ok(()) + } + } + + pub fn wait_for_anchors( + &mut self, + anchors: impl IntoIterator, + ) -> impl 'static + Future> { + let mut futures = Vec::new(); + for anchor in anchors { + if !self.version.observed(anchor.timestamp) + && anchor != Anchor::MAX + && anchor != Anchor::MIN + { + let (tx, rx) = oneshot::channel(); + self.edit_id_resolvers + .entry(anchor.timestamp) + .or_default() + .push(tx); + futures.push(rx); + } + } + + async move { + for mut future in futures { + if future.recv().await.is_none() { + Err(anyhow!("gave up waiting for anchors"))?; + } + } + Ok(()) + } + } + + pub fn wait_for_version(&mut self, version: clock::Global) -> impl Future> { + let mut rx = None; + if !self.snapshot.version.observed_all(&version) { + let channel = oneshot::channel(); + self.wait_for_version_txs.push((version, channel.0)); + rx = Some(channel.1); + } + async move { + if let Some(mut rx) = rx { + if rx.recv().await.is_none() { + Err(anyhow!("gave up waiting for version"))?; + } + } + Ok(()) + } + } + + pub fn give_up_waiting(&mut self) { + self.edit_id_resolvers.clear(); + self.wait_for_version_txs.clear(); + } + + fn resolve_edit(&mut self, edit_id: clock::Lamport) { + for mut tx in self + .edit_id_resolvers + .remove(&edit_id) + .into_iter() + .flatten() + { + tx.try_send(()).ok(); + } + } +} + +#[cfg(any(test, feature = "test-support"))] +impl Buffer { + pub fn edit_via_marked_text(&mut self, marked_string: &str) { + let edits = self.edits_for_marked_text(marked_string); + self.edit(edits); + } + + pub fn edits_for_marked_text(&self, marked_string: &str) -> Vec<(Range, String)> { + let old_text = self.text(); + let (new_text, mut ranges) = util::test::marked_text_ranges(marked_string, false); + if ranges.is_empty() { + ranges.push(0..new_text.len()); + } + + assert_eq!( + old_text[..ranges[0].start], + new_text[..ranges[0].start], + "invalid edit" + ); + + let mut delta = 0; + let mut edits = Vec::new(); + let mut ranges = ranges.into_iter().peekable(); + + while let Some(inserted_range) = ranges.next() { + let new_start = inserted_range.start; + let old_start = (new_start as isize - delta) as usize; + + let following_text = if let Some(next_range) = ranges.peek() { + &new_text[inserted_range.end..next_range.start] + } else { + &new_text[inserted_range.end..] + }; + + let inserted_len = inserted_range.len(); + let deleted_len = old_text[old_start..] + .find(following_text) + .expect("invalid edit"); + + let old_range = old_start..old_start + deleted_len; + edits.push((old_range, new_text[inserted_range].to_string())); + delta += inserted_len as isize - deleted_len as isize; + } + + assert_eq!( + old_text.len() as isize + delta, + new_text.len() as isize, + "invalid edit" + ); + + edits + } + + pub fn check_invariants(&self) { + // Ensure every fragment is ordered by locator in the fragment tree and corresponds + // to an insertion fragment in the insertions tree. + let mut prev_fragment_id = Locator::min(); + for fragment in self.snapshot.fragments.items(&None) { + assert!(fragment.id > prev_fragment_id); + prev_fragment_id = fragment.id.clone(); + + let insertion_fragment = self + .snapshot + .insertions + .get( + &InsertionFragmentKey { + timestamp: fragment.timestamp, + split_offset: fragment.insertion_offset, + }, + &(), + ) + .unwrap(); + assert_eq!( + insertion_fragment.fragment_id, fragment.id, + "fragment: {:?}\ninsertion: {:?}", + fragment, insertion_fragment + ); + } + + let mut cursor = self.snapshot.fragments.cursor::>(); + for insertion_fragment in self.snapshot.insertions.cursor::<()>() { + cursor.seek(&Some(&insertion_fragment.fragment_id), Bias::Left, &None); + let fragment = cursor.item().unwrap(); + assert_eq!(insertion_fragment.fragment_id, fragment.id); + assert_eq!(insertion_fragment.split_offset, fragment.insertion_offset); + } + + let fragment_summary = self.snapshot.fragments.summary(); + assert_eq!( + fragment_summary.text.visible, + self.snapshot.visible_text.len() + ); + assert_eq!( + fragment_summary.text.deleted, + self.snapshot.deleted_text.len() + ); + + assert!(!self.text().contains("\r\n")); + } + + pub fn set_group_interval(&mut self, group_interval: Duration) { + self.history.group_interval = group_interval; + } + + pub fn random_byte_range(&self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { + let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); + let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); + start..end + } + + pub fn get_random_edits( + &self, + rng: &mut T, + edit_count: usize, + ) -> Vec<(Range, Arc)> + where + T: rand::Rng, + { + let mut edits: Vec<(Range, Arc)> = Vec::new(); + let mut last_end = None; + for _ in 0..edit_count { + if last_end.map_or(false, |last_end| last_end >= self.len()) { + break; + } + let new_start = last_end.map_or(0, |last_end| last_end + 1); + let range = self.random_byte_range(new_start, rng); + last_end = Some(range.end); + + let new_text_len = rng.gen_range(0..10); + let new_text: String = RandomCharIter::new(&mut *rng).take(new_text_len).collect(); + + edits.push((range, new_text.into())); + } + edits + } + + #[allow(clippy::type_complexity)] + pub fn randomly_edit( + &mut self, + rng: &mut T, + edit_count: usize, + ) -> (Vec<(Range, Arc)>, Operation) + where + T: rand::Rng, + { + let mut edits = self.get_random_edits(rng, edit_count); + log::info!("mutating buffer {} with {:?}", self.replica_id, edits); + + let op = self.edit(edits.iter().cloned()); + if let Operation::Edit(edit) = &op { + assert_eq!(edits.len(), edit.new_text.len()); + for (edit, new_text) in edits.iter_mut().zip(&edit.new_text) { + edit.1 = new_text.clone(); + } + } else { + unreachable!() + } + + (edits, op) + } + + pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) -> Vec { + use rand::prelude::*; + + let mut ops = Vec::new(); + for _ in 0..rng.gen_range(1..=5) { + if let Some(entry) = self.history.undo_stack.choose(rng) { + let transaction = entry.transaction.clone(); + log::info!( + "undoing buffer {} transaction {:?}", + self.replica_id, + transaction + ); + ops.push(self.undo_or_redo(transaction).unwrap()); + } + } + ops + } +} + +impl Deref for Buffer { + type Target = BufferSnapshot; + + fn deref(&self) -> &Self::Target { + &self.snapshot + } +} + +impl BufferSnapshot { + pub fn as_rope(&self) -> &Rope { + &self.visible_text + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn replica_id(&self) -> ReplicaId { + self.replica_id + } + + pub fn row_count(&self) -> u32 { + self.max_point().row + 1 + } + + pub fn len(&self) -> usize { + self.visible_text.len() + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn chars(&self) -> impl Iterator + '_ { + self.chars_at(0) + } + + pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { + self.text_for_range(range).flat_map(str::chars) + } + + pub fn reversed_chars_for_range( + &self, + range: Range, + ) -> impl Iterator + '_ { + self.reversed_chunks_in_range(range) + .flat_map(|chunk| chunk.chars().rev()) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_in_range(position..self.len()) + .flatten() + .copied() + .take(needle.len()) + .eq(needle.bytes()) + } + + pub fn common_prefix_at(&self, position: T, needle: &str) -> Range + where + T: ToOffset + TextDimension, + { + let offset = position.to_offset(self); + let common_prefix_len = needle + .char_indices() + .map(|(index, _)| index) + .chain([needle.len()]) + .take_while(|&len| len <= offset) + .filter(|&len| { + let left = self + .chars_for_range(offset - len..offset) + .flat_map(char::to_lowercase); + let right = needle[..len].chars().flat_map(char::to_lowercase); + left.eq(right) + }) + .last() + .unwrap_or(0); + let start_offset = offset - common_prefix_len; + let start = self.text_summary_for_range(0..start_offset); + start..position + } + + pub fn text(&self) -> String { + self.visible_text.to_string() + } + + pub fn line_ending(&self) -> LineEnding { + self.line_ending + } + + pub fn deleted_text(&self) -> String { + self.deleted_text.to_string() + } + + pub fn fragments(&self) -> impl Iterator { + self.fragments.iter() + } + + pub fn text_summary(&self) -> TextSummary { + self.visible_text.summary() + } + + pub fn max_point(&self) -> Point { + self.visible_text.max_point() + } + + pub fn max_point_utf16(&self) -> PointUtf16 { + self.visible_text.max_point_utf16() + } + + pub fn point_to_offset(&self, point: Point) -> usize { + self.visible_text.point_to_offset(point) + } + + pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { + self.visible_text.point_utf16_to_offset(point) + } + + pub fn unclipped_point_utf16_to_offset(&self, point: Unclipped) -> usize { + self.visible_text.unclipped_point_utf16_to_offset(point) + } + + pub fn unclipped_point_utf16_to_point(&self, point: Unclipped) -> Point { + self.visible_text.unclipped_point_utf16_to_point(point) + } + + pub fn offset_utf16_to_offset(&self, offset: OffsetUtf16) -> usize { + self.visible_text.offset_utf16_to_offset(offset) + } + + pub fn offset_to_offset_utf16(&self, offset: usize) -> OffsetUtf16 { + self.visible_text.offset_to_offset_utf16(offset) + } + + pub fn offset_to_point(&self, offset: usize) -> Point { + self.visible_text.offset_to_point(offset) + } + + pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { + self.visible_text.offset_to_point_utf16(offset) + } + + pub fn point_to_point_utf16(&self, point: Point) -> PointUtf16 { + self.visible_text.point_to_point_utf16(point) + } + + pub fn version(&self) -> &clock::Global { + &self.version + } + + pub fn chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.chars_at(offset) + } + + pub fn reversed_chars_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.reversed_chars_at(offset) + } + + pub fn reversed_chunks_in_range(&self, range: Range) -> rope::Chunks { + let range = range.start.to_offset(self)..range.end.to_offset(self); + self.visible_text.reversed_chunks_in_range(range) + } + + pub fn bytes_in_range(&self, range: Range) -> rope::Bytes<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.bytes_in_range(start..end) + } + + pub fn reversed_bytes_in_range(&self, range: Range) -> rope::Bytes<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.reversed_bytes_in_range(start..end) + } + + pub fn text_for_range(&self, range: Range) -> Chunks<'_> { + let start = range.start.to_offset(self); + let end = range.end.to_offset(self); + self.visible_text.chunks_in_range(start..end) + } + + pub fn line_len(&self, row: u32) -> u32 { + let row_start_offset = Point::new(row, 0).to_offset(self); + let row_end_offset = if row >= self.max_point().row { + self.len() + } else { + Point::new(row + 1, 0).to_offset(self) - 1 + }; + (row_end_offset - row_start_offset) as u32 + } + + pub fn is_line_blank(&self, row: u32) -> bool { + self.text_for_range(Point::new(row, 0)..Point::new(row, self.line_len(row))) + .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) + } + + pub fn text_summary_for_range(&self, range: Range) -> D + where + D: TextDimension, + { + self.visible_text + .cursor(range.start.to_offset(self)) + .summary(range.end.to_offset(self)) + } + + pub fn summaries_for_anchors<'a, D, A>(&'a self, anchors: A) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + self.summaries_for_anchors_with_payload::(anchors.map(|a| (a, ()))) + .map(|d| d.0) + } + + pub fn summaries_for_anchors_with_payload<'a, D, A, T>( + &'a self, + anchors: A, + ) -> impl 'a + Iterator + where + D: 'a + TextDimension, + A: 'a + IntoIterator, + { + let anchors = anchors.into_iter(); + let mut insertion_cursor = self.insertions.cursor::(); + let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + let mut text_cursor = self.visible_text.cursor(0); + let mut position = D::default(); + + anchors.map(move |(anchor, payload)| { + if *anchor == Anchor::MIN { + return (D::default(), payload); + } else if *anchor == Anchor::MAX { + return (D::from_text_summary(&self.visible_text.summary()), payload); + } + + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + + fragment_cursor.seek_forward(&Some(&insertion.fragment_id), Bias::Left, &None); + let fragment = fragment_cursor.item().unwrap(); + let mut fragment_offset = fragment_cursor.start().1; + if fragment.visible { + fragment_offset += anchor.offset - insertion.split_offset; + } + + position.add_assign(&text_cursor.summary(fragment_offset)); + (position.clone(), payload) + }) + } + + fn summary_for_anchor(&self, anchor: &Anchor) -> D + where + D: TextDimension, + { + if *anchor == Anchor::MIN { + D::default() + } else if *anchor == Anchor::MAX { + D::from_text_summary(&self.visible_text.summary()) + } else { + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + let mut insertion_cursor = self.insertions.cursor::(); + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + + let mut fragment_cursor = self.fragments.cursor::<(Option<&Locator>, usize)>(); + fragment_cursor.seek(&Some(&insertion.fragment_id), Bias::Left, &None); + let fragment = fragment_cursor.item().unwrap(); + let mut fragment_offset = fragment_cursor.start().1; + if fragment.visible { + fragment_offset += anchor.offset - insertion.split_offset; + } + self.text_summary_for_range(0..fragment_offset) + } + } + + fn fragment_id_for_anchor(&self, anchor: &Anchor) -> &Locator { + if *anchor == Anchor::MIN { + Locator::min_ref() + } else if *anchor == Anchor::MAX { + Locator::max_ref() + } else { + let anchor_key = InsertionFragmentKey { + timestamp: anchor.timestamp, + split_offset: anchor.offset, + }; + let mut insertion_cursor = self.insertions.cursor::(); + insertion_cursor.seek(&anchor_key, anchor.bias, &()); + if let Some(insertion) = insertion_cursor.item() { + let comparison = sum_tree::KeyedItem::key(insertion).cmp(&anchor_key); + if comparison == Ordering::Greater + || (anchor.bias == Bias::Left + && comparison == Ordering::Equal + && anchor.offset > 0) + { + insertion_cursor.prev(&()); + } + } else { + insertion_cursor.prev(&()); + } + let insertion = insertion_cursor.item().expect("invalid insertion"); + debug_assert_eq!(insertion.timestamp, anchor.timestamp, "invalid insertion"); + &insertion.fragment_id + } + } + + pub fn anchor_before(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Left) + } + + pub fn anchor_after(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Right) + } + + pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { + self.anchor_at_offset(position.to_offset(self), bias) + } + + fn anchor_at_offset(&self, offset: usize, bias: Bias) -> Anchor { + if bias == Bias::Left && offset == 0 { + Anchor::MIN + } else if bias == Bias::Right && offset == self.len() { + Anchor::MAX + } else { + let mut fragment_cursor = self.fragments.cursor::(); + fragment_cursor.seek(&offset, bias, &None); + let fragment = fragment_cursor.item().unwrap(); + let overshoot = offset - *fragment_cursor.start(); + Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset + overshoot, + bias, + buffer_id: Some(self.remote_id), + } + } + } + + pub fn can_resolve(&self, anchor: &Anchor) -> bool { + *anchor == Anchor::MIN + || *anchor == Anchor::MAX + || (Some(self.remote_id) == anchor.buffer_id && self.version.observed(anchor.timestamp)) + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + self.visible_text.clip_offset(offset, bias) + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + self.visible_text.clip_point(point, bias) + } + + pub fn clip_offset_utf16(&self, offset: OffsetUtf16, bias: Bias) -> OffsetUtf16 { + self.visible_text.clip_offset_utf16(offset, bias) + } + + pub fn clip_point_utf16(&self, point: Unclipped, bias: Bias) -> PointUtf16 { + self.visible_text.clip_point_utf16(point, bias) + } + + pub fn edits_since<'a, D>( + &'a self, + since: &'a clock::Global, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord, + { + self.edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + } + + pub fn anchored_edits_since<'a, D>( + &'a self, + since: &'a clock::Global, + ) -> impl 'a + Iterator, Range)> + where + D: TextDimension + Ord, + { + self.anchored_edits_since_in_range(since, Anchor::MIN..Anchor::MAX) + } + + pub fn edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator> + where + D: TextDimension + Ord, + { + self.anchored_edits_since_in_range(since, range) + .map(|item| item.0) + } + + pub fn anchored_edits_since_in_range<'a, D>( + &'a self, + since: &'a clock::Global, + range: Range, + ) -> impl 'a + Iterator, Range)> + where + D: TextDimension + Ord, + { + let fragments_cursor = if *since == self.version { + None + } else { + let mut cursor = self + .fragments + .filter(move |summary| !since.observed_all(&summary.max_version)); + cursor.next(&None); + Some(cursor) + }; + let mut cursor = self + .fragments + .cursor::<(Option<&Locator>, FragmentTextSummary)>(); + + let start_fragment_id = self.fragment_id_for_anchor(&range.start); + cursor.seek(&Some(start_fragment_id), Bias::Left, &None); + let mut visible_start = cursor.start().1.visible; + let mut deleted_start = cursor.start().1.deleted; + if let Some(fragment) = cursor.item() { + let overshoot = range.start.offset - fragment.insertion_offset; + if fragment.visible { + visible_start += overshoot; + } else { + deleted_start += overshoot; + } + } + let end_fragment_id = self.fragment_id_for_anchor(&range.end); + + Edits { + visible_cursor: self.visible_text.cursor(visible_start), + deleted_cursor: self.deleted_text.cursor(deleted_start), + fragments_cursor, + undos: &self.undo_map, + since, + old_end: Default::default(), + new_end: Default::default(), + range: (start_fragment_id, range.start.offset)..(end_fragment_id, range.end.offset), + buffer_id: self.remote_id, + } + } +} + +struct RopeBuilder<'a> { + old_visible_cursor: rope::Cursor<'a>, + old_deleted_cursor: rope::Cursor<'a>, + new_visible: Rope, + new_deleted: Rope, +} + +impl<'a> RopeBuilder<'a> { + fn new(old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>) -> Self { + Self { + old_visible_cursor, + old_deleted_cursor, + new_visible: Rope::new(), + new_deleted: Rope::new(), + } + } + + fn append(&mut self, len: FragmentTextSummary) { + self.push(len.visible, true, true); + self.push(len.deleted, false, false); + } + + fn push_fragment(&mut self, fragment: &Fragment, was_visible: bool) { + debug_assert!(fragment.len > 0); + self.push(fragment.len, was_visible, fragment.visible) + } + + fn push(&mut self, len: usize, was_visible: bool, is_visible: bool) { + let text = if was_visible { + self.old_visible_cursor + .slice(self.old_visible_cursor.offset() + len) + } else { + self.old_deleted_cursor + .slice(self.old_deleted_cursor.offset() + len) + }; + if is_visible { + self.new_visible.append(text); + } else { + self.new_deleted.append(text); + } + } + + fn push_str(&mut self, text: &str) { + self.new_visible.push(text); + } + + fn finish(mut self) -> (Rope, Rope) { + self.new_visible.append(self.old_visible_cursor.suffix()); + self.new_deleted.append(self.old_deleted_cursor.suffix()); + (self.new_visible, self.new_deleted) + } +} + +impl<'a, D: TextDimension + Ord, F: FnMut(&FragmentSummary) -> bool> Iterator for Edits<'a, D, F> { + type Item = (Edit, Range); + + fn next(&mut self) -> Option { + let mut pending_edit: Option = None; + let cursor = self.fragments_cursor.as_mut()?; + + while let Some(fragment) = cursor.item() { + if fragment.id < *self.range.start.0 { + cursor.next(&None); + continue; + } else if fragment.id > *self.range.end.0 { + break; + } + + if cursor.start().visible > self.visible_cursor.offset() { + let summary = self.visible_cursor.summary(cursor.start().visible); + self.old_end.add_assign(&summary); + self.new_end.add_assign(&summary); + } + + if pending_edit + .as_ref() + .map_or(false, |(change, _)| change.new.end < self.new_end) + { + break; + } + + let start_anchor = Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset, + bias: Bias::Right, + buffer_id: Some(self.buffer_id), + }; + let end_anchor = Anchor { + timestamp: fragment.timestamp, + offset: fragment.insertion_offset + fragment.len, + bias: Bias::Left, + buffer_id: Some(self.buffer_id), + }; + + if !fragment.was_visible(self.since, self.undos) && fragment.visible { + let mut visible_end = cursor.end(&None).visible; + if fragment.id == *self.range.end.0 { + visible_end = cmp::min( + visible_end, + cursor.start().visible + (self.range.end.1 - fragment.insertion_offset), + ); + } + + let fragment_summary = self.visible_cursor.summary(visible_end); + let mut new_end = self.new_end.clone(); + new_end.add_assign(&fragment_summary); + if let Some((edit, range)) = pending_edit.as_mut() { + edit.new.end = new_end.clone(); + range.end = end_anchor; + } else { + pending_edit = Some(( + Edit { + old: self.old_end.clone()..self.old_end.clone(), + new: self.new_end.clone()..new_end.clone(), + }, + start_anchor..end_anchor, + )); + } + + self.new_end = new_end; + } else if fragment.was_visible(self.since, self.undos) && !fragment.visible { + let mut deleted_end = cursor.end(&None).deleted; + if fragment.id == *self.range.end.0 { + deleted_end = cmp::min( + deleted_end, + cursor.start().deleted + (self.range.end.1 - fragment.insertion_offset), + ); + } + + if cursor.start().deleted > self.deleted_cursor.offset() { + self.deleted_cursor.seek_forward(cursor.start().deleted); + } + let fragment_summary = self.deleted_cursor.summary(deleted_end); + let mut old_end = self.old_end.clone(); + old_end.add_assign(&fragment_summary); + if let Some((edit, range)) = pending_edit.as_mut() { + edit.old.end = old_end.clone(); + range.end = end_anchor; + } else { + pending_edit = Some(( + Edit { + old: self.old_end.clone()..old_end.clone(), + new: self.new_end.clone()..self.new_end.clone(), + }, + start_anchor..end_anchor, + )); + } + + self.old_end = old_end; + } + + cursor.next(&None); + } + + pending_edit + } +} + +impl Fragment { + fn insertion_slice(&self) -> InsertionSlice { + InsertionSlice { + insertion_id: self.timestamp, + range: self.insertion_offset..self.insertion_offset + self.len, + } + } + + fn is_visible(&self, undos: &UndoMap) -> bool { + !undos.is_undone(self.timestamp) && self.deletions.iter().all(|d| undos.is_undone(*d)) + } + + fn was_visible(&self, version: &clock::Global, undos: &UndoMap) -> bool { + (version.observed(self.timestamp) && !undos.was_undone(self.timestamp, version)) + && self + .deletions + .iter() + .all(|d| !version.observed(*d) || undos.was_undone(*d, version)) + } +} + +impl sum_tree::Item for Fragment { + type Summary = FragmentSummary; + + fn summary(&self) -> Self::Summary { + let mut max_version = clock::Global::new(); + max_version.observe(self.timestamp); + for deletion in &self.deletions { + max_version.observe(*deletion); + } + max_version.join(&self.max_undos); + + let mut min_insertion_version = clock::Global::new(); + min_insertion_version.observe(self.timestamp); + let max_insertion_version = min_insertion_version.clone(); + if self.visible { + FragmentSummary { + max_id: self.id.clone(), + text: FragmentTextSummary { + visible: self.len, + deleted: 0, + }, + max_version, + min_insertion_version, + max_insertion_version, + } + } else { + FragmentSummary { + max_id: self.id.clone(), + text: FragmentTextSummary { + visible: 0, + deleted: self.len, + }, + max_version, + min_insertion_version, + max_insertion_version, + } + } + } +} + +impl sum_tree::Summary for FragmentSummary { + type Context = Option; + + fn add_summary(&mut self, other: &Self, _: &Self::Context) { + self.max_id.assign(&other.max_id); + self.text.visible += &other.text.visible; + self.text.deleted += &other.text.deleted; + self.max_version.join(&other.max_version); + self.min_insertion_version + .meet(&other.min_insertion_version); + self.max_insertion_version + .join(&other.max_insertion_version); + } +} + +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(), + max_insertion_version: clock::Global::new(), + } + } +} + +impl sum_tree::Item for InsertionFragment { + type Summary = InsertionFragmentKey; + + fn summary(&self) -> Self::Summary { + InsertionFragmentKey { + timestamp: self.timestamp, + split_offset: self.split_offset, + } + } +} + +impl sum_tree::KeyedItem for InsertionFragment { + type Key = InsertionFragmentKey; + + fn key(&self) -> Self::Key { + sum_tree::Item::summary(self) + } +} + +impl InsertionFragment { + fn new(fragment: &Fragment) -> Self { + Self { + timestamp: fragment.timestamp, + split_offset: fragment.insertion_offset, + fragment_id: fragment.id.clone(), + } + } + + fn insert_new(fragment: &Fragment) -> sum_tree::Edit { + sum_tree::Edit::Insert(Self::new(fragment)) + } +} + +impl sum_tree::Summary for InsertionFragmentKey { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + *self = *summary; + } +} + +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct FullOffset(pub usize); + +impl ops::AddAssign for FullOffset { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +impl ops::Add for FullOffset { + type Output = Self; + + fn add(mut self, rhs: usize) -> Self::Output { + self += rhs; + self + } +} + +impl ops::Sub for FullOffset { + type Output = usize; + + fn sub(self, rhs: Self) -> Self::Output { + self.0 - rhs.0 + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize { + fn add_summary(&mut self, summary: &FragmentSummary, _: &Option) { + *self += summary.text.visible; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FullOffset { + fn add_summary(&mut self, summary: &FragmentSummary, _: &Option) { + self.0 += summary.text.visible + summary.text.deleted; + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Option<&'a Locator> { + fn add_summary(&mut self, summary: &'a FragmentSummary, _: &Option) { + *self = Some(&summary.max_id); + } +} + +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, FragmentTextSummary> for usize { + fn cmp( + &self, + cursor_location: &FragmentTextSummary, + _: &Option, + ) -> cmp::Ordering { + Ord::cmp(self, &cursor_location.visible) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum VersionedFullOffset { + Offset(FullOffset), + Invalid, +} + +impl VersionedFullOffset { + fn full_offset(&self) -> FullOffset { + if let Self::Offset(position) = self { + *position + } else { + panic!("invalid version") + } + } +} + +impl Default for VersionedFullOffset { + fn default() -> Self { + Self::Offset(Default::default()) + } +} + +impl<'a> sum_tree::Dimension<'a, FragmentSummary> for VersionedFullOffset { + fn add_summary(&mut self, summary: &'a FragmentSummary, cx: &Option) { + if let Self::Offset(offset) = self { + let version = cx.as_ref().unwrap(); + if version.observed_all(&summary.max_insertion_version) { + *offset += summary.text.visible + summary.text.deleted; + } else if version.observed_any(&summary.min_insertion_version) { + *self = Self::Invalid; + } + } + } +} + +impl<'a> sum_tree::SeekTarget<'a, FragmentSummary, Self> for VersionedFullOffset { + fn cmp(&self, cursor_position: &Self, _: &Option) -> cmp::Ordering { + match (self, cursor_position) { + (Self::Offset(a), Self::Offset(b)) => Ord::cmp(a, b), + (Self::Offset(_), Self::Invalid) => cmp::Ordering::Less, + (Self::Invalid, _) => unreachable!(), + } + } +} + +impl Operation { + fn replica_id(&self) -> ReplicaId { + operation_queue::Operation::lamport_timestamp(self).replica_id + } + + pub fn timestamp(&self) -> clock::Lamport { + match self { + Operation::Edit(edit) => edit.timestamp, + Operation::Undo(undo) => undo.timestamp, + } + } + + pub fn as_edit(&self) -> Option<&EditOperation> { + match self { + Operation::Edit(edit) => Some(edit), + _ => None, + } + } + + pub fn is_edit(&self) -> bool { + matches!(self, Operation::Edit { .. }) + } +} + +impl operation_queue::Operation for Operation { + fn lamport_timestamp(&self) -> clock::Lamport { + match self { + Operation::Edit(edit) => edit.timestamp, + Operation::Undo(undo) => undo.timestamp, + } + } +} + +pub trait ToOffset { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize; +} + +impl ToOffset for Point { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_to_offset(*self) + } +} + +impl ToOffset for usize { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + assert!( + *self <= snapshot.len(), + "offset {} is out of range, max allowed is {}", + self, + snapshot.len() + ); + *self + } +} + +impl ToOffset for Anchor { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.summary_for_anchor(self) + } +} + +impl<'a, T: ToOffset> ToOffset for &'a T { + fn to_offset(&self, content: &BufferSnapshot) -> usize { + (*self).to_offset(content) + } +} + +impl ToOffset for PointUtf16 { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.point_utf16_to_offset(*self) + } +} + +impl ToOffset for Unclipped { + fn to_offset(&self, snapshot: &BufferSnapshot) -> usize { + snapshot.unclipped_point_utf16_to_offset(*self) + } +} + +pub trait ToPoint { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point; +} + +impl ToPoint for Anchor { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.summary_for_anchor(self) + } +} + +impl ToPoint for usize { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.offset_to_point(*self) + } +} + +impl ToPoint for Point { + fn to_point(&self, _: &BufferSnapshot) -> Point { + *self + } +} + +impl ToPoint for Unclipped { + fn to_point(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.unclipped_point_utf16_to_point(*self) + } +} + +pub trait ToPointUtf16 { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16; +} + +impl ToPointUtf16 for Anchor { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.summary_for_anchor(self) + } +} + +impl ToPointUtf16 for usize { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.offset_to_point_utf16(*self) + } +} + +impl ToPointUtf16 for PointUtf16 { + fn to_point_utf16(&self, _: &BufferSnapshot) -> PointUtf16 { + *self + } +} + +impl ToPointUtf16 for Point { + fn to_point_utf16(&self, snapshot: &BufferSnapshot) -> PointUtf16 { + snapshot.point_to_point_utf16(*self) + } +} + +pub trait ToOffsetUtf16 { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16; +} + +impl ToOffsetUtf16 for Anchor { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + snapshot.summary_for_anchor(self) + } +} + +impl ToOffsetUtf16 for usize { + fn to_offset_utf16(&self, snapshot: &BufferSnapshot) -> OffsetUtf16 { + snapshot.offset_to_offset_utf16(*self) + } +} + +impl ToOffsetUtf16 for OffsetUtf16 { + fn to_offset_utf16(&self, _snapshot: &BufferSnapshot) -> OffsetUtf16 { + *self + } +} + +pub trait FromAnchor { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; +} + +impl FromAnchor for Point { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +impl FromAnchor for PointUtf16 { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +impl FromAnchor for usize { + fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self { + snapshot.summary_for_anchor(anchor) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LineEnding { + Unix, + Windows, +} + +impl Default for LineEnding { + fn default() -> Self { + #[cfg(unix)] + return Self::Unix; + + #[cfg(not(unix))] + return Self::CRLF; + } +} + +impl LineEnding { + pub fn as_str(&self) -> &'static str { + match self { + LineEnding::Unix => "\n", + LineEnding::Windows => "\r\n", + } + } + + pub fn detect(text: &str) -> Self { + let mut max_ix = cmp::min(text.len(), 1000); + while !text.is_char_boundary(max_ix) { + max_ix -= 1; + } + + if let Some(ix) = text[..max_ix].find(&['\n']) { + if ix > 0 && text.as_bytes()[ix - 1] == b'\r' { + Self::Windows + } else { + Self::Unix + } + } else { + Self::default() + } + } + + pub fn normalize(text: &mut String) { + if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(text, "\n") { + *text = replaced; + } + } + + pub fn normalize_arc(text: Arc) -> Arc { + if let Cow::Owned(replaced) = LINE_SEPARATORS_REGEX.replace_all(&text, "\n") { + replaced.into() + } else { + text + } + } +} diff --git a/crates/text2/src/undo_map.rs b/crates/text2/src/undo_map.rs new file mode 100644 index 0000000000..f95809c02e --- /dev/null +++ b/crates/text2/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::Lamport, + undo_id: clock::Lamport, +} + +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.timestamp, + }, + undo_count: *count, + }) + }) + .collect::>(); + self.0.edit(edits, &()); + } + + pub fn is_undone(&self, edit_id: clock::Lamport) -> bool { + self.undo_count(edit_id) % 2 == 1 + } + + pub fn was_undone(&self, edit_id: clock::Lamport, 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::Lamport) -> 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 + } +} diff --git a/crates/zed2/Cargo.toml b/crates/zed2/Cargo.toml index b75e2d881f..a54e68b51a 100644 --- a/crates/zed2/Cargo.toml +++ b/crates/zed2/Cargo.toml @@ -63,7 +63,7 @@ settings2 = { path = "../settings2" } feature_flags2 = { path = "../feature_flags2" } sum_tree = { path = "../sum_tree" } shellexpand = "2.1.0" -text = { path = "../text" } +text2 = { path = "../text2" } # terminal_view = { path = "../terminal_view" } theme2 = { path = "../theme2" } # theme_selector = { path = "../theme_selector" } @@ -152,7 +152,7 @@ language2 = { path = "../language2", features = ["test-support"] } project2 = { path = "../project2", features = ["test-support"] } # rpc = { path = "../rpc", features = ["test-support"] } # settings = { path = "../settings", features = ["test-support"] } -# text = { path = "../text", features = ["test-support"] } +text2 = { path = "../text2", features = ["test-support"] } # util = { path = "../util", features = ["test-support"] } # workspace = { path = "../workspace", features = ["test-support"] } unindent.workspace = true