diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 1bdf050f0539dba4553782d41f82e07abfe6fde2..78c7c2f2af4cbe6ac77a736a0e18282803f1e67c 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -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 = std::collections::HashSet; pub struct Buffer { fragments: SumTree, + visible_text: Rope, + deleted_text: Rope, insertion_splits: HashMap>, pub version: time::Global, saved_version: time::Global, @@ -75,10 +78,6 @@ pub struct Buffer { lamport_clock: time::Lamport, } -pub struct Snapshot { - fragments: SumTree, -} - #[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, ops: HashMap, undo_stack: Vec, @@ -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, + range_in_insertion: Range, deletions: HashSet, 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, + new_text: Option, } #[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::() + self.visible_text.summary() } pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - let mut summary = TextSummary::default(); - - let mut cursor = self.fragments.cursor::(); - 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::(&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) -> Point { - let mut summary = TextSummary::default(); - - let mut cursor = self.fragments.cursor::(); - 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::(&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 { @@ -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(&self, position: T) -> Result { + pub fn chars_at(&self, position: T) -> Result { 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>, S: ToOffset, - T: Into, + T: Into, { 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::>>>()?; + 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, + 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::().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::(); - let mut new_fragments = - cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left, &()); + let mut fragments_cursor = old_fragments.cursor::(); - 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::(); + + let mut fragments_cursor = self.fragments.cursor::(); 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(&mut self, mut old_ranges: I, new_text: Option) -> Vec + fn splice_fragments(&mut self, mut old_ranges: I, new_text: Option) -> Vec where I: Iterator>, { @@ -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::(); - 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::(); + 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::(); 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::(), 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, ) -> (Option, Option, Option) { - 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::(); - 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, ) } @@ -1811,7 +1839,7 @@ impl Buffer { cursor.seek(&offset, seek_bias, &()); let fragment = cursor.item().unwrap(); let offset_in_fragment = offset - cursor.start(); - let offset_in_insertion = fragment.start_offset() + offset_in_fragment; + let offset_in_insertion = fragment.range_in_insertion.start + offset_in_fragment; let anchor = Anchor::Middle { insertion_id: fragment.insertion.id, offset: offset_in_insertion, @@ -1852,7 +1880,7 @@ impl Buffer { fn summary_for_anchor(&self, anchor: &Anchor) -> Result { match anchor { Anchor::Start => Ok(TextSummary::default()), - Anchor::End => Ok(self.fragments.summary().text_summary), + Anchor::End => Ok(self.text_summary()), Anchor::Middle { insertion_id, offset, @@ -1873,37 +1901,29 @@ impl Buffer { .item() .ok_or_else(|| anyhow!("split offset is out of range"))?; - let mut fragments_cursor = self.fragments.cursor::(); + let mut fragments_cursor = self + .fragments + .cursor::(); fragments_cursor.seek(&FragmentIdRef::new(&split.fragment_id), SeekBias::Left, &()); let fragment = fragments_cursor .item() .ok_or_else(|| anyhow!("fragment id does not exist"))?; - let mut summary = fragments_cursor.start().clone(); + let mut ix = fragments_cursor.start().clone().visible; if fragment.visible { - summary += fragment - .text - .slice(..offset - fragment.start_offset()) - .summary(); + ix += offset - fragment.range_in_insertion.start; } - Ok(summary) + Ok(self.text_summary_for_range(0..ix)) } } } - #[allow(dead_code)] pub fn point_for_offset(&self, offset: usize) -> Result { - let mut fragments_cursor = self.fragments.cursor::(); - fragments_cursor.seek(&offset, SeekBias::Left, &()); - fragments_cursor - .item() - .ok_or_else(|| anyhow!("offset is out of range")) - .map(|fragment| { - let overshoot = fragment - .point_for_offset(offset - &fragments_cursor.start().chars) - .unwrap(); - fragments_cursor.start().lines + &overshoot - }) + if offset <= self.len() { + Ok(self.text_summary_for_range(0..offset).lines) + } else { + Err(anyhow!("offset out of bounds")) + } } } @@ -1911,6 +1931,8 @@ impl Clone for Buffer { fn clone(&self) -> Self { Self { fragments: self.fragments.clone(), + visible_text: self.visible_text.clone(), + deleted_text: self.deleted_text.clone(), insertion_splits: self.insertion_splits.clone(), version: self.version.clone(), saved_version: self.saved_version.clone(), @@ -1930,13 +1952,55 @@ impl Clone for Buffer { } } -impl Snapshot { - pub fn fragments<'a>(&'a self) -> FragmentIter<'a> { - FragmentIter::new(&self.fragments) +struct RopeBuilder<'a> { + old_visible_cursor: rope::Cursor<'a>, + old_deleted_cursor: rope::Cursor<'a>, + new_visible: Rope, + new_deleted: Rope, +} + +impl<'a> RopeBuilder<'a> { + fn new(old_visible_cursor: rope::Cursor<'a>, old_deleted_cursor: rope::Cursor<'a>) -> Self { + Self { + old_visible_cursor, + old_deleted_cursor, + new_visible: Rope::new(), + new_deleted: Rope::new(), + } } - pub fn text_summary(&self) -> TextSummary { - self.fragments.summary().text_summary + fn push_tree(&mut self, len: FragmentTextSummary) { + self.push(len.visible, true, true); + self.push(len.deleted, false, false); + } + + fn push_fragment(&mut self, fragment: &Fragment, was_visible: bool) { + self.push(fragment.len(), was_visible, fragment.visible) + } + + fn push(&mut self, len: usize, was_visible: bool, is_visible: bool) { + let text = if was_visible { + self.old_visible_cursor + .slice(self.old_visible_cursor.offset() + len) + } else { + self.old_deleted_cursor + .slice(self.old_deleted_cursor.offset() + len) + }; + if is_visible { + self.new_visible.append(text); + } else { + self.new_deleted.append(text); + } + } + + fn push_str(&mut self, text: &str) { + self.new_visible.push(text); + } + + fn finish(mut self) -> (Rope, Rope) { + self.new_visible.append(self.old_visible_cursor.suffix()); + self.new_deleted.append(self.old_deleted_cursor.suffix()); + (self.new_visible, self.new_deleted) } } @@ -1953,81 +2017,6 @@ impl Entity for Buffer { type Event = Event; } -impl<'a> sum_tree::Dimension<'a, FragmentSummary> for Point { - fn add_summary(&mut self, summary: &FragmentSummary) { - *self += &summary.text_summary.lines; - } -} - -impl<'a> CharIter<'a> { - fn new(fragments: &'a SumTree, offset: usize) -> Self { - let mut fragments_cursor = fragments.cursor::(); - fragments_cursor.seek(&offset, SeekBias::Right, &()); - let fragment_chars = fragments_cursor.item().map_or("".chars(), |fragment| { - let offset_in_fragment = offset - fragments_cursor.start(); - fragment.text[offset_in_fragment..].chars() - }); - Self { - fragments_cursor, - fragment_chars, - } - } -} - -impl<'a> Iterator for CharIter<'a> { - type Item = char; - - fn next(&mut self) -> Option { - if let Some(char) = self.fragment_chars.next() { - Some(char) - } else { - loop { - self.fragments_cursor.next(); - if let Some(fragment) = self.fragments_cursor.item() { - if fragment.visible { - self.fragment_chars = fragment.text.as_str().chars(); - return self.fragment_chars.next(); - } - } else { - return None; - } - } - } - } -} - -impl<'a> FragmentIter<'a> { - fn new(fragments: &'a SumTree) -> Self { - let mut cursor = fragments.cursor::(); - cursor.seek(&0, SeekBias::Right, &()); - Self { - cursor, - started: false, - } - } -} - -impl<'a> Iterator for FragmentIter<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - loop { - if self.started { - self.cursor.next(); - } else { - self.started = true; - } - if let Some(fragment) = self.cursor.item() { - if fragment.visible { - return Some(fragment.text.as_str()); - } - } else { - return None; - } - } - } -} - impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { type Item = Edit; @@ -2205,45 +2194,17 @@ impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentIdRef<'a> { } impl Fragment { - fn new(id: FragmentId, insertion: Insertion) -> Self { + fn new(id: FragmentId, insertion: Arc, range_in_insertion: Range) -> Self { Self { id, - text: insertion.text.clone(), insertion, + range_in_insertion, deletions: Default::default(), max_undos: Default::default(), visible: true, } } - fn start_offset(&self) -> usize { - self.text.range().start - } - - fn set_start_offset(&mut self, offset: usize) { - self.text = self.insertion.text.slice(offset..self.end_offset()); - } - - fn end_offset(&self) -> usize { - self.text.range().end - } - - fn set_end_offset(&mut self, offset: usize) { - self.text = self.insertion.text.slice(self.start_offset()..offset); - } - - fn visible_len(&self) -> usize { - if self.visible { - self.len() - } else { - 0 - } - } - - fn len(&self) -> usize { - self.text.len() - } - fn is_visible(&self, undos: &UndoMap) -> bool { !undos.is_undone(self.insertion.id) && self.deletions.iter().all(|d| undos.is_undone(*d)) } @@ -2256,12 +2217,16 @@ impl Fragment { .all(|d| !version.observed(*d) || undos.was_undone(*d, version)) } - fn point_for_offset(&self, offset: usize) -> Result { - Ok(self.text.point_for_offset(offset)) + fn len(&self) -> usize { + self.range_in_insertion.len() } - fn offset_for_point(&self, point: Point) -> Result { - Ok(self.text.offset_for_point(point)) + fn visible_len(&self) -> usize { + if self.visible { + self.range_in_insertion.len() + } else { + 0 + } } } @@ -2278,13 +2243,19 @@ impl sum_tree::Item for Fragment { if self.visible { FragmentSummary { - text_summary: self.text.summary(), + text: FragmentTextSummary { + visible: self.len(), + deleted: 0, + }, max_fragment_id: self.id.clone(), max_version, } } else { FragmentSummary { - text_summary: TextSummary::default(), + text: FragmentTextSummary { + visible: 0, + deleted: self.len(), + }, max_fragment_id: self.id.clone(), max_version, } @@ -2295,8 +2266,9 @@ impl sum_tree::Item for Fragment { impl sum_tree::Summary for FragmentSummary { type Context = (); - fn add_summary(&mut self, other: &Self, _: &()) { - self.text_summary += &other.text_summary; + fn add_summary(&mut self, other: &Self, _: &Self::Context) { + self.text.visible += &other.text.visible; + self.text.deleted += &other.text.deleted; debug_assert!(self.max_fragment_id <= other.max_fragment_id); self.max_fragment_id = other.max_fragment_id.clone(); self.max_version.observe_all(&other.max_version); @@ -2306,45 +2278,16 @@ impl sum_tree::Summary for FragmentSummary { impl Default for FragmentSummary { fn default() -> Self { FragmentSummary { - text_summary: TextSummary::default(), + text: FragmentTextSummary::default(), max_fragment_id: FragmentId::min_value().clone(), max_version: time::Global::new(), } } } -impl<'a> sum_tree::Dimension<'a, FragmentSummary> for TextSummary { - fn add_summary(&mut self, summary: &FragmentSummary) { - *self += &summary.text_summary; - } -} - -impl<'a> AddAssign<&'a FragmentExtent> for FragmentExtent { - fn add_assign(&mut self, other: &Self) { - self.chars += other.chars; - self.lines += &other.lines; - } -} - -impl Default for FragmentExtent { - fn default() -> Self { - FragmentExtent { - lines: Point::zero(), - chars: 0, - } - } -} - -impl<'a> sum_tree::Dimension<'a, FragmentSummary> for FragmentExtent { - fn add_summary(&mut self, summary: &FragmentSummary) { - self.chars += summary.text_summary.chars; - self.lines += &summary.text_summary.lines; - } -} - impl<'a> sum_tree::Dimension<'a, FragmentSummary> for usize { fn add_summary(&mut self, summary: &FragmentSummary) { - *self += summary.text_summary.chars; + *self += summary.text.visible; } } @@ -2417,17 +2360,7 @@ pub trait ToOffset { impl ToOffset for Point { fn to_offset(&self, buffer: &Buffer) -> Result { - let mut fragments_cursor = buffer.fragments.cursor::(); - fragments_cursor.seek(self, SeekBias::Left, &()); - fragments_cursor - .item() - .ok_or_else(|| anyhow!("point is out of range")) - .map(|fragment| { - let overshoot = fragment - .offset_for_point(*self - fragments_cursor.start().lines) - .unwrap(); - fragments_cursor.start().chars + overshoot - }) + buffer.visible_text.to_offset(*self) } } @@ -2461,17 +2394,7 @@ impl ToPoint for Anchor { impl ToPoint for usize { fn to_point(&self, buffer: &Buffer) -> Result { - let mut fragments_cursor = buffer.fragments.cursor::(); - fragments_cursor.seek(&self, SeekBias::Left, &()); - fragments_cursor - .item() - .ok_or_else(|| anyhow!("offset is out of range")) - .map(|fragment| { - let overshoot = fragment - .point_for_offset(*self - &fragments_cursor.start().chars) - .unwrap(); - fragments_cursor.start().lines + overshoot - }) + buffer.visible_text.to_point(*self) } } @@ -2704,26 +2627,55 @@ mod tests { fn test_text_summary_for_range(ctx: &mut gpui::MutableAppContext) { ctx.add_model(|ctx| { let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", ctx); - let text = Text::from(buffer.text()); assert_eq!( buffer.text_summary_for_range(1..3), - text.slice(1..3).summary() + TextSummary { + chars: 2, + bytes: 2, + lines: Point::new(1, 0), + first_line_len: 1, + rightmost_point: Point::new(0, 1), + } ); assert_eq!( buffer.text_summary_for_range(1..12), - text.slice(1..12).summary() + TextSummary { + chars: 11, + bytes: 11, + lines: Point::new(3, 0), + first_line_len: 1, + rightmost_point: Point::new(2, 4), + } ); assert_eq!( buffer.text_summary_for_range(0..20), - text.slice(0..20).summary() + TextSummary { + chars: 20, + bytes: 20, + lines: Point::new(4, 1), + first_line_len: 2, + rightmost_point: Point::new(3, 6), + } ); assert_eq!( buffer.text_summary_for_range(0..22), - text.slice(0..22).summary() + TextSummary { + chars: 22, + bytes: 22, + lines: Point::new(4, 3), + first_line_len: 2, + rightmost_point: Point::new(3, 6), + } ); assert_eq!( buffer.text_summary_for_range(7..22), - text.slice(7..22).summary() + TextSummary { + chars: 15, + bytes: 15, + lines: Point::new(2, 3), + first_line_len: 4, + rightmost_point: Point::new(1, 6), + } ); buffer }); @@ -2732,133 +2684,39 @@ mod tests { #[gpui::test] fn test_chars_at(ctx: &mut gpui::MutableAppContext) { ctx.add_model(|ctx| { - let mut buffer = Buffer::new(0, "", ctx); - buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap(); - buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); - buffer.edit(vec![18..18], "\npqrs", None).unwrap(); - buffer.edit(vec![18..21], "\nPQ", None).unwrap(); + let mut buffer = Buffer::new(0, "", ctx); + buffer.edit(vec![0..0], "abcd\nefgh\nij", None).unwrap(); + buffer.edit(vec![12..12], "kl\nmno", None).unwrap(); + buffer.edit(vec![18..18], "\npqrs", None).unwrap(); + buffer.edit(vec![18..21], "\nPQ", None).unwrap(); - let chars = buffer.chars_at(Point::new(0, 0)).unwrap(); - assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + let chars = buffer.chars_at(Point::new(0, 0)).unwrap(); + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); - let chars = buffer.chars_at(Point::new(1, 0)).unwrap(); - assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + let chars = buffer.chars_at(Point::new(1, 0)).unwrap(); + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); - let chars = buffer.chars_at(Point::new(2, 0)).unwrap(); - assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + let chars = buffer.chars_at(Point::new(2, 0)).unwrap(); + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); - let chars = buffer.chars_at(Point::new(3, 0)).unwrap(); - assert_eq!(chars.collect::(), "mno\nPQrs"); + let chars = buffer.chars_at(Point::new(3, 0)).unwrap(); + assert_eq!(chars.collect::(), "mno\nPQrs"); - let chars = buffer.chars_at(Point::new(4, 0)).unwrap(); - assert_eq!(chars.collect::(), "PQrs"); + let chars = buffer.chars_at(Point::new(4, 0)).unwrap(); + assert_eq!(chars.collect::(), "PQrs"); - // Regression test: - let mut buffer = Buffer::new(0, "", ctx); - buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None).unwrap(); - buffer.edit(vec![60..60], "\n", None).unwrap(); + // Regression test: + let mut buffer = Buffer::new(0, "", ctx); + buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", None).unwrap(); + buffer.edit(vec![60..60], "\n", None).unwrap(); - let chars = buffer.chars_at(Point::new(6, 0)).unwrap(); - assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); + let chars = buffer.chars_at(Point::new(6, 0)).unwrap(); + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); - buffer - }); + buffer + }); } - // #[test] - // fn test_point_for_offset() -> Result<()> { - // let text = Text::from("abc\ndefgh\nijklm\nopq"); - // assert_eq!(text.point_for_offset(0)?, Point { row: 0, column: 0 }); - // assert_eq!(text.point_for_offset(1)?, Point { row: 0, column: 1 }); - // assert_eq!(text.point_for_offset(2)?, Point { row: 0, column: 2 }); - // assert_eq!(text.point_for_offset(3)?, Point { row: 0, column: 3 }); - // assert_eq!(text.point_for_offset(4)?, Point { row: 1, column: 0 }); - // assert_eq!(text.point_for_offset(5)?, Point { row: 1, column: 1 }); - // assert_eq!(text.point_for_offset(9)?, Point { row: 1, column: 5 }); - // assert_eq!(text.point_for_offset(10)?, Point { row: 2, column: 0 }); - // assert_eq!(text.point_for_offset(14)?, Point { row: 2, column: 4 }); - // assert_eq!(text.point_for_offset(15)?, Point { row: 2, column: 5 }); - // assert_eq!(text.point_for_offset(16)?, Point { row: 3, column: 0 }); - // assert_eq!(text.point_for_offset(17)?, Point { row: 3, column: 1 }); - // assert_eq!(text.point_for_offset(19)?, Point { row: 3, column: 3 }); - // assert!(text.point_for_offset(20).is_err()); - // - // let text = Text::from("abc"); - // assert_eq!(text.point_for_offset(0)?, Point { row: 0, column: 0 }); - // assert_eq!(text.point_for_offset(1)?, Point { row: 0, column: 1 }); - // assert_eq!(text.point_for_offset(2)?, Point { row: 0, column: 2 }); - // assert_eq!(text.point_for_offset(3)?, Point { row: 0, column: 3 }); - // assert!(text.point_for_offset(4).is_err()); - // Ok(()) - // } - - // #[test] - // fn test_offset_for_point() -> Result<()> { - // let text = Text::from("abc\ndefgh"); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 0 })?, 0); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 1 })?, 1); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 2 })?, 2); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 3 })?, 3); - // assert!(text.offset_for_point(Point { row: 0, column: 4 }).is_err()); - // assert_eq!(text.offset_for_point(Point { row: 1, column: 0 })?, 4); - // assert_eq!(text.offset_for_point(Point { row: 1, column: 1 })?, 5); - // assert_eq!(text.offset_for_point(Point { row: 1, column: 5 })?, 9); - // assert!(text.offset_for_point(Point { row: 1, column: 6 }).is_err()); - // - // let text = Text::from("abc"); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 0 })?, 0); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 1 })?, 1); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 2 })?, 2); - // assert_eq!(text.offset_for_point(Point { row: 0, column: 3 })?, 3); - // assert!(text.offset_for_point(Point { row: 0, column: 4 }).is_err()); - // Ok(()) - // } - - // #[test] - // fn test_longest_row_in_range() -> Result<()> { - // for seed in 0..100 { - // println!("{:?}", seed); - // let mut rng = &mut StdRng::seed_from_u64(seed); - // let string_len = rng.gen_range(1, 10); - // let string = RandomCharIter(&mut rng) - // .take(string_len) - // .collect::(); - // let text = Text::from(string.as_ref()); - // - // for _i in 0..10 { - // let end = rng.gen_range(1, string.len() + 1); - // let start = rng.gen_range(0, end); - // - // let mut cur_row = string[0..start].chars().filter(|c| *c == '\n').count() as u32; - // let mut cur_row_len = 0; - // let mut expected_longest_row = cur_row; - // let mut expected_longest_row_len = cur_row_len; - // for ch in string[start..end].chars() { - // if ch == '\n' { - // if cur_row_len > expected_longest_row_len { - // expected_longest_row = cur_row; - // expected_longest_row_len = cur_row_len; - // } - // cur_row += 1; - // cur_row_len = 0; - // } else { - // cur_row_len += 1; - // } - // } - // if cur_row_len > expected_longest_row_len { - // expected_longest_row = cur_row; - // expected_longest_row_len = cur_row_len; - // } - // - // assert_eq!( - // text.longest_row_in_range(start..end)?, - // (expected_longest_row, expected_longest_row_len) - // ); - // } - // } - // Ok(()) - // } - #[test] fn test_fragment_ids() { for seed in 0..10 { diff --git a/zed/src/editor/buffer/rope.rs b/zed/src/editor/buffer/rope.rs new file mode 100644 index 0000000000000000000000000000000000000000..74a680286bc0ad2a0430094308d3040f8499f966 --- /dev/null +++ b/zed/src/editor/buffer/rope.rs @@ -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, +} + +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) -> 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 { + self.chunks.cursor::<(), ()>().map(|c| c.0.as_str()) + } + + pub fn to_point(&self, offset: usize) -> Result { + if offset <= self.summary().chars { + let mut cursor = self.chunks.cursor::(); + 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 { + // TODO: Verify the point actually exists. + if point <= self.summary().lines { + let mut cursor = self.chunks.cursor::(); + 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 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::(); + 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 { + 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::(), 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 + } + } +} diff --git a/zed/src/editor/buffer/text.rs b/zed/src/editor/buffer/text.rs deleted file mode 100644 index 03b8b528547c5abab29bd6e650835044a9ebe684..0000000000000000000000000000000000000000 --- a/zed/src/editor/buffer/text.rs +++ /dev/null @@ -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 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, - runs: SumTree, - range: Range, -} - -impl From 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> for Text { - fn from(text: Arc) -> 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> Index 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 { - self.range.clone() - } - - pub fn as_str(&self) -> &str { - &self[..] - } - - pub fn slice>(&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::(); - cursor.seek(&self.range.start, SeekBias::Right, &()); - let absolute_row = cursor.start().row + row; - - let mut cursor = self.runs.cursor::(); - cursor.seek(&Point::new(absolute_row, 0), SeekBias::Right, &()); - let prefix_len = self.range.start.saturating_sub(*cursor.start()); - let line_len = - cursor.summary::(&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::(); - cursor.seek(&self.range.start, SeekBias::Right, &()); - let absolute_start_row = cursor.start().row; - - let mut cursor = self.runs.cursor::(); - cursor.seek(&Point::new(absolute_start_row + 1, 0), SeekBias::Right, &()); - let summary = cursor.summary::( - &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::(); - 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::(); - 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::(); - 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::(); - let expected_line_endpoints = string_slice - .split('\n') - .enumerate() - .map(|(row, line)| Point::new(row as u32, line.chars().count() as u32)) - .collect::>(); - let text_slice = text.slice(start..end); - - assert_eq!(text_slice.lines(), lines(&string_slice)); - - let mut rightmost_points: HashSet = 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::()); - 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) - } -} diff --git a/zed/src/editor/display_map/fold_map.rs b/zed/src/editor/display_map/fold_map.rs index e33013565f66fc1a2d0a650850a1ad7e46e751e0..b404bcb3dc766fd1a8688c867ed9ae140eff2b98 100644 --- a/zed/src/editor/display_map/fold_map.rs +++ b/zed/src/editor/display_map/fold_map.rs @@ -1,8 +1,9 @@ use super::{ - buffer::{self, AnchorRangeExt}, - Anchor, Buffer, DisplayPoint, Edit, Point, TextSummary, ToOffset, + buffer::{AnchorRangeExt, TextSummary}, + Anchor, Buffer, DisplayPoint, Edit, Point, ToOffset, }; use crate::{ + editor::rope, sum_tree::{self, Cursor, FilterCursor, SeekBias, SumTree}, time, }; @@ -607,7 +608,7 @@ pub struct Chars<'a> { cursor: Cursor<'a, Transform, DisplayOffset, TransformSummary>, offset: usize, buffer: &'a Buffer, - buffer_chars: Option>>, + buffer_chars: Option>>, } impl<'a> Iterator for Chars<'a> { @@ -669,8 +670,8 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { #[cfg(test)] mod tests { use super::*; + use crate::editor::buffer::ToPoint; use crate::test::sample_text; - use buffer::ToPoint; #[gpui::test] fn test_basic_folds(app: &mut gpui::MutableAppContext) { @@ -916,6 +917,7 @@ mod tests { assert_eq!(line_len, line.chars().count() as u32); } + let rightmost_point = map.rightmost_point(app.as_ref()); let mut display_point = DisplayPoint::new(0, 0); let mut display_offset = DisplayOffset(0); for c in expected_text.chars() { @@ -941,6 +943,12 @@ mod tests { *display_point.column_mut() += 1; } display_offset.0 += 1; + if display_point.column() > rightmost_point.column() { + panic!( + "invalid rightmost point {:?}, found point {:?}", + rightmost_point, display_point + ); + } } for _ in 0..5 { diff --git a/zed/src/editor/display_map/mod.rs b/zed/src/editor/display_map/mod.rs index 608d39dac85161a28b4d9d4756cdea87d5c3d114..7ee540bd2e094522f9f2fd417ed7a2b68f5a9a4f 100644 --- a/zed/src/editor/display_map/mod.rs +++ b/zed/src/editor/display_map/mod.rs @@ -1,6 +1,6 @@ mod fold_map; -use super::{buffer, Anchor, Buffer, Edit, Point, TextSummary, ToOffset, ToPoint}; +use super::{buffer, Anchor, Buffer, Edit, Point, ToOffset, ToPoint}; use anyhow::Result; pub use fold_map::BufferRows; use fold_map::{FoldMap, FoldMapSnapshot}; diff --git a/zed/src/sum_tree/mod.rs b/zed/src/sum_tree/mod.rs index e70440f16f0c0bfeb811c8ad1aa6167adc85b253..b96b36c49d4444566688c72a470974dffb5dac62 100644 --- a/zed/src/sum_tree/mod.rs +++ b/zed/src/sum_tree/mod.rs @@ -101,6 +101,46 @@ impl SumTree { self.rightmost_leaf().0.items().last() } + pub fn update_last(&mut self, f: impl FnOnce(&mut T), ctx: &::Context) { + self.update_last_recursive(f, ctx); + } + + fn update_last_recursive( + &mut self, + f: impl FnOnce(&mut T), + ctx: &::Context, + ) -> Option { + match Arc::make_mut(&mut self.0) { + Node::Internal { + summary, + child_summaries, + child_trees, + .. + } => { + let last_summary = child_summaries.last_mut().unwrap(); + let last_child = child_trees.last_mut().unwrap(); + *last_summary = last_child.update_last_recursive(f, ctx).unwrap(); + *summary = sum(child_summaries.iter(), ctx); + Some(summary.clone()) + } + Node::Leaf { + summary, + items, + item_summaries, + } => { + if let Some((item, item_summary)) = items.last_mut().zip(item_summaries.last_mut()) + { + (f)(item); + *item_summary = item.summary(); + *summary = sum(item_summaries.iter(), ctx); + Some(summary.clone()) + } else { + None + } + } + } + } + pub fn extent<'a, D: Dimension<'a, T::Summary>>(&'a self) -> D { let mut extent = D::default(); match self.0.as_ref() { diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 0f69ea833113b37f87b4658a12b407a757368897..2a924cf58636d2788514d40195906fd8ea9897b5 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -3,7 +3,7 @@ mod fuzzy; mod ignore; use crate::{ - editor::{History, Snapshot as BufferSnapshot}, + editor::{History, Rope}, sum_tree::{self, Cursor, Edit, SeekBias, SumTree}, }; use ::ignore::gitignore::Gitignore; @@ -198,20 +198,15 @@ impl Worktree { }) } - pub fn save<'a>( - &self, - path: &Path, - content: BufferSnapshot, - ctx: &AppContext, - ) -> Task> { + pub fn save<'a>(&self, path: &Path, content: Rope, ctx: &AppContext) -> Task> { let handles = self.handles.clone(); let path = path.to_path_buf(); let abs_path = self.absolutize(&path); ctx.background_executor().spawn(async move { - let buffer_size = content.text_summary().bytes.min(10 * 1024); + let buffer_size = content.summary().bytes.min(10 * 1024); let file = fs::File::create(&abs_path)?; let mut writer = io::BufWriter::with_capacity(buffer_size, &file); - for chunk in content.fragments() { + for chunk in content.chunks() { writer.write(chunk.as_bytes())?; } writer.flush()?; @@ -459,7 +454,7 @@ impl FileHandle { self.worktree.read(ctx).load_history(&self.path(), ctx) } - pub fn save<'a>(&self, content: BufferSnapshot, ctx: &AppContext) -> Task> { + pub fn save<'a>(&self, content: Rope, ctx: &AppContext) -> Task> { let worktree = self.worktree.read(ctx); worktree.save(&self.path(), content, ctx) }