Rename buffer crate to text and name its entrypoint after the crate

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
Nathan Sobo 2021-11-30 12:26:12 -07:00
parent eacd2a45bb
commit d3f28166cb
31 changed files with 84 additions and 85 deletions

25
crates/text/Cargo.toml Normal file
View file

@ -0,0 +1,25 @@
[package]
name = "text"
version = "0.1.0"
edition = "2021"
[lib]
path = "src/text.rs"
[features]
test-support = ["rand"]
[dependencies]
clock = { path = "../clock" }
collections = { path = "../collections" }
sum_tree = { path = "../sum_tree" }
anyhow = "1.0.38"
arrayvec = "0.7.1"
log = "0.4"
rand = { version = "0.8.3", optional = true }
smallvec = { version = "1.6", features = ["union"] }
[dev-dependencies]
collections = { path = "../collections", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
rand = "0.8.3"

594
crates/text/src/anchor.rs Normal file
View file

@ -0,0 +1,594 @@
use crate::rope::TextDimension;
use super::{Buffer, Content, FromAnchor, FullOffset, Point, ToOffset};
use anyhow::Result;
use std::{
cmp::Ordering,
fmt::{Debug, Formatter},
ops::Range,
};
use sum_tree::{Bias, SumTree};
#[derive(Clone, Eq, PartialEq, Debug, Hash)]
pub struct Anchor {
pub full_offset: FullOffset,
pub bias: Bias,
pub version: clock::Global,
}
#[derive(Clone)]
pub struct AnchorMap<T> {
pub(crate) version: clock::Global,
pub(crate) bias: Bias,
pub(crate) entries: Vec<(FullOffset, T)>,
}
#[derive(Clone)]
pub struct AnchorSet(pub(crate) AnchorMap<()>);
#[derive(Clone)]
pub struct AnchorRangeMap<T> {
pub(crate) version: clock::Global,
pub(crate) entries: Vec<(Range<FullOffset>, T)>,
pub(crate) start_bias: Bias,
pub(crate) end_bias: Bias,
}
#[derive(Clone)]
pub struct AnchorRangeSet(pub(crate) AnchorRangeMap<()>);
#[derive(Clone)]
pub struct AnchorRangeMultimap<T: Clone> {
pub(crate) entries: SumTree<AnchorRangeMultimapEntry<T>>,
pub(crate) version: clock::Global,
pub(crate) start_bias: Bias,
pub(crate) end_bias: Bias,
}
#[derive(Clone)]
pub(crate) struct AnchorRangeMultimapEntry<T> {
pub(crate) range: FullOffsetRange,
pub(crate) value: T,
}
#[derive(Clone, Debug)]
pub(crate) struct FullOffsetRange {
pub(crate) start: FullOffset,
pub(crate) end: FullOffset,
}
#[derive(Clone, Debug)]
pub(crate) struct AnchorRangeMultimapSummary {
start: FullOffset,
end: FullOffset,
min_start: FullOffset,
max_end: FullOffset,
count: usize,
}
impl Anchor {
pub fn min() -> Self {
Self {
full_offset: FullOffset(0),
bias: Bias::Left,
version: Default::default(),
}
}
pub fn max() -> Self {
Self {
full_offset: FullOffset::MAX,
bias: Bias::Right,
version: Default::default(),
}
}
pub fn cmp<'a>(&self, other: &Anchor, buffer: impl Into<Content<'a>>) -> Result<Ordering> {
let buffer = buffer.into();
if self == other {
return Ok(Ordering::Equal);
}
let offset_comparison = if self.version == other.version {
self.full_offset.cmp(&other.full_offset)
} else {
buffer
.full_offset_for_anchor(self)
.cmp(&buffer.full_offset_for_anchor(other))
};
Ok(offset_comparison.then_with(|| self.bias.cmp(&other.bias)))
}
pub fn bias_left(&self, buffer: &Buffer) -> Anchor {
if self.bias == Bias::Left {
self.clone()
} else {
buffer.anchor_before(self)
}
}
pub fn bias_right(&self, buffer: &Buffer) -> Anchor {
if self.bias == Bias::Right {
self.clone()
} else {
buffer.anchor_after(self)
}
}
pub fn summary<'a, D, C>(&self, content: C) -> D
where
D: TextDimension<'a>,
C: Into<Content<'a>>,
{
content.into().summary_for_anchor(self)
}
}
impl<T> AnchorMap<T> {
pub fn version(&self) -> &clock::Global {
&self.version
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn iter<'a, D, C>(&'a self, content: C) -> impl Iterator<Item = (D, &'a T)> + 'a
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
let content = content.into();
content
.summaries_for_anchors(
self.version.clone(),
self.bias,
self.entries.iter().map(|e| &e.0),
)
.zip(self.entries.iter().map(|e| &e.1))
}
}
impl AnchorSet {
pub fn version(&self) -> &clock::Global {
&self.0.version
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter<'a, D, C>(&'a self, content: C) -> impl Iterator<Item = D> + 'a
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.0.iter(content).map(|(position, _)| position)
}
}
impl<T> AnchorRangeMap<T> {
pub fn version(&self) -> &clock::Global {
&self.version
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn from_full_offset_ranges(
version: clock::Global,
start_bias: Bias,
end_bias: Bias,
entries: Vec<(Range<FullOffset>, T)>,
) -> Self {
Self {
version,
start_bias,
end_bias,
entries,
}
}
pub fn ranges<'a, D>(
&'a self,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
where
D: 'a + TextDimension<'a>,
{
let content = content.into();
content
.summaries_for_anchor_ranges(
self.version.clone(),
self.start_bias,
self.end_bias,
self.entries.iter().map(|e| &e.0),
)
.zip(self.entries.iter().map(|e| &e.1))
}
pub fn intersecting_ranges<'a, D, I>(
&'a self,
range: Range<(I, Bias)>,
content: impl Into<Content<'a>> + 'a,
) -> impl Iterator<Item = (Range<D>, &'a T)> + 'a
where
D: 'a + TextDimension<'a>,
I: ToOffset,
{
let content = content.into();
let range = content.anchor_at(range.start.0, range.start.1)
..content.anchor_at(range.end.0, range.end.1);
let mut probe_anchor = Anchor {
full_offset: Default::default(),
bias: self.start_bias,
version: self.version.clone(),
};
let start_ix = self.entries.binary_search_by(|probe| {
probe_anchor.full_offset = probe.0.end;
probe_anchor.cmp(&range.start, &content).unwrap()
});
match start_ix {
Ok(start_ix) | Err(start_ix) => content
.summaries_for_anchor_ranges(
self.version.clone(),
self.start_bias,
self.end_bias,
self.entries[start_ix..].iter().map(|e| &e.0),
)
.zip(self.entries.iter().map(|e| &e.1)),
}
}
pub fn full_offset_ranges(&self) -> impl Iterator<Item = &(Range<FullOffset>, T)> {
self.entries.iter()
}
pub fn min_by_key<'a, C, D, F, K>(
&self,
content: C,
mut extract_key: F,
) -> Option<(Range<D>, &T)>
where
C: Into<Content<'a>>,
D: 'a + TextDimension<'a>,
F: FnMut(&T) -> K,
K: Ord,
{
let content = content.into();
self.entries
.iter()
.min_by_key(|(_, value)| extract_key(value))
.map(|(range, value)| (self.resolve_range(range, &content), value))
}
pub fn max_by_key<'a, C, D, F, K>(
&self,
content: C,
mut extract_key: F,
) -> Option<(Range<D>, &T)>
where
C: Into<Content<'a>>,
D: 'a + TextDimension<'a>,
F: FnMut(&T) -> K,
K: Ord,
{
let content = content.into();
self.entries
.iter()
.max_by_key(|(_, value)| extract_key(value))
.map(|(range, value)| (self.resolve_range(range, &content), value))
}
fn resolve_range<'a, D>(&self, range: &Range<FullOffset>, content: &Content<'a>) -> Range<D>
where
D: 'a + TextDimension<'a>,
{
let mut anchor = Anchor {
full_offset: range.start,
bias: self.start_bias,
version: self.version.clone(),
};
let start = content.summary_for_anchor(&anchor);
anchor.full_offset = range.end;
anchor.bias = self.end_bias;
let end = content.summary_for_anchor(&anchor);
start..end
}
}
impl<T: PartialEq> PartialEq for AnchorRangeMap<T> {
fn eq(&self, other: &Self) -> bool {
self.version == other.version && self.entries == other.entries
}
}
impl<T: Eq> Eq for AnchorRangeMap<T> {}
impl<T: Debug> Debug for AnchorRangeMap<T> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
let mut f = f.debug_map();
for (range, value) in &self.entries {
f.key(range);
f.value(value);
}
f.finish()
}
}
impl Debug for AnchorRangeSet {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut f = f.debug_set();
for (range, _) in &self.0.entries {
f.entry(range);
}
f.finish()
}
}
impl AnchorRangeSet {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn version(&self) -> &clock::Global {
self.0.version()
}
pub fn ranges<'a, D, C>(&'a self, content: C) -> impl 'a + Iterator<Item = Range<Point>>
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.0.ranges(content).map(|(range, _)| range)
}
}
impl<T: Clone> Default for AnchorRangeMultimap<T> {
fn default() -> Self {
Self {
entries: Default::default(),
version: Default::default(),
start_bias: Bias::Left,
end_bias: Bias::Left,
}
}
}
impl<T: Clone> AnchorRangeMultimap<T> {
pub fn version(&self) -> &clock::Global {
&self.version
}
pub fn intersecting_ranges<'a, I, O>(
&'a self,
range: Range<I>,
content: Content<'a>,
inclusive: bool,
) -> impl Iterator<Item = (usize, Range<O>, &T)> + 'a
where
I: ToOffset,
O: FromAnchor,
{
let end_bias = if inclusive { Bias::Right } else { Bias::Left };
let range = range.start.to_full_offset(&content, Bias::Left)
..range.end.to_full_offset(&content, end_bias);
let mut cursor = self.entries.filter::<_, usize>(
{
let content = content.clone();
let mut endpoint = Anchor {
full_offset: FullOffset(0),
bias: Bias::Right,
version: self.version.clone(),
};
move |summary: &AnchorRangeMultimapSummary| {
endpoint.full_offset = summary.max_end;
endpoint.bias = self.end_bias;
let max_end = endpoint.to_full_offset(&content, self.end_bias);
let start_cmp = range.start.cmp(&max_end);
endpoint.full_offset = summary.min_start;
endpoint.bias = self.start_bias;
let min_start = endpoint.to_full_offset(&content, self.start_bias);
let end_cmp = range.end.cmp(&min_start);
if inclusive {
start_cmp <= Ordering::Equal && end_cmp >= Ordering::Equal
} else {
start_cmp == Ordering::Less && end_cmp == Ordering::Greater
}
}
},
&(),
);
std::iter::from_fn({
let mut endpoint = Anchor {
full_offset: FullOffset(0),
bias: Bias::Left,
version: self.version.clone(),
};
move || {
if let Some(item) = cursor.item() {
let ix = *cursor.start();
endpoint.full_offset = item.range.start;
endpoint.bias = self.start_bias;
let start = O::from_anchor(&endpoint, &content);
endpoint.full_offset = item.range.end;
endpoint.bias = self.end_bias;
let end = O::from_anchor(&endpoint, &content);
let value = &item.value;
cursor.next(&());
Some((ix, start..end, value))
} else {
None
}
}
})
}
pub fn from_full_offset_ranges(
version: clock::Global,
start_bias: Bias,
end_bias: Bias,
entries: impl Iterator<Item = (Range<FullOffset>, T)>,
) -> Self {
Self {
version,
start_bias,
end_bias,
entries: SumTree::from_iter(
entries.map(|(range, value)| AnchorRangeMultimapEntry {
range: FullOffsetRange {
start: range.start,
end: range.end,
},
value,
}),
&(),
),
}
}
pub fn full_offset_ranges(&self) -> impl Iterator<Item = (Range<FullOffset>, &T)> {
self.entries
.cursor::<()>()
.map(|entry| (entry.range.start..entry.range.end, &entry.value))
}
pub fn filter<'a, O, F>(
&'a self,
content: Content<'a>,
mut f: F,
) -> impl 'a + Iterator<Item = (usize, Range<O>, &T)>
where
O: FromAnchor,
F: 'a + FnMut(&'a T) -> bool,
{
let mut endpoint = Anchor {
full_offset: FullOffset(0),
bias: Bias::Left,
version: self.version.clone(),
};
self.entries
.cursor::<()>()
.enumerate()
.filter_map(move |(ix, entry)| {
if f(&entry.value) {
endpoint.full_offset = entry.range.start;
endpoint.bias = self.start_bias;
let start = O::from_anchor(&endpoint, &content);
endpoint.full_offset = entry.range.end;
endpoint.bias = self.end_bias;
let end = O::from_anchor(&endpoint, &content);
Some((ix, start..end, &entry.value))
} else {
None
}
})
}
}
impl<T: Clone> sum_tree::Item for AnchorRangeMultimapEntry<T> {
type Summary = AnchorRangeMultimapSummary;
fn summary(&self) -> Self::Summary {
AnchorRangeMultimapSummary {
start: self.range.start,
end: self.range.end,
min_start: self.range.start,
max_end: self.range.end,
count: 1,
}
}
}
impl Default for AnchorRangeMultimapSummary {
fn default() -> Self {
Self {
start: FullOffset(0),
end: FullOffset::MAX,
min_start: FullOffset::MAX,
max_end: FullOffset(0),
count: 0,
}
}
}
impl sum_tree::Summary for AnchorRangeMultimapSummary {
type Context = ();
fn add_summary(&mut self, other: &Self, _: &Self::Context) {
self.min_start = self.min_start.min(other.min_start);
self.max_end = self.max_end.max(other.max_end);
#[cfg(debug_assertions)]
{
let start_comparison = self.start.cmp(&other.start);
assert!(start_comparison <= Ordering::Equal);
if start_comparison == Ordering::Equal {
assert!(self.end.cmp(&other.end) >= Ordering::Equal);
}
}
self.start = other.start;
self.end = other.end;
self.count += other.count;
}
}
impl Default for FullOffsetRange {
fn default() -> Self {
Self {
start: FullOffset(0),
end: FullOffset::MAX,
}
}
}
impl<'a> sum_tree::Dimension<'a, AnchorRangeMultimapSummary> for usize {
fn add_summary(&mut self, summary: &'a AnchorRangeMultimapSummary, _: &()) {
*self += summary.count;
}
}
impl<'a> sum_tree::Dimension<'a, AnchorRangeMultimapSummary> for FullOffsetRange {
fn add_summary(&mut self, summary: &'a AnchorRangeMultimapSummary, _: &()) {
self.start = summary.start;
self.end = summary.end;
}
}
impl<'a> sum_tree::SeekTarget<'a, AnchorRangeMultimapSummary, FullOffsetRange> for FullOffsetRange {
fn cmp(&self, cursor_location: &FullOffsetRange, _: &()) -> Ordering {
Ord::cmp(&self.start, &cursor_location.start)
.then_with(|| Ord::cmp(&cursor_location.end, &self.end))
}
}
pub trait AnchorRangeExt {
fn cmp<'a>(&self, b: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering>;
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize>;
}
impl AnchorRangeExt for Range<Anchor> {
fn cmp<'a>(&self, other: &Range<Anchor>, buffer: impl Into<Content<'a>>) -> Result<Ordering> {
let buffer = buffer.into();
Ok(match self.start.cmp(&other.start, &buffer)? {
Ordering::Equal => other.end.cmp(&self.end, buffer)?,
ord @ _ => ord,
})
}
fn to_offset<'a>(&self, content: impl Into<Content<'a>>) -> Range<usize> {
let content = content.into();
self.start.to_offset(&content)..self.end.to_offset(&content)
}
}

View file

@ -0,0 +1,127 @@
use super::Operation;
use std::{fmt::Debug, ops::Add};
use sum_tree::{Cursor, Dimension, Edit, Item, KeyedItem, SumTree, Summary};
#[derive(Clone, Debug)]
pub struct OperationQueue(SumTree<Operation>);
#[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 OperationQueue {
pub fn new() -> Self {
OperationQueue(SumTree::new())
}
pub fn len(&self) -> usize {
self.0.summary().len
}
pub fn insert(&mut self, mut ops: Vec<Operation>) {
ops.sort_by_key(|op| op.lamport_timestamp());
ops.dedup_by_key(|op| op.lamport_timestamp());
self.0
.edit(ops.into_iter().map(Edit::Insert).collect(), &());
}
pub fn drain(&mut self) -> Self {
let clone = self.clone();
self.0 = SumTree::new();
clone
}
pub fn cursor(&self) -> Cursor<Operation, ()> {
self.0.cursor()
}
}
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 Operation {
type Summary = OperationSummary;
fn summary(&self) -> Self::Summary {
OperationSummary {
key: OperationKey::new(self.lamport_timestamp()),
len: 1,
}
}
}
impl KeyedItem for Operation {
type Key = OperationKey;
fn key(&self) -> Self::Key {
OperationKey::new(self.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![
Operation::Test(clock.tick()),
Operation::Test(clock.tick()),
]);
assert_eq!(queue.len(), 2);
queue.insert(vec![Operation::Test(clock.tick())]);
assert_eq!(queue.len(), 3);
drop(queue.drain());
assert_eq!(queue.len(), 0);
queue.insert(vec![Operation::Test(clock.tick())]);
assert_eq!(queue.len(), 1);
}
#[derive(Clone, Debug, Eq, PartialEq)]
struct TestOperation(clock::Lamport);
}

120
crates/text/src/point.rs Normal file
View file

@ -0,0 +1,120 @@
use std::{
cmp::Ordering,
ops::{Add, AddAssign, Sub},
};
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
pub struct Point {
pub row: u32,
pub column: u32,
}
impl Point {
pub const MAX: Self = Self {
row: u32::MAX,
column: u32::MAX,
};
pub fn new(row: u32, column: u32) -> Self {
Point { row, column }
}
pub fn zero() -> Self {
Point::new(0, 0)
}
pub fn from_str(s: &str) -> Self {
let mut point = Self::zero();
for (row, line) in s.split('\n').enumerate() {
point.row = row as u32;
point.column = line.len() as u32;
}
point
}
pub fn is_zero(&self) -> bool {
self.row == 0 && self.column == 0
}
}
impl<'a> Add<&'a Self> for Point {
type Output = Point;
fn add(self, other: &'a Self) -> Self::Output {
self + *other
}
}
impl Add for Point {
type Output = Point;
fn add(self, other: Self) -> Self::Output {
if other.row == 0 {
Point::new(self.row, self.column + other.column)
} else {
Point::new(self.row + other.row, other.column)
}
}
}
impl<'a> Sub<&'a Self> for Point {
type Output = Point;
fn sub(self, other: &'a Self) -> Self::Output {
self - *other
}
}
impl Sub for Point {
type Output = Point;
fn sub(self, other: Self) -> Self::Output {
debug_assert!(other <= self);
if self.row == other.row {
Point::new(0, self.column - other.column)
} else {
Point::new(self.row - other.row, self.column)
}
}
}
impl<'a> AddAssign<&'a Self> for Point {
fn add_assign(&mut self, other: &'a Self) {
*self += *other;
}
}
impl AddAssign<Self> for Point {
fn add_assign(&mut self, other: Self) {
if other.row == 0 {
self.column += other.column;
} else {
self.row += other.row;
self.column = other.column;
}
}
}
impl PartialOrd for Point {
fn partial_cmp(&self, other: &Point) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Point {
#[cfg(target_pointer_width = "64")]
fn cmp(&self, other: &Point) -> Ordering {
let a = (self.row as usize) << 32 | self.column as usize;
let b = (other.row as usize) << 32 | other.column as usize;
a.cmp(&b)
}
#[cfg(target_pointer_width = "32")]
fn cmp(&self, other: &Point) -> Ordering {
match self.row.cmp(&other.row) {
Ordering::Equal => self.column.cmp(&other.column),
comparison @ _ => comparison,
}
}
}

View file

@ -0,0 +1,111 @@
use std::{
cmp::Ordering,
ops::{Add, AddAssign, Sub},
};
#[derive(Clone, Copy, Default, Eq, PartialEq, Debug, Hash)]
pub struct PointUtf16 {
pub row: u32,
pub column: u32,
}
impl PointUtf16 {
pub const MAX: Self = Self {
row: u32::MAX,
column: u32::MAX,
};
pub fn new(row: u32, column: u32) -> Self {
PointUtf16 { row, column }
}
pub fn zero() -> Self {
PointUtf16::new(0, 0)
}
pub fn is_zero(&self) -> bool {
self.row == 0 && self.column == 0
}
}
impl<'a> Add<&'a Self> for PointUtf16 {
type Output = PointUtf16;
fn add(self, other: &'a Self) -> Self::Output {
self + *other
}
}
impl Add for PointUtf16 {
type Output = PointUtf16;
fn add(self, other: Self) -> Self::Output {
if other.row == 0 {
PointUtf16::new(self.row, self.column + other.column)
} else {
PointUtf16::new(self.row + other.row, other.column)
}
}
}
impl<'a> Sub<&'a Self> for PointUtf16 {
type Output = PointUtf16;
fn sub(self, other: &'a Self) -> Self::Output {
self - *other
}
}
impl Sub for PointUtf16 {
type Output = PointUtf16;
fn sub(self, other: Self) -> Self::Output {
debug_assert!(other <= self);
if self.row == other.row {
PointUtf16::new(0, self.column - other.column)
} else {
PointUtf16::new(self.row - other.row, self.column)
}
}
}
impl<'a> AddAssign<&'a Self> for PointUtf16 {
fn add_assign(&mut self, other: &'a Self) {
*self += *other;
}
}
impl AddAssign<Self> for PointUtf16 {
fn add_assign(&mut self, other: Self) {
if other.row == 0 {
self.column += other.column;
} else {
self.row += other.row;
self.column = other.column;
}
}
}
impl PartialOrd for PointUtf16 {
fn partial_cmp(&self, other: &PointUtf16) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PointUtf16 {
#[cfg(target_pointer_width = "64")]
fn cmp(&self, other: &PointUtf16) -> Ordering {
let a = (self.row as usize) << 32 | self.column as usize;
let b = (other.row as usize) << 32 | other.column as usize;
a.cmp(&b)
}
#[cfg(target_pointer_width = "32")]
fn cmp(&self, other: &PointUtf16) -> Ordering {
match self.row.cmp(&other.row) {
Ordering::Equal => self.column.cmp(&other.column),
comparison @ _ => comparison,
}
}
}

View file

@ -0,0 +1,28 @@
use rand::prelude::*;
pub struct RandomCharIter<T: Rng>(T);
impl<T: Rng> RandomCharIter<T> {
pub fn new(rng: T) -> Self {
Self(rng)
}
}
impl<T: Rng> Iterator for RandomCharIter<T> {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
match self.0.gen_range(0..100) {
// whitespace
0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(),
// two-byte greek letters
20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))),
// three-byte characters
33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(),
// four-byte characters
46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(),
// ascii letters
_ => Some(self.0.gen_range(b'a'..b'z' + 1).into()),
}
}
}

1014
crates/text/src/rope.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,174 @@
use sum_tree::Bias;
use crate::rope::TextDimension;
use super::{AnchorRangeMap, Buffer, Content, Point, ToOffset, ToPoint};
use std::{cmp::Ordering, ops::Range, sync::Arc};
pub type SelectionSetId = clock::Lamport;
pub type SelectionsVersion = usize;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SelectionGoal {
None,
Column(u32),
ColumnRange { start: u32, end: u32 },
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Selection<T> {
pub id: usize,
pub start: T,
pub end: T,
pub reversed: bool,
pub goal: SelectionGoal,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct SelectionSet {
pub id: SelectionSetId,
pub active: bool,
pub selections: Arc<AnchorRangeMap<SelectionState>>,
}
#[derive(Debug, Eq, PartialEq)]
pub struct SelectionState {
pub id: usize,
pub reversed: bool,
pub goal: SelectionGoal,
}
impl<T: Clone> Selection<T> {
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()
}
}
}
impl<T: ToOffset + ToPoint + Copy + Ord> Selection<T> {
pub fn is_empty(&self) -> bool {
self.start == self.end
}
pub fn set_head(&mut self, head: T) {
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;
}
}
pub fn point_range(&self, buffer: &Buffer) -> Range<Point> {
let start = self.start.to_point(buffer);
let end = self.end.to_point(buffer);
if self.reversed {
end..start
} else {
start..end
}
}
pub fn offset_range(&self, buffer: &Buffer) -> Range<usize> {
let start = self.start.to_offset(buffer);
let end = self.end.to_offset(buffer);
if self.reversed {
end..start
} else {
start..end
}
}
}
impl SelectionSet {
pub fn len(&self) -> usize {
self.selections.len()
}
pub fn selections<'a, D, C>(&'a self, content: C) -> impl 'a + Iterator<Item = Selection<D>>
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.selections
.ranges(content)
.map(|(range, state)| Selection {
id: state.id,
start: range.start,
end: range.end,
reversed: state.reversed,
goal: state.goal,
})
}
pub fn intersecting_selections<'a, D, I, C>(
&'a self,
range: Range<(I, Bias)>,
content: C,
) -> impl 'a + Iterator<Item = Selection<D>>
where
D: 'a + TextDimension<'a>,
I: 'a + ToOffset,
C: 'a + Into<Content<'a>>,
{
self.selections
.intersecting_ranges(range, content)
.map(|(range, state)| Selection {
id: state.id,
start: range.start,
end: range.end,
reversed: state.reversed,
goal: state.goal,
})
}
pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.selections
.min_by_key(content, |selection| selection.id)
.map(|(range, state)| Selection {
id: state.id,
start: range.start,
end: range.end,
reversed: state.reversed,
goal: state.goal,
})
}
pub fn newest_selection<'a, D, C>(&'a self, content: C) -> Option<Selection<D>>
where
D: 'a + TextDimension<'a>,
C: 'a + Into<Content<'a>>,
{
self.selections
.max_by_key(content, |selection| selection.id)
.map(|(range, state)| Selection {
id: state.id,
start: range.start,
end: range.end,
reversed: state.reversed,
goal: state.goal,
})
}
}

665
crates/text/src/tests.rs Normal file
View file

@ -0,0 +1,665 @@
use super::*;
use clock::ReplicaId;
use rand::prelude::*;
use std::{
cmp::Ordering,
env,
iter::Iterator,
time::{Duration, Instant},
};
#[test]
fn test_edit() {
let mut buffer = Buffer::new(0, 0, History::new("abc".into()));
assert_eq!(buffer.text(), "abc");
buffer.edit(vec![3..3], "def");
assert_eq!(buffer.text(), "abcdef");
buffer.edit(vec![0..0], "ghi");
assert_eq!(buffer.text(), "ghiabcdef");
buffer.edit(vec![5..5], "jkl");
assert_eq!(buffer.text(), "ghiabjklcdef");
buffer.edit(vec![6..7], "");
assert_eq!(buffer.text(), "ghiabjlcdef");
buffer.edit(vec![4..9], "mno");
assert_eq!(buffer.text(), "ghiamnoef");
}
#[gpui::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::<String>();
let mut buffer = Buffer::new(0, 0, History::new(reference_string.clone().into()));
buffer.history.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 (old_ranges, new_text, _) = buffer.randomly_edit(&mut rng, 5);
for old_range in old_ranges.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])
);
if rng.gen_bool(0.3) {
buffer_versions.push(buffer.clone());
}
}
for mut old_buffer in buffer_versions {
let edits = buffer
.edits_since::<usize>(&old_buffer.version)
.collect::<Vec<_>>();
log::info!(
"mutating old buffer version {:?}, text: {:?}, edits since: {:?}",
old_buffer.version(),
old_buffer.text(),
edits,
);
for edit in edits {
let new_text: String = buffer.text_for_range(edit.new.clone()).collect();
old_buffer.edit(
Some(edit.new.start..edit.new.start + edit.old.len()),
new_text,
);
}
assert_eq!(old_buffer.text(), buffer.text());
}
}
#[test]
fn test_line_len() {
let mut buffer = Buffer::new(0, 0, History::new("".into()));
buffer.edit(vec![0..0], "abcd\nefg\nhij");
buffer.edit(vec![12..12], "kl\nmno");
buffer.edit(vec![18..18], "\npqrs\n");
buffer.edit(vec![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_text_summary_for_range() {
let buffer = Buffer::new(0, 0, History::new("ab\nefg\nhklm\nnopqrs\ntuvwxyz".into()));
assert_eq!(
buffer.text_summary_for_range(1..3),
TextSummary {
bytes: 2,
lines: Point::new(1, 0),
lines_utf16: PointUtf16::new(1, 0),
first_line_chars: 1,
last_line_chars: 0,
longest_row: 0,
longest_row_chars: 1,
}
);
assert_eq!(
buffer.text_summary_for_range(1..12),
TextSummary {
bytes: 11,
lines: Point::new(3, 0),
lines_utf16: PointUtf16::new(3, 0),
first_line_chars: 1,
last_line_chars: 0,
longest_row: 2,
longest_row_chars: 4,
}
);
assert_eq!(
buffer.text_summary_for_range(0..20),
TextSummary {
bytes: 20,
lines: Point::new(4, 1),
lines_utf16: PointUtf16::new(4, 1),
first_line_chars: 2,
last_line_chars: 1,
longest_row: 3,
longest_row_chars: 6,
}
);
assert_eq!(
buffer.text_summary_for_range(0..22),
TextSummary {
bytes: 22,
lines: Point::new(4, 3),
lines_utf16: PointUtf16::new(4, 3),
first_line_chars: 2,
last_line_chars: 3,
longest_row: 3,
longest_row_chars: 6,
}
);
assert_eq!(
buffer.text_summary_for_range(7..22),
TextSummary {
bytes: 15,
lines: Point::new(2, 3),
lines_utf16: PointUtf16::new(2, 3),
first_line_chars: 4,
last_line_chars: 3,
longest_row: 1,
longest_row_chars: 6,
}
);
}
#[test]
fn test_chars_at() {
let mut buffer = Buffer::new(0, 0, History::new("".into()));
buffer.edit(vec![0..0], "abcd\nefgh\nij");
buffer.edit(vec![12..12], "kl\nmno");
buffer.edit(vec![18..18], "\npqrs");
buffer.edit(vec![18..21], "\nPQ");
let chars = buffer.chars_at(Point::new(0, 0));
assert_eq!(chars.collect::<String>(), "abcd\nefgh\nijkl\nmno\nPQrs");
let chars = buffer.chars_at(Point::new(1, 0));
assert_eq!(chars.collect::<String>(), "efgh\nijkl\nmno\nPQrs");
let chars = buffer.chars_at(Point::new(2, 0));
assert_eq!(chars.collect::<String>(), "ijkl\nmno\nPQrs");
let chars = buffer.chars_at(Point::new(3, 0));
assert_eq!(chars.collect::<String>(), "mno\nPQrs");
let chars = buffer.chars_at(Point::new(4, 0));
assert_eq!(chars.collect::<String>(), "PQrs");
// Regression test:
let mut buffer = Buffer::new(0, 0, History::new("".into()));
buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n");
buffer.edit(vec![60..60], "\n");
let chars = buffer.chars_at(Point::new(6, 0));
assert_eq!(chars.collect::<String>(), " \"xray_wasm\",\n]\n");
}
#[test]
fn test_anchors() {
let mut buffer = Buffer::new(0, 0, History::new("".into()));
buffer.edit(vec![0..0], "abc");
let left_anchor = buffer.anchor_before(2);
let right_anchor = buffer.anchor_after(2);
buffer.edit(vec![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(vec![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(vec![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(vec![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)
.unwrap(),
Ordering::Equal
);
assert_eq!(
anchor_at_offset_1
.cmp(&anchor_at_offset_1, &buffer)
.unwrap(),
Ordering::Equal
);
assert_eq!(
anchor_at_offset_2
.cmp(&anchor_at_offset_2, &buffer)
.unwrap(),
Ordering::Equal
);
assert_eq!(
anchor_at_offset_0
.cmp(&anchor_at_offset_1, &buffer)
.unwrap(),
Ordering::Less
);
assert_eq!(
anchor_at_offset_1
.cmp(&anchor_at_offset_2, &buffer)
.unwrap(),
Ordering::Less
);
assert_eq!(
anchor_at_offset_0
.cmp(&anchor_at_offset_2, &buffer)
.unwrap(),
Ordering::Less
);
assert_eq!(
anchor_at_offset_1
.cmp(&anchor_at_offset_0, &buffer)
.unwrap(),
Ordering::Greater
);
assert_eq!(
anchor_at_offset_2
.cmp(&anchor_at_offset_1, &buffer)
.unwrap(),
Ordering::Greater
);
assert_eq!(
anchor_at_offset_2
.cmp(&anchor_at_offset_0, &buffer)
.unwrap(),
Ordering::Greater
);
}
#[test]
fn test_anchors_at_start_and_end() {
let mut buffer = Buffer::new(0, 0, History::new("".into()));
let before_start_anchor = buffer.anchor_before(0);
let after_end_anchor = buffer.anchor_after(0);
buffer.edit(vec![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(vec![3..3], "def");
buffer.edit(vec![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, History::new("1234".into()));
// Set group interval to zero so as to not group edits in the undo stack.
buffer.history.group_interval = Duration::from_secs(0);
buffer.edit(vec![1..1], "abx");
buffer.edit(vec![3..4], "yzef");
buffer.edit(vec![3..5], "cd");
assert_eq!(buffer.text(), "1abcdef234");
let transactions = buffer.history.undo_stack.clone();
assert_eq!(transactions.len(), 3);
buffer.undo_or_redo(transactions[0].clone()).unwrap();
assert_eq!(buffer.text(), "1cdef234");
buffer.undo_or_redo(transactions[0].clone()).unwrap();
assert_eq!(buffer.text(), "1abcdef234");
buffer.undo_or_redo(transactions[1].clone()).unwrap();
assert_eq!(buffer.text(), "1abcdx234");
buffer.undo_or_redo(transactions[2].clone()).unwrap();
assert_eq!(buffer.text(), "1abx234");
buffer.undo_or_redo(transactions[1].clone()).unwrap();
assert_eq!(buffer.text(), "1abyzef234");
buffer.undo_or_redo(transactions[2].clone()).unwrap();
assert_eq!(buffer.text(), "1abcdef234");
buffer.undo_or_redo(transactions[2].clone()).unwrap();
assert_eq!(buffer.text(), "1abyzef234");
buffer.undo_or_redo(transactions[0].clone()).unwrap();
assert_eq!(buffer.text(), "1yzef234");
buffer.undo_or_redo(transactions[1].clone()).unwrap();
assert_eq!(buffer.text(), "1234");
}
#[test]
fn test_history() {
let mut now = Instant::now();
let mut buffer = Buffer::new(0, 0, History::new("123456".into()));
let set_id = if let Operation::UpdateSelections { set_id, .. } =
buffer.add_selection_set(&buffer.selections_from_ranges(vec![4..4]).unwrap())
{
set_id
} else {
unreachable!()
};
buffer.start_transaction_at(Some(set_id), now).unwrap();
buffer.edit(vec![2..4], "cd");
buffer.end_transaction_at(Some(set_id), now).unwrap();
assert_eq!(buffer.text(), "12cd56");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
buffer.start_transaction_at(Some(set_id), now).unwrap();
buffer
.update_selection_set(set_id, &buffer.selections_from_ranges(vec![1..3]).unwrap())
.unwrap();
buffer.edit(vec![4..5], "e");
buffer.end_transaction_at(Some(set_id), now).unwrap();
assert_eq!(buffer.text(), "12cde6");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
now += buffer.history.group_interval + Duration::from_millis(1);
buffer.start_transaction_at(Some(set_id), now).unwrap();
buffer
.update_selection_set(set_id, &buffer.selections_from_ranges(vec![2..2]).unwrap())
.unwrap();
buffer.edit(vec![0..1], "a");
buffer.edit(vec![1..1], "b");
buffer.end_transaction_at(Some(set_id), now).unwrap();
assert_eq!(buffer.text(), "ab2cde6");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
// Last transaction happened past the group interval, undo it on its
// own.
buffer.undo();
assert_eq!(buffer.text(), "12cde6");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
// First two transactions happened within the group interval, undo them
// together.
buffer.undo();
assert_eq!(buffer.text(), "123456");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]);
// Redo the first two transactions together.
buffer.redo();
assert_eq!(buffer.text(), "12cde6");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]);
// Redo the last transaction on its own.
buffer.redo();
assert_eq!(buffer.text(), "ab2cde6");
assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]);
buffer.start_transaction_at(None, now).unwrap();
assert!(buffer.end_transaction_at(None, now).is_none());
buffer.undo();
assert_eq!(buffer.text(), "12cde6");
}
#[test]
fn test_concurrent_edits() {
let text = "abcdef";
let mut buffer1 = Buffer::new(1, 0, History::new(text.into()));
let mut buffer2 = Buffer::new(2, 0, History::new(text.into()));
let mut buffer3 = Buffer::new(3, 0, History::new(text.into()));
let buf1_op = buffer1.edit(vec![1..2], "12");
assert_eq!(buffer1.text(), "a12cdef");
let buf2_op = buffer2.edit(vec![3..4], "34");
assert_eq!(buffer2.text(), "abc34ef");
let buf3_op = buffer3.edit(vec![5..6], "56");
assert_eq!(buffer3.text(), "abcde56");
buffer1.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
buffer1.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
buffer2.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
buffer2.apply_op(Operation::Edit(buf3_op.clone())).unwrap();
buffer3.apply_op(Operation::Edit(buf1_op.clone())).unwrap();
buffer3.apply_op(Operation::Edit(buf2_op.clone())).unwrap();
assert_eq!(buffer1.text(), "a12c34e56");
assert_eq!(buffer2.text(), "a12c34e56");
assert_eq!(buffer3.text(), "a12c34e56");
}
#[gpui::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::<String>();
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, History::new(base_text.clone().into()));
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 ops = buffer.randomly_mutate(&mut rng);
network.broadcast(buffer.replica_id, ops);
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();
}
}
_ => {}
}
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
);
assert_eq!(
buffer.selection_sets().collect::<HashMap<_, _>>(),
first_buffer.selection_sets().collect::<HashMap<_, _>>()
);
assert_eq!(
buffer
.all_selection_ranges::<usize>()
.collect::<HashMap<_, _>>(),
first_buffer
.all_selection_ranges::<usize>()
.collect::<HashMap<_, _>>()
);
}
}
#[derive(Clone)]
struct Envelope<T: Clone> {
message: T,
sender: ReplicaId,
}
struct Network<T: Clone, R: rand::Rng> {
inboxes: std::collections::BTreeMap<ReplicaId, Vec<Envelope<T>>>,
all_messages: Vec<T>,
rng: R,
}
impl<T: Clone, R: rand::Rng> Network<T, R> {
fn new(rng: R) -> Self {
Network {
inboxes: Default::default(),
all_messages: Vec::new(),
rng,
}
}
fn add_peer(&mut self, id: ReplicaId) {
self.inboxes.insert(id, Vec::new());
}
fn is_idle(&self) -> bool {
self.inboxes.values().all(|i| i.is_empty())
}
fn broadcast(&mut self, sender: ReplicaId, messages: Vec<T>) {
for (replica, inbox) in self.inboxes.iter_mut() {
if *replica != sender {
for message in &messages {
let min_index = inbox
.iter()
.enumerate()
.rev()
.find_map(|(index, envelope)| {
if sender == envelope.sender {
Some(index + 1)
} else {
None
}
})
.unwrap_or(0);
// Insert one or more duplicates of this message *after* the previous
// message delivered by this replica.
for _ in 0..self.rng.gen_range(1..4) {
let insertion_index = self.rng.gen_range(min_index..inbox.len() + 1);
inbox.insert(
insertion_index,
Envelope {
message: message.clone(),
sender,
},
);
}
}
}
}
self.all_messages.extend(messages);
}
fn has_unreceived(&self, receiver: ReplicaId) -> bool {
!self.inboxes[&receiver].is_empty()
}
fn receive(&mut self, receiver: ReplicaId) -> Vec<T> {
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()
}
}

2405
crates/text/src/text.rs Normal file

File diff suppressed because it is too large Load diff