@@ -1,18 +1,18 @@
mod anchor;
mod point;
+pub mod rope;
mod selection;
-mod text;
pub use anchor::*;
pub use point::*;
+pub use rope::{Rope, TextSummary};
use seahash::SeaHasher;
pub use selection::*;
use similar::{ChangeTag, TextDiff};
-pub use text::*;
use crate::{
operation_queue::{self, OperationQueue},
- sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree},
+ sum_tree::{self, FilterCursor, SeekBias, SumTree},
time::{self, ReplicaId},
util::RandomCharIter,
worktree::FileHandle,
@@ -25,7 +25,8 @@ use std::{
cmp,
hash::BuildHasher,
iter::{self, Iterator},
- ops::{AddAssign, Range},
+ mem,
+ ops::Range,
str,
sync::Arc,
time::{Duration, Instant, SystemTime, UNIX_EPOCH},
@@ -58,6 +59,8 @@ type HashSet<T> = std::collections::HashSet<T>;
pub struct Buffer {
fragments: SumTree<Fragment>,
+ visible_text: Rope,
+ deleted_text: Rope,
insertion_splits: HashMap<time::Local, SumTree<InsertionSplit>>,
pub version: time::Global,
saved_version: time::Global,
@@ -75,10 +78,6 @@ pub struct Buffer {
lamport_clock: time::Lamport,
}
-pub struct Snapshot {
- fragments: SumTree<Fragment>,
-}
-
#[derive(Clone)]
struct Transaction {
start: time::Global,
@@ -92,6 +91,7 @@ struct Transaction {
#[derive(Clone)]
pub struct History {
+ // TODO: Turn this into a String or Rope, maybe.
pub base_text: Arc<str>,
ops: HashMap<time::Local, EditOperation>,
undo_stack: Vec<Transaction>,
@@ -238,18 +238,6 @@ impl UndoMap {
}
}
-#[derive(Clone)]
-pub struct CharIter<'a> {
- fragments_cursor: Cursor<'a, Fragment, usize, usize>,
- fragment_chars: str::Chars<'a>,
-}
-
-#[derive(Clone)]
-pub struct FragmentIter<'a> {
- cursor: Cursor<'a, Fragment, usize, usize>,
- started: bool,
-}
-
struct Edits<'a, F: Fn(&FragmentSummary) -> bool> {
cursor: FilterCursor<'a, F, Fragment, usize>,
undos: &'a UndoMap,
@@ -285,15 +273,14 @@ pub struct Insertion {
id: time::Local,
parent_id: time::Local,
offset_in_parent: usize,
- text: Text,
lamport_timestamp: time::Lamport,
}
#[derive(Eq, PartialEq, Clone, Debug)]
struct Fragment {
id: FragmentId,
- insertion: Insertion,
- text: Text,
+ insertion: Arc<Insertion>,
+ range_in_insertion: Range<usize>,
deletions: HashSet<time::Local>,
max_undos: time::Global,
visible: bool,
@@ -301,15 +288,22 @@ struct Fragment {
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct FragmentSummary {
- text_summary: TextSummary,
+ text: FragmentTextSummary,
max_fragment_id: FragmentId,
max_version: time::Global,
}
-#[derive(Eq, PartialEq, Clone, Debug, Ord, PartialOrd)]
-struct FragmentExtent {
- chars: usize,
- lines: Point,
+#[derive(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) {
+ self.visible += summary.text.visible;
+ self.deleted += summary.text.deleted;
+ }
}
#[derive(Eq, PartialEq, Clone, Debug)]
@@ -348,7 +342,7 @@ pub struct EditOperation {
end_id: time::Local,
end_offset: usize,
version_in_range: time::Global,
- new_text: Option<Text>,
+ new_text: Option<String>,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
@@ -418,16 +412,17 @@ impl Buffer {
saved_mtime = UNIX_EPOCH;
}
+ let mut visible_text = Rope::new();
let mut insertion_splits = HashMap::default();
let mut fragments = SumTree::new();
- let base_insertion = Insertion {
+ let base_text = Rope::from(history.base_text.as_ref());
+ let base_insertion = Arc::new(Insertion {
id: time::Local::default(),
parent_id: time::Local::default(),
offset_in_parent: 0,
- text: history.base_text.clone().into(),
lamport_timestamp: time::Lamport::default(),
- };
+ });
insertion_splits.insert(
base_insertion.id,
@@ -440,42 +435,36 @@ impl Buffer {
),
);
fragments.push(
- Fragment {
- id: FragmentId::min_value().clone(),
- insertion: base_insertion.clone(),
- text: base_insertion.text.slice(0..0),
- deletions: Default::default(),
- max_undos: Default::default(),
- visible: true,
- },
+ Fragment::new(
+ FragmentId::min_value().clone(),
+ base_insertion.clone(),
+ 0..0,
+ ),
&(),
);
- if base_insertion.text.len() > 0 {
+ if base_text.len() > 0 {
let base_fragment_id =
FragmentId::between(&FragmentId::min_value(), &FragmentId::max_value());
+ let range_in_insertion = 0..base_text.len();
+ visible_text = base_text.clone();
insertion_splits.get_mut(&base_insertion.id).unwrap().push(
InsertionSplit {
fragment_id: base_fragment_id.clone(),
- extent: base_insertion.text.len(),
+ extent: range_in_insertion.end,
},
&(),
);
fragments.push(
- Fragment {
- id: base_fragment_id,
- text: base_insertion.text.clone(),
- insertion: base_insertion,
- deletions: Default::default(),
- max_undos: Default::default(),
- visible: true,
- },
+ Fragment::new(base_fragment_id, base_insertion, range_in_insertion.clone()),
&(),
);
}
Self {
+ visible_text,
+ deleted_text: Rope::new(),
fragments,
insertion_splits,
version: time::Global::new(),
@@ -495,10 +484,8 @@ impl Buffer {
}
}
- pub fn snapshot(&self) -> Snapshot {
- Snapshot {
- fragments: self.fragments.clone(),
- }
+ pub fn snapshot(&self) -> Rope {
+ self.visible_text.clone()
}
pub fn file(&self) -> Option<&FileHandle> {
@@ -609,33 +596,12 @@ impl Buffer {
}
pub fn text_summary(&self) -> TextSummary {
- self.fragments.extent::<TextSummary>()
+ self.visible_text.summary()
}
pub fn text_summary_for_range(&self, range: Range<usize>) -> TextSummary {
- let mut summary = TextSummary::default();
-
- let mut cursor = self.fragments.cursor::<usize, usize>();
- cursor.seek(&range.start, SeekBias::Right, &());
-
- if let Some(fragment) = cursor.item() {
- let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
- let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += fragment.text.slice(summary_start..summary_end).summary();
- cursor.next();
- }
-
- if range.end > *cursor.start() {
- summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
-
- if let Some(fragment) = cursor.item() {
- let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
- let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += fragment.text.slice(summary_start..summary_end).summary();
- }
- }
-
- summary
+ // TODO: Use a dedicated ::summarize method in Rope.
+ self.visible_text.slice(range).summary()
}
pub fn len(&self) -> usize {
@@ -654,37 +620,15 @@ impl Buffer {
}
pub fn rightmost_point(&self) -> Point {
- self.fragments.summary().text_summary.rightmost_point
+ self.visible_text.summary().rightmost_point
}
pub fn rightmost_point_in_range(&self, range: Range<usize>) -> Point {
- let mut summary = TextSummary::default();
-
- let mut cursor = self.fragments.cursor::<usize, usize>();
- cursor.seek(&range.start, SeekBias::Right, &());
-
- if let Some(fragment) = cursor.item() {
- let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
- let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += fragment.text.slice(summary_start..summary_end).summary();
- cursor.next();
- }
-
- if range.end > *cursor.start() {
- summary += cursor.summary::<TextSummary>(&range.end, SeekBias::Right, &());
-
- if let Some(fragment) = cursor.item() {
- let summary_start = cmp::max(*cursor.start(), range.start) - cursor.start();
- let summary_end = cmp::min(range.end - cursor.start(), fragment.len());
- summary += fragment.text.slice(summary_start..summary_end).summary();
- }
- }
-
- summary.rightmost_point
+ self.text_summary_for_range(range).rightmost_point
}
pub fn max_point(&self) -> Point {
- self.fragments.extent()
+ self.visible_text.max_point()
}
pub fn line(&self, row: u32) -> Result<String> {
@@ -707,13 +651,13 @@ impl Buffer {
Ok(self.chars_at(start)?.take(end - start))
}
- pub fn chars(&self) -> CharIter {
+ pub fn chars(&self) -> rope::Chars {
self.chars_at(0).unwrap()
}
- pub fn chars_at<T: ToOffset>(&self, position: T) -> Result<CharIter> {
+ pub fn chars_at<T: ToOffset>(&self, position: T) -> Result<rope::Chars> {
let offset = position.to_offset(self)?;
- Ok(CharIter::new(&self.fragments, offset))
+ Ok(self.visible_text.chars_at(offset))
}
pub fn selections_changed_since(&self, since: SelectionsVersion) -> bool {
@@ -807,7 +751,7 @@ impl Buffer {
where
I: IntoIterator<Item = Range<S>>,
S: ToOffset,
- T: Into<Text>,
+ T: Into<String>,
{
self.start_transaction_at(None, Instant::now())?;
@@ -823,11 +767,12 @@ impl Buffer {
.map(|range| Ok(range.start.to_offset(self)?..range.end.to_offset(self)?))
.collect::<Result<Vec<Range<usize>>>>()?;
+ let has_new_text = new_text.is_some();
let ops = self.splice_fragments(
old_ranges
.into_iter()
- .filter(|old_range| new_text.is_some() || old_range.end > old_range.start),
- new_text.clone(),
+ .filter(|old_range| has_new_text || old_range.end > old_range.start),
+ new_text.into(),
);
for op in &ops {
@@ -1022,7 +967,7 @@ impl Buffer {
edit.start_offset,
edit.end_id,
edit.end_offset,
- edit.new_text.as_ref().cloned(),
+ edit.new_text.as_deref(),
&edit.version_in_range,
edit.id,
lamport_timestamp,
@@ -1064,29 +1009,38 @@ impl Buffer {
start_offset: usize,
end_id: time::Local,
end_offset: usize,
- new_text: Option<Text>,
+ mut new_text: Option<&str>,
version_in_range: &time::Global,
local_timestamp: time::Local,
lamport_timestamp: time::Lamport,
) -> Result<()> {
- let mut new_text = new_text.as_ref().cloned();
let start_fragment_id = self.resolve_fragment_id(start_id, start_offset)?;
let end_fragment_id = self.resolve_fragment_id(end_id, end_offset)?;
- let old_fragments = self.fragments.clone();
- let last_id = old_fragments.extent::<FragmentIdRef>().0.unwrap();
- let last_id_ref = FragmentIdRef::new(&last_id);
+ let mut old_visible_text = Rope::new();
+ let mut old_deleted_text = Rope::new();
+ let mut old_fragments = SumTree::new();
+ mem::swap(&mut old_visible_text, &mut self.visible_text);
+ mem::swap(&mut old_deleted_text, &mut self.deleted_text);
+ mem::swap(&mut old_fragments, &mut self.fragments);
- let mut cursor = old_fragments.cursor::<FragmentIdRef, ()>();
- let mut new_fragments =
- cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
+ let mut fragments_cursor = old_fragments.cursor::<FragmentIdRef, FragmentTextSummary>();
- if start_offset == cursor.item().unwrap().end_offset() {
- new_fragments.push(cursor.item().unwrap().clone(), &());
- cursor.next();
+ let mut new_fragments =
+ fragments_cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
+ let mut new_ropes =
+ RopeBuilder::new(old_visible_text.cursor(0), old_deleted_text.cursor(0));
+ new_ropes.push_tree(new_fragments.summary().text);
+
+ let start_fragment = fragments_cursor.item().unwrap();
+ if start_offset == start_fragment.range_in_insertion.end {
+ let fragment = fragments_cursor.item().unwrap().clone();
+ new_ropes.push_fragment(&fragment, fragment.visible);
+ new_fragments.push(fragment, &());
+ fragments_cursor.next();
}
- while let Some(fragment) = cursor.item() {
+ while let Some(fragment) = fragments_cursor.item() {
if new_text.is_none() && fragment.id > end_fragment_id {
break;
}
@@ -1097,21 +1051,22 @@ impl Buffer {
let split_start = if start_fragment_id == fragment.id {
start_offset
} else {
- fragment.start_offset()
+ fragment.range_in_insertion.start
};
let split_end = if end_fragment_id == fragment.id {
end_offset
} else {
- fragment.end_offset()
+ fragment.range_in_insertion.end
};
let (before_range, within_range, after_range) = self.split_fragment(
- cursor.prev_item().as_ref().unwrap(),
+ fragments_cursor.prev_item().as_ref().unwrap(),
&fragment,
split_start..split_end,
);
- let insertion = if let Some(new_text) = new_text.take() {
+ let insertion = if let Some(new_text) = new_text {
+ let prev_fragment = fragments_cursor.prev_item();
Some(self.build_fragment_to_insert(
- before_range.as_ref().or(cursor.prev_item()).unwrap(),
+ before_range.as_ref().or(prev_fragment).unwrap(),
within_range.as_ref().or(after_range.as_ref()),
new_text,
local_timestamp,
@@ -1121,62 +1076,78 @@ impl Buffer {
None
};
if let Some(fragment) = before_range {
+ new_ropes.push_fragment(&fragment, fragment.visible);
new_fragments.push(fragment, &());
}
if let Some(fragment) = insertion {
+ new_ropes.push_str(new_text.take().unwrap());
new_fragments.push(fragment, &());
}
if let Some(mut fragment) = within_range {
+ let fragment_was_visible = fragment.visible;
if fragment.was_visible(&version_in_range, &self.undo_map) {
fragment.deletions.insert(local_timestamp);
- fragment.visible = false;
+ if fragment.visible {
+ fragment.visible = false;
+ }
}
+
+ new_ropes.push_fragment(&fragment, fragment_was_visible);
new_fragments.push(fragment, &());
}
if let Some(fragment) = after_range {
+ new_ropes.push_fragment(&fragment, fragment.visible);
new_fragments.push(fragment, &());
}
} else {
if new_text.is_some() && lamport_timestamp > fragment.insertion.lamport_timestamp {
- new_fragments.push(
- self.build_fragment_to_insert(
- cursor.prev_item().as_ref().unwrap(),
- Some(&fragment),
- new_text.take().unwrap(),
- local_timestamp,
- lamport_timestamp,
- ),
- &(),
+ let new_text = new_text.take().unwrap();
+ let fragment = self.build_fragment_to_insert(
+ fragments_cursor.prev_item().as_ref().unwrap(),
+ Some(&fragment),
+ new_text,
+ local_timestamp,
+ lamport_timestamp,
);
+ new_ropes.push_str(new_text);
+ new_fragments.push(fragment, &());
}
+ let fragment_was_visible = fragment.visible;
if fragment.id < end_fragment_id
&& fragment.was_visible(&version_in_range, &self.undo_map)
{
fragment.deletions.insert(local_timestamp);
- fragment.visible = false;
+ if fragment.visible {
+ fragment.visible = false;
+ }
}
+
+ new_ropes.push_fragment(&fragment, fragment_was_visible);
new_fragments.push(fragment, &());
}
- cursor.next();
+ fragments_cursor.next();
}
if let Some(new_text) = new_text {
- new_fragments.push(
- self.build_fragment_to_insert(
- cursor.prev_item().as_ref().unwrap(),
- None,
- new_text,
- local_timestamp,
- lamport_timestamp,
- ),
- &(),
+ let fragment = self.build_fragment_to_insert(
+ fragments_cursor.prev_item().as_ref().unwrap(),
+ None,
+ new_text,
+ local_timestamp,
+ lamport_timestamp,
);
+ new_ropes.push_str(new_text);
+ new_fragments.push(fragment, &());
}
- new_fragments.push_tree(cursor.slice(&last_id_ref, SeekBias::Right, &()), &());
+ let (visible_text, deleted_text) = new_ropes.finish();
+ new_fragments.push_tree(fragments_cursor.suffix(&()), &());
+
self.fragments = new_fragments;
+ self.visible_text = visible_text;
+ self.deleted_text = deleted_text;
self.local_clock.observe(local_timestamp);
self.lamport_clock.observe(lamport_timestamp);
Ok(())
@@ -1251,58 +1222,83 @@ impl Buffer {
fn apply_undo(&mut self, undo: UndoOperation) -> Result<()> {
let mut new_fragments;
+ let mut old_visible_text = Rope::new();
+ let mut old_deleted_text = Rope::new();
+ mem::swap(&mut old_visible_text, &mut self.visible_text);
+ mem::swap(&mut old_deleted_text, &mut self.deleted_text);
+ let mut new_ropes =
+ RopeBuilder::new(old_visible_text.cursor(0), old_deleted_text.cursor(0));
self.undo_map.insert(undo);
let edit = &self.history.ops[&undo.edit_id];
let start_fragment_id = self.resolve_fragment_id(edit.start_id, edit.start_offset)?;
let end_fragment_id = self.resolve_fragment_id(edit.end_id, edit.end_offset)?;
- let mut cursor = self.fragments.cursor::<FragmentIdRef, ()>();
+
+ let mut fragments_cursor = self.fragments.cursor::<FragmentIdRef, ()>();
if edit.start_id == edit.end_id && edit.start_offset == edit.end_offset {
let splits = &self.insertion_splits[&undo.edit_id];
let mut insertion_splits = splits.cursor::<(), ()>().map(|s| &s.fragment_id).peekable();
let first_split_id = insertion_splits.next().unwrap();
- new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left, &());
+ new_fragments =
+ fragments_cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left, &());
+ new_ropes.push_tree(new_fragments.summary().text);
loop {
- let mut fragment = cursor.item().unwrap().clone();
+ let mut fragment = fragments_cursor.item().unwrap().clone();
+ let was_visible = fragment.visible;
fragment.visible = fragment.is_visible(&self.undo_map);
fragment.max_undos.observe(undo.id);
- new_fragments.push(fragment, &());
- cursor.next();
+
+ new_ropes.push_fragment(&fragment, was_visible);
+ new_fragments.push(fragment.clone(), &());
+
+ fragments_cursor.next();
if let Some(split_id) = insertion_splits.next() {
- new_fragments.push_tree(
- cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left, &()),
- &(),
- );
+ let slice =
+ fragments_cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left, &());
+ new_ropes.push_tree(slice.summary().text);
+ new_fragments.push_tree(slice, &());
} else {
break;
}
}
} else {
- new_fragments =
- cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &());
- while let Some(fragment) = cursor.item() {
+ new_fragments = fragments_cursor.slice(
+ &FragmentIdRef::new(&start_fragment_id),
+ SeekBias::Left,
+ &(),
+ );
+ new_ropes.push_tree(new_fragments.summary().text);
+
+ while let Some(fragment) = fragments_cursor.item() {
if fragment.id > end_fragment_id {
break;
} else {
let mut fragment = fragment.clone();
+ let fragment_was_visible = fragment.visible;
if edit.version_in_range.observed(fragment.insertion.id)
|| fragment.insertion.id == undo.edit_id
{
fragment.visible = fragment.is_visible(&self.undo_map);
fragment.max_undos.observe(undo.id);
}
+
+ new_ropes.push_fragment(&fragment, fragment_was_visible);
new_fragments.push(fragment, &());
- cursor.next();
+ fragments_cursor.next();
}
}
}
- new_fragments.push_tree(cursor.suffix(&()), &());
- drop(cursor);
+ new_fragments.push_tree(fragments_cursor.suffix(&()), &());
+ let (visible_text, deleted_text) = new_ropes.finish();
+ drop(fragments_cursor);
+
self.fragments = new_fragments;
+ self.visible_text = visible_text;
+ self.deleted_text = deleted_text;
Ok(())
}
@@ -1372,7 +1368,7 @@ impl Buffer {
.clone())
}
- fn splice_fragments<I>(&mut self, mut old_ranges: I, new_text: Option<Text>) -> Vec<Operation>
+ fn splice_fragments<I>(&mut self, mut old_ranges: I, new_text: Option<String>) -> Vec<Operation>
where
I: Iterator<Item = Range<usize>>,
{
@@ -1383,13 +1379,20 @@ impl Buffer {
let mut ops = Vec::with_capacity(old_ranges.size_hint().0);
- let old_fragments = self.fragments.clone();
- let mut cursor = old_fragments.cursor::<usize, usize>();
- let mut new_fragments = SumTree::new();
- new_fragments.push_tree(
- cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
- &(),
- );
+ let mut old_fragments = SumTree::new();
+ let mut old_visible_text = Rope::new();
+ let mut old_deleted_text = Rope::new();
+ mem::swap(&mut old_visible_text, &mut self.visible_text);
+ mem::swap(&mut old_deleted_text, &mut self.deleted_text);
+ mem::swap(&mut old_fragments, &mut self.fragments);
+
+ let mut fragments_cursor = old_fragments.cursor::<usize, usize>();
+ let mut new_fragments =
+ fragments_cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &());
+
+ let mut new_ropes =
+ RopeBuilder::new(old_visible_text.cursor(0), old_deleted_text.cursor(0));
+ new_ropes.push_tree(new_fragments.summary().text);
let mut start_id = None;
let mut start_offset = None;
@@ -1400,11 +1403,12 @@ impl Buffer {
let mut local_timestamp = self.local_clock.tick();
let mut lamport_timestamp = self.lamport_clock.tick();
- while cur_range.is_some() && cursor.item().is_some() {
- let mut fragment = cursor.item().unwrap().clone();
- let fragment_summary = cursor.item_summary().unwrap();
- let mut fragment_start = *cursor.start();
+ while cur_range.is_some() && fragments_cursor.item().is_some() {
+ let mut fragment = fragments_cursor.item().unwrap().clone();
+ let fragment_summary = fragments_cursor.item_summary().unwrap();
+ let mut fragment_start = *fragments_cursor.start();
let mut fragment_end = fragment_start + fragment.visible_len();
+ let fragment_was_visible = fragment.visible;
let old_split_tree = self
.insertion_splits
@@ -1412,7 +1416,7 @@ impl Buffer {
.unwrap();
let mut splits_cursor = old_split_tree.cursor::<usize, ()>();
let mut new_split_tree =
- splits_cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
+ splits_cursor.slice(&fragment.range_in_insertion.start, SeekBias::Right, &());
// Find all splices that start or end within the current fragment. Then, split the
// fragment and reassemble it in both trees accounting for the deleted and the newly
@@ -1421,14 +1425,17 @@ impl Buffer {
let range = cur_range.clone().unwrap();
if range.start > fragment_start {
let mut prefix = fragment.clone();
- prefix.set_end_offset(prefix.start_offset() + (range.start - fragment_start));
+ prefix.range_in_insertion.end =
+ prefix.range_in_insertion.start + (range.start - fragment_start);
prefix.id =
FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
- fragment.set_start_offset(prefix.end_offset());
+ fragment.range_in_insertion.start = prefix.range_in_insertion.end;
+
+ new_ropes.push_fragment(&prefix, prefix.visible);
new_fragments.push(prefix.clone(), &());
new_split_tree.push(
InsertionSplit {
- extent: prefix.end_offset() - prefix.start_offset(),
+ extent: prefix.range_in_insertion.end - prefix.range_in_insertion.start,
fragment_id: prefix.id,
},
&(),
@@ -1438,24 +1445,26 @@ impl Buffer {
if range.end == fragment_start {
end_id = Some(new_fragments.last().unwrap().insertion.id);
- end_offset = Some(new_fragments.last().unwrap().end_offset());
+ end_offset = Some(new_fragments.last().unwrap().range_in_insertion.end);
} else if range.end == fragment_end {
end_id = Some(fragment.insertion.id);
- end_offset = Some(fragment.end_offset());
+ end_offset = Some(fragment.range_in_insertion.end);
}
if range.start == fragment_start {
start_id = Some(new_fragments.last().unwrap().insertion.id);
- start_offset = Some(new_fragments.last().unwrap().end_offset());
+ start_offset = Some(new_fragments.last().unwrap().range_in_insertion.end);
if let Some(new_text) = new_text.clone() {
let new_fragment = self.build_fragment_to_insert(
&new_fragments.last().unwrap(),
Some(&fragment),
- new_text,
+ &new_text,
local_timestamp,
lamport_timestamp,
);
+
+ new_ropes.push_str(&new_text);
new_fragments.push(new_fragment, &());
}
}
@@ -1463,26 +1472,29 @@ impl Buffer {
if range.end < fragment_end {
if range.end > fragment_start {
let mut prefix = fragment.clone();
- prefix.set_end_offset(prefix.start_offset() + (range.end - fragment_start));
+ prefix.range_in_insertion.end =
+ prefix.range_in_insertion.start + (range.end - fragment_start);
prefix.id =
FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id);
version_in_range.observe_all(&fragment_summary.max_version);
- if fragment.visible {
+ if prefix.visible {
prefix.deletions.insert(local_timestamp);
prefix.visible = false;
}
- fragment.set_start_offset(prefix.end_offset());
+ fragment.range_in_insertion.start = prefix.range_in_insertion.end;
+ new_ropes.push_fragment(&prefix, fragment_was_visible);
new_fragments.push(prefix.clone(), &());
new_split_tree.push(
InsertionSplit {
- extent: prefix.end_offset() - prefix.start_offset(),
+ extent: prefix.range_in_insertion.end
+ - prefix.range_in_insertion.start,
fragment_id: prefix.id,
},
&(),
);
fragment_start = range.end;
end_id = Some(fragment.insertion.id);
- end_offset = Some(fragment.start_offset());
+ end_offset = Some(fragment.range_in_insertion.start);
}
} else {
version_in_range.observe_all(&fragment_summary.max_version);
@@ -1525,7 +1537,7 @@ impl Buffer {
}
new_split_tree.push(
InsertionSplit {
- extent: fragment.end_offset() - fragment.start_offset(),
+ extent: fragment.range_in_insertion.end - fragment.range_in_insertion.start,
fragment_id: fragment.id.clone(),
},
&(),
@@ -1537,14 +1549,17 @@ impl Buffer {
);
self.insertion_splits
.insert(fragment.insertion.id, new_split_tree);
+
+ new_ropes.push_fragment(&fragment, fragment_was_visible);
new_fragments.push(fragment, &());
// Scan forward until we find a fragment that is not fully contained by the current splice.
- cursor.next();
+ fragments_cursor.next();
if let Some(range) = cur_range.clone() {
- while let Some(fragment) = cursor.item() {
- let fragment_summary = cursor.item_summary().unwrap();
- fragment_start = *cursor.start();
+ while let Some(fragment) = fragments_cursor.item() {
+ let fragment_summary = fragments_cursor.item_summary().unwrap();
+ let fragment_was_visible = fragment.visible;
+ fragment_start = *fragments_cursor.start();
fragment_end = fragment_start + fragment.visible_len();
if range.start < fragment_start && range.end >= fragment_end {
let mut new_fragment = fragment.clone();
@@ -1553,12 +1568,14 @@ impl Buffer {
new_fragment.deletions.insert(local_timestamp);
new_fragment.visible = false;
}
+
+ new_ropes.push_fragment(&new_fragment, fragment_was_visible);
new_fragments.push(new_fragment, &());
- cursor.next();
+ fragments_cursor.next();
if range.end == fragment_end {
end_id = Some(fragment.insertion.id);
- end_offset = Some(fragment.end_offset());
+ end_offset = Some(fragment.range_in_insertion.end);
ops.push(Operation::Edit {
edit: EditOperation {
id: local_timestamp,
@@ -1594,10 +1611,13 @@ impl Buffer {
// that the cursor is parked at, we should seek to the next splice's start range
// and push all the fragments in between into the new tree.
if cur_range.as_ref().map_or(false, |r| r.start > fragment_end) {
- new_fragments.push_tree(
- cursor.slice(&cur_range.as_ref().unwrap().start, SeekBias::Right, &()),
+ let slice = fragments_cursor.slice(
+ &cur_range.as_ref().unwrap().start,
+ SeekBias::Right,
&(),
);
+ new_ropes.push_tree(slice.summary().text);
+ new_fragments.push_tree(slice, &());
}
}
}
@@ -1611,10 +1631,11 @@ impl Buffer {
edit: EditOperation {
id: local_timestamp,
start_id: last_fragment.insertion.id,
- start_offset: last_fragment.end_offset(),
+ start_offset: last_fragment.range_in_insertion.end,
end_id: last_fragment.insertion.id,
- end_offset: last_fragment.end_offset(),
+ end_offset: last_fragment.range_in_insertion.end,
version_in_range: time::Global::new(),
+ // TODO: avoid cloning the String.
new_text: new_text.clone(),
},
lamport_timestamp,
@@ -1624,20 +1645,22 @@ impl Buffer {
let new_fragment = self.build_fragment_to_insert(
&last_fragment,
None,
- new_text,
+ &new_text,
local_timestamp,
lamport_timestamp,
);
+
+ new_ropes.push_str(&new_text);
new_fragments.push(new_fragment, &());
}
- } else {
- new_fragments.push_tree(
- cursor.slice(&old_fragments.extent::<usize>(), SeekBias::Right, &()),
- &(),
- );
}
+ new_fragments.push_tree(fragments_cursor.suffix(&()), &());
+ let (visible_text, deleted_text) = new_ropes.finish();
+
self.fragments = new_fragments;
+ self.visible_text = visible_text;
+ self.deleted_text = deleted_text;
ops
}
@@ -1647,24 +1670,26 @@ impl Buffer {
fragment: &Fragment,
range: Range<usize>,
) -> (Option<Fragment>, Option<Fragment>, Option<Fragment>) {
- debug_assert!(range.start >= fragment.start_offset());
- debug_assert!(range.start <= fragment.end_offset());
- debug_assert!(range.end <= fragment.end_offset());
- debug_assert!(range.end >= fragment.start_offset());
+ debug_assert!(range.start >= fragment.range_in_insertion.start);
+ debug_assert!(range.start <= fragment.range_in_insertion.end);
+ debug_assert!(range.end <= fragment.range_in_insertion.end);
+ debug_assert!(range.end >= fragment.range_in_insertion.start);
- if range.end == fragment.start_offset() {
+ if range.end == fragment.range_in_insertion.start {
(None, None, Some(fragment.clone()))
- } else if range.start == fragment.end_offset() {
+ } else if range.start == fragment.range_in_insertion.end {
(Some(fragment.clone()), None, None)
- } else if range.start == fragment.start_offset() && range.end == fragment.end_offset() {
+ } else if range.start == fragment.range_in_insertion.start
+ && range.end == fragment.range_in_insertion.end
+ {
(None, Some(fragment.clone()), None)
} else {
let mut prefix = fragment.clone();
- let after_range = if range.end < fragment.end_offset() {
+ let after_range = if range.end < fragment.range_in_insertion.end {
let mut suffix = prefix.clone();
- suffix.set_start_offset(range.end);
- prefix.set_end_offset(range.end);
+ suffix.range_in_insertion.start = range.end;
+ prefix.range_in_insertion.end = range.end;
prefix.id = FragmentId::between(&prev_fragment.id, &suffix.id);
Some(suffix)
} else {
@@ -1673,15 +1698,15 @@ impl Buffer {
let within_range = if range.start != range.end {
let mut suffix = prefix.clone();
- suffix.set_start_offset(range.start);
- prefix.set_end_offset(range.start);
+ suffix.range_in_insertion.start = range.start;
+ prefix.range_in_insertion.end = range.start;
prefix.id = FragmentId::between(&prev_fragment.id, &suffix.id);
Some(suffix)
} else {
None
};
- let before_range = if range.start > fragment.start_offset() {
+ let before_range = if range.start > fragment.range_in_insertion.start {
Some(prefix)
} else {
None
@@ -1692,12 +1717,13 @@ impl Buffer {
.remove(&fragment.insertion.id)
.unwrap();
let mut cursor = old_split_tree.cursor::<usize, ()>();
- let mut new_split_tree = cursor.slice(&fragment.start_offset(), SeekBias::Right, &());
+ let mut new_split_tree =
+ cursor.slice(&fragment.range_in_insertion.start, SeekBias::Right, &());
if let Some(ref fragment) = before_range {
new_split_tree.push(
InsertionSplit {
- extent: range.start - fragment.start_offset(),
+ extent: range.start - fragment.range_in_insertion.start,
fragment_id: fragment.id.clone(),
},
&(),
@@ -1717,7 +1743,7 @@ impl Buffer {
if let Some(ref fragment) = after_range {
new_split_tree.push(
InsertionSplit {
- extent: fragment.end_offset() - range.end,
+ extent: fragment.range_in_insertion.end - range.end,
fragment_id: fragment.id.clone(),
},
&(),
@@ -1741,8 +1767,8 @@ impl Buffer {
&mut self,
prev_fragment: &Fragment,
next_fragment: Option<&Fragment>,
- text: Text,
- local_timestamp: time::Local,
+ text: &str,
+ insertion_id: time::Local,
lamport_timestamp: time::Lamport,
) -> Fragment {
let new_fragment_id = FragmentId::between(
@@ -1752,25 +1778,27 @@ impl Buffer {
.unwrap_or(&FragmentId::max_value()),
);
+ // TODO: extent could be expressed in bytes, which would save a linear scan.
+ let range_in_insertion = 0..text.chars().count();
let mut split_tree = SumTree::new();
split_tree.push(
InsertionSplit {
- extent: text.len(),
+ extent: range_in_insertion.len(),
fragment_id: new_fragment_id.clone(),
},
&(),
);
- self.insertion_splits.insert(local_timestamp, split_tree);
+ self.insertion_splits.insert(insertion_id, split_tree);
Fragment::new(
new_fragment_id,
- Insertion {
- id: local_timestamp,
+ Arc::new(Insertion {
+ id: insertion_id,
parent_id: prev_fragment.insertion.id,
- offset_in_parent: prev_fragment.end_offset(),
- text,
+ offset_in_parent: prev_fragment.range_in_insertion.end,
lamport_timestamp,
- },
+ }),
+ range_in_insertion,
)
}
@@ -0,0 +1,477 @@
+use super::Point;
+use crate::sum_tree::{self, SeekBias, SumTree};
+use anyhow::{anyhow, Result};
+use arrayvec::ArrayString;
+use smallvec::SmallVec;
+use std::{cmp, ops::Range, str};
+
+#[cfg(test)]
+const CHUNK_BASE: usize = 2;
+
+#[cfg(not(test))]
+const CHUNK_BASE: usize = 16;
+
+#[derive(Clone, Default, Debug)]
+pub struct Rope {
+ chunks: SumTree<Chunk>,
+}
+
+impl Rope {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ pub fn append(&mut self, rope: Rope) {
+ let mut chunks = rope.chunks.cursor::<(), ()>();
+ chunks.next();
+ if let Some(chunk) = chunks.item() {
+ self.push(&chunk.0);
+ chunks.next();
+ }
+
+ self.chunks.push_tree(chunks.suffix(&()), &());
+ self.check_invariants();
+ }
+
+ pub fn push(&mut self, text: &str) {
+ let mut new_chunks = SmallVec::<[_; 16]>::new();
+ let mut new_chunk = ArrayString::new();
+ for ch in text.chars() {
+ if new_chunk.len() + ch.len_utf8() > 2 * CHUNK_BASE {
+ new_chunks.push(Chunk(new_chunk));
+ new_chunk = ArrayString::new();
+ }
+ new_chunk.push(ch);
+ }
+ if !new_chunk.is_empty() {
+ new_chunks.push(Chunk(new_chunk));
+ }
+
+ let mut new_chunks = new_chunks.into_iter();
+ let mut first_new_chunk = new_chunks.next();
+ self.chunks.update_last(
+ |last_chunk| {
+ if let Some(first_new_chunk_ref) = first_new_chunk.as_mut() {
+ if last_chunk.0.len() + first_new_chunk_ref.0.len() <= 2 * CHUNK_BASE {
+ last_chunk.0.push_str(&first_new_chunk.take().unwrap().0);
+ } else {
+ let mut text = ArrayString::<[_; 4 * CHUNK_BASE]>::new();
+ text.push_str(&last_chunk.0);
+ text.push_str(&first_new_chunk_ref.0);
+
+ let mut midpoint = text.len() / 2;
+ while !text.is_char_boundary(midpoint) {
+ midpoint += 1;
+ }
+ let (left, right) = text.split_at(midpoint);
+ last_chunk.0.clear();
+ last_chunk.0.push_str(left);
+ first_new_chunk_ref.0.clear();
+ first_new_chunk_ref.0.push_str(right);
+ }
+ }
+ },
+ &(),
+ );
+
+ self.chunks
+ .extend(first_new_chunk.into_iter().chain(new_chunks), &());
+ self.check_invariants();
+ }
+
+ fn check_invariants(&self) {
+ #[cfg(test)]
+ {
+ // Ensure all chunks except maybe the last one are not underflowing.
+ let mut chunks = self.chunks.cursor::<(), ()>().peekable();
+ while let Some(chunk) = chunks.next() {
+ if chunks.peek().is_some() {
+ assert!(chunk.0.len() >= CHUNK_BASE);
+ }
+ }
+ }
+ }
+
+ pub fn slice(&self, range: Range<usize>) -> Rope {
+ self.cursor(range.start).slice(range.end)
+ }
+
+ pub fn summary(&self) -> TextSummary {
+ self.chunks.summary()
+ }
+
+ pub fn len(&self) -> usize {
+ self.chunks.extent()
+ }
+
+ pub fn max_point(&self) -> Point {
+ self.chunks.extent()
+ }
+
+ pub fn cursor(&self, offset: usize) -> Cursor {
+ Cursor::new(self, offset)
+ }
+
+ pub fn chars(&self) -> Chars {
+ self.chars_at(0)
+ }
+
+ pub fn chars_at(&self, start: usize) -> Chars {
+ Chars::new(self, start)
+ }
+
+ pub fn chunks<'a>(&'a self) -> impl Iterator<Item = &'a str> {
+ self.chunks.cursor::<(), ()>().map(|c| c.0.as_str())
+ }
+
+ pub fn to_point(&self, offset: usize) -> Result<Point> {
+ if offset <= self.summary().chars {
+ let mut cursor = self.chunks.cursor::<usize, TextSummary>();
+ cursor.seek(&offset, SeekBias::Left, &());
+ let overshoot = offset - cursor.start().chars;
+ Ok(cursor.start().lines
+ + cursor
+ .item()
+ .map_or(Point::zero(), |chunk| chunk.to_point(overshoot)))
+ } else {
+ Err(anyhow!("offset out of bounds"))
+ }
+ }
+
+ pub fn to_offset(&self, point: Point) -> Result<usize> {
+ // TODO: Verify the point actually exists.
+ if point <= self.summary().lines {
+ let mut cursor = self.chunks.cursor::<Point, TextSummary>();
+ cursor.seek(&point, SeekBias::Left, &());
+ let overshoot = point - cursor.start().lines;
+ Ok(cursor.start().chars + cursor.item().map_or(0, |chunk| chunk.to_offset(overshoot)))
+ } else {
+ Err(anyhow!("offset out of bounds"))
+ }
+ }
+}
+
+impl<'a> From<&'a str> for Rope {
+ fn from(text: &'a str) -> Self {
+ let mut rope = Self::new();
+ rope.push(text);
+ rope
+ }
+}
+
+pub struct Cursor<'a> {
+ rope: &'a Rope,
+ chunks: sum_tree::Cursor<'a, Chunk, usize, usize>,
+ offset: usize,
+}
+
+impl<'a> Cursor<'a> {
+ pub fn new(rope: &'a Rope, offset: usize) -> Self {
+ let mut chunks = rope.chunks.cursor();
+ chunks.seek(&offset, SeekBias::Right, &());
+ Self {
+ rope,
+ chunks,
+ offset,
+ }
+ }
+
+ pub fn seek_forward(&mut self, end_offset: usize) {
+ debug_assert!(end_offset >= self.offset);
+
+ self.chunks.seek_forward(&end_offset, SeekBias::Right, &());
+ self.offset = end_offset;
+ }
+
+ pub fn slice(&mut self, end_offset: usize) -> Rope {
+ debug_assert!(end_offset >= self.offset);
+
+ let mut slice = Rope::new();
+ if let Some(start_chunk) = self.chunks.item() {
+ let start_ix = self.offset - self.chunks.start();
+ let end_ix = cmp::min(end_offset, self.chunks.end()) - self.chunks.start();
+ slice.push(&start_chunk.0[start_ix..end_ix]);
+ }
+
+ if end_offset > self.chunks.end() {
+ self.chunks.next();
+ slice.append(Rope {
+ chunks: self.chunks.slice(&end_offset, SeekBias::Right, &()),
+ });
+ if let Some(end_chunk) = self.chunks.item() {
+ slice.push(&end_chunk.0[..end_offset - self.chunks.start()]);
+ }
+ }
+
+ self.offset = end_offset;
+ slice
+ }
+
+ pub fn suffix(mut self) -> Rope {
+ self.slice(self.rope.chunks.extent())
+ }
+
+ pub fn offset(&self) -> usize {
+ self.offset
+ }
+}
+
+#[derive(Clone, Debug, Default)]
+struct Chunk(ArrayString<[u8; 2 * CHUNK_BASE]>);
+
+impl Chunk {
+ fn to_point(&self, target: usize) -> Point {
+ let mut offset = 0;
+ let mut point = Point::new(0, 0);
+ for ch in self.0.chars() {
+ if offset >= target {
+ break;
+ }
+
+ if ch == '\n' {
+ point.row += 1;
+ point.column = 0;
+ } else {
+ point.column += 1;
+ }
+ offset += 1;
+ }
+ point
+ }
+
+ fn to_offset(&self, target: Point) -> usize {
+ let mut offset = 0;
+ let mut point = Point::new(0, 0);
+ for ch in self.0.chars() {
+ if point >= target {
+ break;
+ }
+
+ if ch == '\n' {
+ point.row += 1;
+ point.column = 0;
+ } else {
+ point.column += 1;
+ }
+ offset += 1;
+ }
+ offset
+ }
+}
+
+impl sum_tree::Item for Chunk {
+ type Summary = TextSummary;
+
+ fn summary(&self) -> Self::Summary {
+ let mut chars = 0;
+ let mut bytes = 0;
+ let mut lines = Point::new(0, 0);
+ let mut first_line_len = 0;
+ let mut rightmost_point = Point::new(0, 0);
+ for c in self.0.chars() {
+ chars += 1;
+ bytes += c.len_utf8();
+ if c == '\n' {
+ lines.row += 1;
+ lines.column = 0;
+ } else {
+ lines.column += 1;
+ if lines.row == 0 {
+ first_line_len = lines.column;
+ }
+ if lines.column > rightmost_point.column {
+ rightmost_point = lines;
+ }
+ }
+ }
+
+ TextSummary {
+ chars,
+ bytes,
+ lines,
+ first_line_len,
+ rightmost_point,
+ }
+ }
+}
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct TextSummary {
+ pub chars: usize,
+ pub bytes: usize,
+ pub lines: Point,
+ pub first_line_len: u32,
+ pub rightmost_point: Point,
+}
+
+impl sum_tree::Summary for TextSummary {
+ type Context = ();
+
+ fn add_summary(&mut self, summary: &Self, _: &Self::Context) {
+ *self += summary;
+ }
+}
+
+impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
+ fn add_assign(&mut self, other: &'a Self) {
+ let joined_line_len = self.lines.column + other.first_line_len;
+ if joined_line_len > self.rightmost_point.column {
+ self.rightmost_point = Point::new(self.lines.row, joined_line_len);
+ }
+ if other.rightmost_point.column > self.rightmost_point.column {
+ self.rightmost_point = self.lines + &other.rightmost_point;
+ }
+
+ if self.lines.row == 0 {
+ self.first_line_len += other.first_line_len;
+ }
+
+ self.chars += other.chars;
+ self.bytes += other.bytes;
+ self.lines += &other.lines;
+ }
+}
+
+impl std::ops::AddAssign<Self> for TextSummary {
+ fn add_assign(&mut self, other: Self) {
+ *self += &other;
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary {
+ fn add_summary(&mut self, summary: &'a TextSummary) {
+ *self += summary;
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
+ fn add_summary(&mut self, summary: &'a TextSummary) {
+ *self += summary.chars;
+ }
+}
+
+impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
+ fn add_summary(&mut self, summary: &'a TextSummary) {
+ *self += &summary.lines;
+ }
+}
+
+pub struct Chars<'a> {
+ cursor: sum_tree::Cursor<'a, Chunk, usize, usize>,
+ chars: str::Chars<'a>,
+}
+
+impl<'a> Chars<'a> {
+ pub fn new(rope: &'a Rope, start: usize) -> Self {
+ let mut cursor = rope.chunks.cursor::<usize, usize>();
+ cursor.slice(&start, SeekBias::Left, &());
+ let chars = if let Some(chunk) = cursor.item() {
+ let ix = start - cursor.start();
+ cursor.next();
+ chunk.0[ix..].chars()
+ } else {
+ "".chars()
+ };
+
+ Self { cursor, chars }
+ }
+}
+
+impl<'a> Iterator for Chars<'a> {
+ type Item = char;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Some(ch) = self.chars.next() {
+ Some(ch)
+ } else if let Some(chunk) = self.cursor.item() {
+ self.chars = chunk.0.chars();
+ self.cursor.next();
+ Some(self.chars.next().unwrap())
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::util::RandomCharIter;
+
+ use super::*;
+ use rand::prelude::*;
+ use std::env;
+
+ #[test]
+ fn test_random() {
+ let iterations = env::var("ITERATIONS")
+ .map(|i| i.parse().expect("invalid `ITERATIONS` variable"))
+ .unwrap_or(100);
+ let operations = env::var("OPERATIONS")
+ .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
+ .unwrap_or(10);
+ let seed_range = if let Ok(seed) = env::var("SEED") {
+ let seed = seed.parse().expect("invalid `SEED` variable");
+ seed..seed + 1
+ } else {
+ 0..iterations
+ };
+
+ for seed in seed_range {
+ dbg!(seed);
+ let mut rng = StdRng::seed_from_u64(seed);
+ let mut expected = String::new();
+ let mut actual = Rope::new();
+ for _ in 0..operations {
+ let end_ix = rng.gen_range(0..=expected.len());
+ let start_ix = rng.gen_range(0..=end_ix);
+ let len = rng.gen_range(0..=20);
+ let new_text: String = RandomCharIter::new(&mut rng).take(len).collect();
+
+ let mut new_actual = Rope::new();
+ let mut cursor = actual.cursor(0);
+ new_actual.append(cursor.slice(start_ix));
+ new_actual.push(&new_text);
+ cursor.seek_forward(end_ix);
+ new_actual.append(cursor.suffix());
+ actual = new_actual;
+
+ let mut new_expected = String::new();
+ new_expected.push_str(&expected[..start_ix]);
+ new_expected.push_str(&new_text);
+ new_expected.push_str(&expected[end_ix..]);
+ expected = new_expected;
+
+ assert_eq!(actual.text(), expected);
+
+ for _ in 0..5 {
+ let ix = rng.gen_range(0..=expected.len());
+ assert_eq!(actual.chars_at(ix).collect::<String>(), expected[ix..]);
+ }
+
+ let mut point = Point::new(0, 0);
+ let mut offset = 0;
+ for ch in expected.chars() {
+ assert_eq!(actual.to_point(offset).unwrap(), point);
+ assert_eq!(actual.to_offset(point).unwrap(), offset);
+ if ch == '\n' {
+ point.row += 1;
+ point.column = 0
+ } else {
+ point.column += 1;
+ }
+ offset += 1;
+ }
+ }
+ }
+ }
+
+ impl Rope {
+ fn text(&self) -> String {
+ let mut text = String::new();
+ for chunk in self.chunks.cursor::<(), ()>() {
+ text.push_str(&chunk.0);
+ }
+ text
+ }
+ }
+}
@@ -1,461 +0,0 @@
-use super::Point;
-use crate::sum_tree::{self, SeekBias, SumTree};
-use arrayvec::ArrayVec;
-use std::{
- cmp,
- fmt::{self, Debug},
- ops::{Bound, Index, Range, RangeBounds},
- sync::Arc,
-};
-
-#[derive(Copy, Clone, Debug, Eq, PartialEq)]
-enum Run {
- Newline,
- Chars { len: usize, char_size: u8 },
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
-struct ByteOffset(usize);
-
-impl sum_tree::Item for Run {
- type Summary = TextSummary;
-
- fn summary(&self) -> Self::Summary {
- match *self {
- Run::Newline => TextSummary {
- chars: 1,
- bytes: 1,
- lines: Point::new(1, 0),
- first_line_len: 0,
- rightmost_point: Point::new(0, 0),
- },
- Run::Chars { len, char_size } => TextSummary {
- chars: len,
- bytes: len * char_size as usize,
- lines: Point::new(0, len as u32),
- first_line_len: len as u32,
- rightmost_point: Point::new(0, len as u32),
- },
- }
- }
-}
-
-impl Run {
- fn char_size(&self) -> u8 {
- match self {
- Run::Newline => 1,
- Run::Chars { char_size, .. } => *char_size,
- }
- }
-}
-
-#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct TextSummary {
- pub chars: usize,
- pub bytes: usize,
- pub lines: Point,
- pub first_line_len: u32,
- pub rightmost_point: Point,
-}
-
-impl sum_tree::Summary for TextSummary {
- type Context = ();
-
- fn add_summary(&mut self, other: &Self, _: &()) {
- *self += other;
- }
-}
-
-impl<'a> std::ops::AddAssign<&'a Self> for TextSummary {
- fn add_assign(&mut self, other: &'a Self) {
- let joined_line_len = self.lines.column + other.first_line_len;
- if joined_line_len > self.rightmost_point.column {
- self.rightmost_point = Point::new(self.lines.row, joined_line_len);
- }
- if other.rightmost_point.column > self.rightmost_point.column {
- self.rightmost_point = self.lines + &other.rightmost_point;
- }
-
- if self.lines.row == 0 {
- self.first_line_len += other.first_line_len;
- }
-
- self.chars += other.chars;
- self.bytes += other.bytes;
- self.lines += &other.lines;
- }
-}
-
-impl std::ops::AddAssign<Self> for TextSummary {
- fn add_assign(&mut self, other: Self) {
- *self += &other;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for TextSummary {
- fn add_summary(&mut self, other: &TextSummary) {
- *self += other;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for Point {
- fn add_summary(&mut self, summary: &TextSummary) {
- *self += &summary.lines;
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for ByteOffset {
- fn add_summary(&mut self, summary: &TextSummary) {
- self.0 += summary.bytes
- }
-}
-
-impl<'a> sum_tree::Dimension<'a, TextSummary> for usize {
- fn add_summary(&mut self, summary: &TextSummary) {
- *self += summary.chars;
- }
-}
-
-#[derive(Clone)]
-pub struct Text {
- text: Arc<str>,
- runs: SumTree<Run>,
- range: Range<usize>,
-}
-
-impl From<String> for Text {
- fn from(text: String) -> Self {
- Self::from(Arc::from(text))
- }
-}
-
-impl<'a> From<&'a str> for Text {
- fn from(text: &'a str) -> Self {
- Self::from(Arc::from(text))
- }
-}
-
-impl From<Arc<str>> for Text {
- fn from(text: Arc<str>) -> Self {
- let mut runs = Vec::new();
-
- let mut chars_len = 0;
- let mut run_char_size = 0;
- let mut run_chars = 0;
-
- let mut chars = text.chars();
- loop {
- let ch = chars.next();
- let ch_size = ch.map_or(0, |ch| ch.len_utf8());
- if run_chars != 0 && (ch.is_none() || ch == Some('\n') || run_char_size != ch_size) {
- runs.push(Run::Chars {
- len: run_chars,
- char_size: run_char_size as u8,
- });
- run_chars = 0;
- }
- run_char_size = ch_size;
-
- match ch {
- Some('\n') => runs.push(Run::Newline),
- Some(_) => run_chars += 1,
- None => break,
- }
- chars_len += 1;
- }
-
- let mut tree = SumTree::new();
- tree.extend(runs, &());
- Text {
- text,
- runs: tree,
- range: 0..chars_len,
- }
- }
-}
-
-impl Debug for Text {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- f.debug_tuple("Text").field(&self.as_str()).finish()
- }
-}
-
-impl PartialEq for Text {
- fn eq(&self, other: &Self) -> bool {
- self.text == other.text
- }
-}
-
-impl Eq for Text {}
-
-impl<T: RangeBounds<usize>> Index<T> for Text {
- type Output = str;
-
- fn index(&self, range: T) -> &Self::Output {
- let start = match range.start_bound() {
- Bound::Included(start) => cmp::min(self.range.start + start, self.range.end),
- Bound::Excluded(_) => unimplemented!(),
- Bound::Unbounded => self.range.start,
- };
- let end = match range.end_bound() {
- Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end),
- Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end),
- Bound::Unbounded => self.range.end,
- };
-
- let byte_start = self.abs_byte_offset_for_offset(start);
- let byte_end = self.abs_byte_offset_for_offset(end);
- &self.text[byte_start..byte_end]
- }
-}
-
-impl Text {
- pub fn range(&self) -> Range<usize> {
- self.range.clone()
- }
-
- pub fn as_str(&self) -> &str {
- &self[..]
- }
-
- pub fn slice<T: RangeBounds<usize>>(&self, range: T) -> Text {
- let start = match range.start_bound() {
- Bound::Included(start) => cmp::min(self.range.start + start, self.range.end),
- Bound::Excluded(_) => unimplemented!(),
- Bound::Unbounded => self.range.start,
- };
- let end = match range.end_bound() {
- Bound::Included(end) => cmp::min(self.range.start + end + 1, self.range.end),
- Bound::Excluded(end) => cmp::min(self.range.start + end, self.range.end),
- Bound::Unbounded => self.range.end,
- };
-
- Text {
- text: self.text.clone(),
- runs: self.runs.clone(),
- range: start..end,
- }
- }
-
- pub fn line_len(&self, row: u32) -> u32 {
- let mut cursor = self.runs.cursor::<usize, Point>();
- cursor.seek(&self.range.start, SeekBias::Right, &());
- let absolute_row = cursor.start().row + row;
-
- let mut cursor = self.runs.cursor::<Point, usize>();
- cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right, &());
- let prefix_len = self.range.start.saturating_sub(*cursor.start());
- let line_len =
- cursor.summary::<usize>(&Point::new(absolute_row + 1, 0), SeekBias::Left, &());
- let suffix_len = cursor.start().saturating_sub(self.range.end);
-
- line_len
- .saturating_sub(prefix_len)
- .saturating_sub(suffix_len) as u32
- }
-
- pub fn len(&self) -> usize {
- self.range.end - self.range.start
- }
-
- pub fn lines(&self) -> Point {
- self.abs_point_for_offset(self.range.end) - &self.abs_point_for_offset(self.range.start)
- }
-
- pub fn rightmost_point(&self) -> Point {
- let lines = self.lines();
-
- let mut candidates = ArrayVec::<[Point; 3]>::new();
- candidates.push(lines);
- if lines.row > 0 {
- candidates.push(Point::new(0, self.line_len(0)));
- if lines.row > 1 {
- let mut cursor = self.runs.cursor::<usize, Point>();
- cursor.seek(&self.range.start, SeekBias::Right, &());
- let absolute_start_row = cursor.start().row;
-
- let mut cursor = self.runs.cursor::<Point, usize>();
- cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right, &());
- let summary = cursor.summary::<TextSummary>(
- &Point::new(absolute_start_row + lines.row, 0),
- SeekBias::Left,
- &(),
- );
-
- candidates.push(Point::new(1, 0) + &summary.rightmost_point);
- }
- }
-
- candidates.into_iter().max_by_key(|p| p.column).unwrap()
- }
-
- pub fn point_for_offset(&self, offset: usize) -> Point {
- self.abs_point_for_offset(self.range.start + offset)
- - &self.abs_point_for_offset(self.range.start)
- }
-
- pub fn offset_for_point(&self, point: Point) -> usize {
- let mut cursor = self.runs.cursor::<Point, TextSummary>();
- let abs_point = self.abs_point_for_offset(self.range.start) + &point;
- cursor.seek(&abs_point, SeekBias::Right, &());
- let overshoot = abs_point - &cursor.start().lines;
- let abs_offset = cursor.start().chars + overshoot.column as usize;
- abs_offset - self.range.start
- }
-
- pub fn summary(&self) -> TextSummary {
- TextSummary {
- chars: self.range.end - self.range.start,
- bytes: self.abs_byte_offset_for_offset(self.range.end)
- - self.abs_byte_offset_for_offset(self.range.start),
- lines: self.abs_point_for_offset(self.range.end)
- - &self.abs_point_for_offset(self.range.start),
- first_line_len: self.line_len(0),
- rightmost_point: self.rightmost_point(),
- }
- }
-
- fn abs_point_for_offset(&self, offset: usize) -> Point {
- let mut cursor = self.runs.cursor::<usize, TextSummary>();
- cursor.seek(&offset, SeekBias::Right, &());
- let overshoot = (offset - cursor.start().chars) as u32;
- cursor.start().lines + &Point::new(0, overshoot)
- }
-
- fn abs_byte_offset_for_offset(&self, offset: usize) -> usize {
- let mut cursor = self.runs.cursor::<usize, TextSummary>();
- cursor.seek(&offset, SeekBias::Right, &());
- let overshoot = offset - cursor.start().chars;
- cursor.start().bytes + overshoot * cursor.item().map_or(0, |run| run.char_size()) as usize
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::collections::HashSet;
- use std::iter::FromIterator;
-
- #[test]
- fn test_basic() {
- let text = Text::from(String::from("ab\ncd€\nfghij\nkl¢m"));
- assert_eq!(text.len(), 17);
- assert_eq!(text.as_str(), "ab\ncd€\nfghij\nkl¢m");
- assert_eq!(text.lines(), Point::new(3, 4));
- assert_eq!(text.line_len(0), 2);
- assert_eq!(text.line_len(1), 3);
- assert_eq!(text.line_len(2), 5);
- assert_eq!(text.line_len(3), 4);
- assert_eq!(text.rightmost_point(), Point::new(2, 5));
-
- let b_to_g = text.slice(1..9);
- assert_eq!(b_to_g.as_str(), "b\ncd€\nfg");
- assert_eq!(b_to_g.len(), 8);
- assert_eq!(b_to_g.lines(), Point::new(2, 2));
- assert_eq!(b_to_g.line_len(0), 1);
- assert_eq!(b_to_g.line_len(1), 3);
- assert_eq!(b_to_g.line_len(2), 2);
- assert_eq!(b_to_g.line_len(3), 0);
- assert_eq!(b_to_g.rightmost_point(), Point::new(1, 3));
-
- let d_to_i = text.slice(4..11);
- assert_eq!(d_to_i.as_str(), "d€\nfghi");
- assert_eq!(&d_to_i[1..5], "€\nfg");
- assert_eq!(d_to_i.len(), 7);
- assert_eq!(d_to_i.lines(), Point::new(1, 4));
- assert_eq!(d_to_i.line_len(0), 2);
- assert_eq!(d_to_i.line_len(1), 4);
- assert_eq!(d_to_i.line_len(2), 0);
- assert_eq!(d_to_i.rightmost_point(), Point::new(1, 4));
-
- let d_to_j = text.slice(4..=11);
- assert_eq!(d_to_j.as_str(), "d€\nfghij");
- assert_eq!(&d_to_j[1..], "€\nfghij");
- assert_eq!(d_to_j.len(), 8);
- }
-
- #[test]
- fn test_random() {
- use rand::prelude::*;
-
- for seed in 0..100 {
- println!("buffer::text seed: {}", seed);
- let rng = &mut StdRng::seed_from_u64(seed);
-
- let len = rng.gen_range(0..50);
- let mut string = String::new();
- for _ in 0..len {
- if rng.gen_ratio(1, 5) {
- string.push('\n');
- } else {
- string.push(rng.gen());
- }
- }
- let text = Text::from(string.clone());
-
- for _ in 0..10 {
- let start = rng.gen_range(0..text.len() + 1);
- let end = rng.gen_range(start..text.len() + 2);
-
- let string_slice = string
- .chars()
- .skip(start)
- .take(end - start)
- .collect::<String>();
- let expected_line_endpoints = string_slice
- .split('\n')
- .enumerate()
- .map(|(row, line)| Point::new(row as u32, line.chars().count() as u32))
- .collect::<Vec<_>>();
- let text_slice = text.slice(start..end);
-
- assert_eq!(text_slice.lines(), lines(&string_slice));
-
- let mut rightmost_points: HashSet<Point> = HashSet::new();
- for endpoint in &expected_line_endpoints {
- if let Some(rightmost_point) = rightmost_points.iter().next().cloned() {
- if endpoint.column > rightmost_point.column {
- rightmost_points.clear();
- }
- if endpoint.column >= rightmost_point.column {
- rightmost_points.insert(*endpoint);
- }
- } else {
- rightmost_points.insert(*endpoint);
- }
-
- assert_eq!(text_slice.line_len(endpoint.row as u32), endpoint.column);
- }
-
- assert!(rightmost_points.contains(&text_slice.rightmost_point()));
-
- for _ in 0..10 {
- let offset = rng.gen_range(0..string_slice.chars().count() + 1);
- let point = lines(&string_slice.chars().take(offset).collect::<String>());
- assert_eq!(text_slice.point_for_offset(offset), point);
- assert_eq!(text_slice.offset_for_point(point), offset);
- if offset < string_slice.chars().count() {
- assert_eq!(
- &text_slice[offset..offset + 1],
- String::from_iter(string_slice.chars().nth(offset)).as_str()
- );
- }
- }
- }
- }
- }
-
- pub fn lines(s: &str) -> Point {
- let mut row = 0;
- let mut column = 0;
- for ch in s.chars() {
- if ch == '\n' {
- row += 1;
- column = 0;
- } else {
- column += 1;
- }
- }
- Point::new(row, column)
- }
-}