diff --git a/crates/buffer/src/lib.rs b/crates/buffer/src/lib.rs index 3817b7131a..ad3a319421 100644 --- a/crates/buffer/src/lib.rs +++ b/crates/buffer/src/lib.rs @@ -158,24 +158,15 @@ impl Drop for QueryCursorHandle { } } -pub struct Buffer { +#[derive(Clone)] +pub struct TextBuffer { fragments: SumTree, visible_text: Rope, deleted_text: Rope, pub version: clock::Global, - saved_version: clock::Global, - saved_mtime: SystemTime, last_edit: clock::Local, undo_map: UndoMap, history: History, - file: Option>, - language: Option>, - autoindent_requests: Vec>, - pending_autoindent: Option>, - sync_parse_timeout: Duration, - syntax_tree: Mutex>, - parsing_in_background: bool, - parse_count: usize, selections: HashMap, deferred_ops: OperationQueue, deferred_replicas: HashSet, @@ -183,6 +174,20 @@ pub struct Buffer { remote_id: u64, local_clock: clock::Local, lamport_clock: clock::Lamport, +} + +pub struct Buffer { + buffer: TextBuffer, + file: Option>, + saved_version: clock::Global, + saved_mtime: SystemTime, + language: Option>, + autoindent_requests: Vec>, + pending_autoindent: Option>, + sync_parse_timeout: Duration, + syntax_tree: Mutex>, + parsing_in_background: bool, + parse_count: usize, #[cfg(test)] operations: Vec, } @@ -208,10 +213,9 @@ struct AutoindentRequest { } #[derive(Clone, Debug)] -struct Transaction { +pub struct Transaction { start: clock::Global, end: clock::Global, - buffer_was_dirty: bool, edits: Vec, ranges: Vec>, selections_before: HashMap>, @@ -221,6 +225,10 @@ struct Transaction { } impl Transaction { + pub fn starting_selection_set_ids<'a>(&'a self) -> impl Iterator + 'a { + self.selections_before.keys().copied() + } + fn push_edit(&mut self, edit: &EditOperation) { self.edits.push(edit.timestamp.local()); self.end.observe(edit.timestamp.local()); @@ -298,7 +306,6 @@ impl History { fn start_transaction( &mut self, start: clock::Global, - buffer_was_dirty: bool, selections_before: HashMap>, now: Instant, ) { @@ -307,7 +314,6 @@ impl History { self.undo_stack.push(Transaction { start: start.clone(), end: start, - buffer_was_dirty, edits: Vec::new(), ranges: Vec::new(), selections_before, @@ -574,6 +580,884 @@ pub struct UndoOperation { version: clock::Global, } +impl Deref for Buffer { + type Target = TextBuffer; + + fn deref(&self) -> &Self::Target { + &self.buffer + } +} + +impl TextBuffer { + pub fn new(replica_id: u16, remote_id: u64, history: History) -> TextBuffer { + let mut fragments = SumTree::new(); + + let visible_text = Rope::from(history.base_text.as_ref()); + if visible_text.len() > 0 { + fragments.push( + Fragment { + timestamp: Default::default(), + len: visible_text.len(), + visible: true, + deletions: Default::default(), + max_undos: Default::default(), + }, + &None, + ); + } + + TextBuffer { + visible_text, + deleted_text: Rope::new(), + fragments, + version: clock::Global::new(), + last_edit: clock::Local::default(), + undo_map: Default::default(), + history, + selections: HashMap::default(), + deferred_ops: OperationQueue::new(), + deferred_replicas: HashSet::default(), + replica_id, + remote_id, + local_clock: clock::Local::new(replica_id), + lamport_clock: clock::Lamport::new(replica_id), + } + } + + pub fn version(&self) -> clock::Global { + self.version.clone() + } + + fn content<'a>(&'a self) -> Content<'a> { + self.into() + } + + pub fn as_rope(&self) -> &Rope { + &self.visible_text + } + + pub fn text_summary_for_range(&self, range: Range) -> TextSummary { + self.content().text_summary_for_range(range) + } + + pub fn anchor_before(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Left) + } + + pub fn anchor_after(&self, position: T) -> Anchor { + self.anchor_at(position, Bias::Right) + } + + pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { + self.content().anchor_at(position, bias) + } + + pub fn point_for_offset(&self, offset: usize) -> Result { + self.content().point_for_offset(offset) + } + + pub fn clip_point(&self, point: Point, bias: Bias) -> Point { + self.visible_text.clip_point(point, bias) + } + + pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { + self.visible_text.clip_offset(offset, bias) + } + + pub fn replica_id(&self) -> ReplicaId { + self.local_clock.replica_id + } + + pub fn remote_id(&self) -> u64 { + self.remote_id + } + + pub fn text_summary(&self) -> TextSummary { + self.visible_text.summary() + } + + pub fn len(&self) -> usize { + self.content().len() + } + + pub fn line_len(&self, row: u32) -> u32 { + self.content().line_len(row) + } + + pub fn max_point(&self) -> Point { + self.visible_text.max_point() + } + + pub fn row_count(&self) -> u32 { + self.max_point().row + 1 + } + + pub fn text(&self) -> String { + self.text_for_range(0..self.len()).collect() + } + + pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range) -> Chunks<'a> { + self.content().text_for_range(range) + } + + pub fn chars(&self) -> impl Iterator + '_ { + self.chars_at(0) + } + + pub fn chars_at<'a, T: 'a + ToOffset>( + &'a self, + position: T, + ) -> impl Iterator + 'a { + self.content().chars_at(position) + } + + pub fn reversed_chars_at<'a, T: 'a + ToOffset>( + &'a self, + position: T, + ) -> impl Iterator + 'a { + self.content().reversed_chars_at(position) + } + + pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { + self.text_for_range(range).flat_map(str::chars) + } + + pub fn bytes_at(&self, position: T) -> impl Iterator + '_ { + let offset = position.to_offset(self); + self.visible_text.bytes_at(offset) + } + + pub fn contains_str_at(&self, position: T, needle: &str) -> bool + where + T: ToOffset, + { + let position = position.to_offset(self); + position == self.clip_offset(position, Bias::Left) + && self + .bytes_at(position) + .take(needle.len()) + .eq(needle.bytes()) + } + + pub fn deferred_ops_len(&self) -> usize { + self.deferred_ops.len() + } + + pub fn edit(&mut self, ranges: R, new_text: T) -> EditOperation + where + R: IntoIterator, + I: ExactSizeIterator>, + S: ToOffset, + T: Into, + { + let new_text = new_text.into(); + let new_text_len = new_text.len(); + let new_text = if new_text_len > 0 { + Some(new_text) + } else { + None + }; + + self.start_transaction(None).unwrap(); + let timestamp = InsertionTimestamp { + replica_id: self.replica_id, + local: self.local_clock.tick().value, + lamport: self.lamport_clock.tick().value, + }; + let edit = self.apply_local_edit(ranges.into_iter(), new_text, timestamp); + + self.history.push(edit.clone()); + self.history.push_undo(edit.timestamp.local()); + self.last_edit = edit.timestamp.local(); + self.version.observe(edit.timestamp.local()); + self.end_transaction(None); + edit + } + + fn apply_local_edit( + &mut self, + ranges: impl ExactSizeIterator>, + new_text: Option, + timestamp: InsertionTimestamp, + ) -> EditOperation { + let mut edit = EditOperation { + timestamp, + version: self.version(), + ranges: Vec::with_capacity(ranges.len()), + new_text: None, + }; + + let mut ranges = ranges + .map(|range| range.start.to_offset(&*self)..range.end.to_offset(&*self)) + .peekable(); + + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::(); + let mut new_fragments = + old_fragments.slice(&ranges.peek().unwrap().start, Bias::Right, &None); + new_ropes.push_tree(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().visible; + for range in ranges { + let fragment_end = old_fragments.end(&None).visible; + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let slice = old_fragments.slice(&range.start, Bias::Right, &None); + new_ropes.push_tree(slice.summary().text); + new_fragments.push_tree(slice, &None); + fragment_start = old_fragments.start().visible; + } + + let full_range_start = range.start + old_fragments.start().deleted; + + // Preserve any portion of the current fragment that precedes this range. + if fragment_start < range.start { + let mut prefix = old_fragments.item().unwrap().clone(); + prefix.len = range.start - fragment_start; + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + fragment_start = range.start; + } + + // Insert the new text before any existing fragments within the range. + if let Some(new_text) = new_text.as_deref() { + new_ropes.push_str(new_text); + new_fragments.push( + Fragment { + timestamp, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }, + &None, + ); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&None).visible; + let mut intersection = fragment.clone(); + let intersection_end = cmp::min(range.end, fragment_end); + if fragment.visible { + intersection.len = intersection_end - fragment_start; + intersection.deletions.insert(timestamp.local()); + intersection.visible = false; + } + if intersection.len > 0 { + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&None); + } + } + + let full_range_end = range.end + old_fragments.start().deleted; + edit.ranges.push(full_range_start..full_range_end); + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().visible { + let fragment_end = old_fragments.end(&None).visible; + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&None); + } + + let suffix = old_fragments.suffix(&None); + new_ropes.push_tree(suffix.summary().text); + new_fragments.push_tree(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.fragments = new_fragments; + self.visible_text = visible_text; + self.deleted_text = deleted_text; + edit.new_text = new_text; + edit + } + + pub fn apply_ops>(&mut self, ops: I) -> Result<()> { + let mut deferred_ops = Vec::new(); + for op in ops { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + self.flush_deferred_ops()?; + Ok(()) + } + + fn apply_op(&mut self, op: Operation) -> Result<()> { + match op { + Operation::Edit(edit) => { + if !self.version.observed(edit.timestamp.local()) { + self.apply_remote_edit( + &edit.version, + &edit.ranges, + edit.new_text.as_deref(), + edit.timestamp, + ); + self.version.observe(edit.timestamp.local()); + self.history.push(edit); + } + } + Operation::Undo { + undo, + lamport_timestamp, + } => { + if !self.version.observed(undo.id) { + self.apply_undo(&undo)?; + self.version.observe(undo.id); + self.lamport_clock.observe(lamport_timestamp); + } + } + Operation::UpdateSelections { + set_id, + selections, + lamport_timestamp, + } => { + if let Some(selections) = selections { + if let Some(set) = self.selections.get_mut(&set_id) { + set.selections = selections; + } else { + self.selections.insert( + set_id, + SelectionSet { + selections, + active: false, + }, + ); + } + } else { + self.selections.remove(&set_id); + } + self.lamport_clock.observe(lamport_timestamp); + } + Operation::SetActiveSelections { + set_id, + lamport_timestamp, + } => { + for (id, set) in &mut self.selections { + if id.replica_id == lamport_timestamp.replica_id { + if Some(*id) == set_id { + set.active = true; + } else { + set.active = false; + } + } + } + self.lamport_clock.observe(lamport_timestamp); + } + #[cfg(test)] + Operation::Test(_) => {} + } + Ok(()) + } + + fn apply_remote_edit( + &mut self, + version: &clock::Global, + ranges: &[Range], + new_text: Option<&str>, + timestamp: InsertionTimestamp, + ) { + if ranges.is_empty() { + return; + } + + let cx = Some(version.clone()); + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + let mut old_fragments = self.fragments.cursor::(); + let mut new_fragments = + old_fragments.slice(&VersionedOffset::Offset(ranges[0].start), Bias::Left, &cx); + new_ropes.push_tree(new_fragments.summary().text); + + let mut fragment_start = old_fragments.start().offset(); + for range in ranges { + let fragment_end = old_fragments.end(&cx).offset(); + + // If the current fragment ends before this range, then jump ahead to the first fragment + // that extends past the start of this range, reusing any intervening fragments. + if fragment_end < range.start { + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().offset() { + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let slice = + old_fragments.slice(&VersionedOffset::Offset(range.start), Bias::Left, &cx); + new_ropes.push_tree(slice.summary().text); + new_fragments.push_tree(slice, &None); + fragment_start = old_fragments.start().offset(); + } + + // If we are at the end of a non-concurrent fragment, advance to the next one. + let fragment_end = old_fragments.end(&cx).offset(); + if fragment_end == range.start && fragment_end > fragment_start { + let mut fragment = old_fragments.item().unwrap().clone(); + fragment.len = fragment_end - fragment_start; + new_ropes.push_fragment(&fragment, fragment.visible); + new_fragments.push(fragment, &None); + old_fragments.next(&cx); + fragment_start = old_fragments.start().offset(); + } + + // Skip over insertions that are concurrent to this edit, but have a lower lamport + // timestamp. + while let Some(fragment) = old_fragments.item() { + if fragment_start == range.start + && fragment.timestamp.lamport() > timestamp.lamport() + { + new_ropes.push_fragment(fragment, fragment.visible); + new_fragments.push(fragment.clone(), &None); + old_fragments.next(&cx); + debug_assert_eq!(fragment_start, range.start); + } else { + break; + } + } + debug_assert!(fragment_start <= range.start); + + // Preserve any portion of the current fragment that precedes this range. + if fragment_start < range.start { + let mut prefix = old_fragments.item().unwrap().clone(); + prefix.len = range.start - fragment_start; + fragment_start = range.start; + new_ropes.push_fragment(&prefix, prefix.visible); + new_fragments.push(prefix, &None); + } + + // Insert the new text before any existing fragments within the range. + if let Some(new_text) = new_text { + new_ropes.push_str(new_text); + new_fragments.push( + Fragment { + timestamp, + len: new_text.len(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, + }, + &None, + ); + } + + // Advance through every fragment that intersects this range, marking the intersecting + // portions as deleted. + while fragment_start < range.end { + let fragment = old_fragments.item().unwrap(); + let fragment_end = old_fragments.end(&cx).offset(); + let mut intersection = fragment.clone(); + let intersection_end = cmp::min(range.end, fragment_end); + if fragment.was_visible(version, &self.undo_map) { + intersection.len = intersection_end - fragment_start; + intersection.deletions.insert(timestamp.local()); + intersection.visible = false; + } + if intersection.len > 0 { + new_ropes.push_fragment(&intersection, fragment.visible); + new_fragments.push(intersection, &None); + fragment_start = intersection_end; + } + if fragment_end <= range.end { + old_fragments.next(&cx); + } + } + } + + // If the current fragment has been partially consumed, then consume the rest of it + // and advance to the next fragment before slicing. + if fragment_start > old_fragments.start().offset() { + let fragment_end = old_fragments.end(&cx).offset(); + if fragment_end > fragment_start { + let mut suffix = old_fragments.item().unwrap().clone(); + suffix.len = fragment_end - fragment_start; + new_ropes.push_fragment(&suffix, suffix.visible); + new_fragments.push(suffix, &None); + } + old_fragments.next(&cx); + } + + let suffix = old_fragments.suffix(&cx); + new_ropes.push_tree(suffix.summary().text); + new_fragments.push_tree(suffix, &None); + let (visible_text, deleted_text) = new_ropes.finish(); + drop(old_fragments); + + self.fragments = new_fragments; + self.visible_text = visible_text; + self.deleted_text = deleted_text; + self.local_clock.observe(timestamp.local()); + self.lamport_clock.observe(timestamp.lamport()); + } + + fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { + self.undo_map.insert(undo); + + let mut cx = undo.version.clone(); + for edit_id in undo.counts.keys().copied() { + cx.observe(edit_id); + } + let cx = Some(cx); + + let mut old_fragments = self.fragments.cursor::(); + let mut new_fragments = old_fragments.slice( + &VersionedOffset::Offset(undo.ranges[0].start), + Bias::Right, + &cx, + ); + let mut new_ropes = + RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); + new_ropes.push_tree(new_fragments.summary().text); + + for range in &undo.ranges { + let mut end_offset = old_fragments.end(&cx).offset(); + + if end_offset < range.start { + let preceding_fragments = + old_fragments.slice(&VersionedOffset::Offset(range.start), Bias::Right, &cx); + new_ropes.push_tree(preceding_fragments.summary().text); + new_fragments.push_tree(preceding_fragments, &None); + } + + while end_offset <= range.end { + if let Some(fragment) = old_fragments.item() { + let mut fragment = fragment.clone(); + let fragment_was_visible = fragment.visible; + + if fragment.was_visible(&undo.version, &self.undo_map) + || undo.counts.contains_key(&fragment.timestamp.local()) + { + 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, &None); + + old_fragments.next(&cx); + if end_offset == old_fragments.end(&cx).offset() { + let unseen_fragments = old_fragments.slice( + &VersionedOffset::Offset(end_offset), + Bias::Right, + &cx, + ); + new_ropes.push_tree(unseen_fragments.summary().text); + new_fragments.push_tree(unseen_fragments, &None); + } + end_offset = old_fragments.end(&cx).offset(); + } else { + break; + } + } + } + + let suffix = old_fragments.suffix(&cx); + new_ropes.push_tree(suffix.summary().text); + new_fragments.push_tree(suffix, &None); + + drop(old_fragments); + let (visible_text, deleted_text) = new_ropes.finish(); + self.fragments = new_fragments; + self.visible_text = visible_text; + self.deleted_text = deleted_text; + Ok(()) + } + + fn flush_deferred_ops(&mut self) -> Result<()> { + self.deferred_replicas.clear(); + let mut deferred_ops = Vec::new(); + for op in self.deferred_ops.drain().cursor().cloned() { + if self.can_apply_op(&op) { + self.apply_op(op)?; + } else { + self.deferred_replicas.insert(op.replica_id()); + deferred_ops.push(op); + } + } + self.deferred_ops.insert(deferred_ops); + Ok(()) + } + + fn can_apply_op(&self, op: &Operation) -> bool { + if self.deferred_replicas.contains(&op.replica_id()) { + false + } else { + match op { + Operation::Edit(edit) => self.version >= edit.version, + Operation::Undo { undo, .. } => self.version >= undo.version, + Operation::UpdateSelections { selections, .. } => { + if let Some(selections) = selections { + selections.iter().all(|selection| { + let contains_start = self.version >= selection.start.version; + let contains_end = self.version >= selection.end.version; + contains_start && contains_end + }) + } else { + true + } + } + Operation::SetActiveSelections { set_id, .. } => { + set_id.map_or(true, |set_id| self.selections.contains_key(&set_id)) + } + #[cfg(test)] + Operation::Test(_) => true, + } + } + } + + pub fn peek_undo_stack(&self) -> Option<&Transaction> { + self.history.undo_stack.last() + } + + pub fn start_transaction( + &mut self, + selection_set_ids: impl IntoIterator, + ) -> Result<()> { + self.start_transaction_at(selection_set_ids, Instant::now())?; + Ok(()) + } + + fn start_transaction_at( + &mut self, + selection_set_ids: impl IntoIterator, + now: Instant, + ) -> Result<()> { + let selections = selection_set_ids + .into_iter() + .map(|set_id| { + let set = self + .selections + .get(&set_id) + .expect("invalid selection set id"); + (set_id, set.selections.clone()) + }) + .collect(); + self.history + .start_transaction(self.version.clone(), selections, now); + Ok(()) + } + + fn end_transaction(&mut self, selection_set_ids: impl IntoIterator) { + self.end_transaction_at(selection_set_ids, Instant::now()); + } + + fn end_transaction_at( + &mut self, + selection_set_ids: impl IntoIterator, + now: Instant, + ) -> Option { + let selections = selection_set_ids + .into_iter() + .map(|set_id| { + let set = self + .selections + .get(&set_id) + .expect("invalid selection set id"); + (set_id, set.selections.clone()) + }) + .collect(); + + if let Some(transaction) = self.history.end_transaction(selections, now) { + let since = transaction.start.clone(); + self.history.group(); + Some(since) + } else { + None + } + } + + fn remove_peer(&mut self, replica_id: ReplicaId) { + self.selections + .retain(|set_id, _| set_id.replica_id != replica_id) + } + + fn undo(&mut self) -> Vec { + let mut ops = Vec::new(); + if let Some(transaction) = self.history.pop_undo().cloned() { + let selections = transaction.selections_before.clone(); + ops.push(self.undo_or_redo(transaction).unwrap()); + for (set_id, selections) in selections { + ops.extend(self.update_selection_set(set_id, selections)); + } + } + ops + } + + fn redo(&mut self) -> Vec { + let mut ops = Vec::new(); + if let Some(transaction) = self.history.pop_redo().cloned() { + let selections = transaction.selections_after.clone(); + ops.push(self.undo_or_redo(transaction).unwrap()); + for (set_id, selections) in selections { + ops.extend(self.update_selection_set(set_id, selections)); + } + } + ops + } + + fn undo_or_redo(&mut self, transaction: Transaction) -> Result { + let mut counts = HashMap::default(); + for edit_id in transaction.edits { + counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); + } + + let undo = UndoOperation { + id: self.local_clock.tick(), + counts, + ranges: transaction.ranges, + version: transaction.start.clone(), + }; + self.apply_undo(&undo)?; + self.version.observe(undo.id); + + Ok(Operation::Undo { + undo, + lamport_timestamp: self.lamport_clock.tick(), + }) + } + + pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> { + self.selections + .get(&set_id) + .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id)) + } + + pub fn selection_sets(&self) -> impl Iterator { + self.selections.iter() + } + + pub fn update_selection_set( + &mut self, + set_id: SelectionSetId, + selections: impl Into>, + ) -> Result { + let selections = selections.into(); + let set = self + .selections + .get_mut(&set_id) + .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; + set.selections = selections.clone(); + Ok(Operation::UpdateSelections { + set_id, + selections: Some(selections), + lamport_timestamp: self.lamport_clock.tick(), + }) + } + + pub fn add_selection_set(&mut self, selections: impl Into>) -> Operation { + let selections = selections.into(); + let lamport_timestamp = self.lamport_clock.tick(); + self.selections.insert( + lamport_timestamp, + SelectionSet { + selections: selections.clone(), + active: false, + }, + ); + Operation::UpdateSelections { + set_id: lamport_timestamp, + selections: Some(selections), + lamport_timestamp, + } + } + + pub fn set_active_selection_set( + &mut self, + set_id: Option, + ) -> Result { + if let Some(set_id) = set_id { + assert_eq!(set_id.replica_id, self.replica_id()); + } + + for (id, set) in &mut self.selections { + if id.replica_id == self.local_clock.replica_id { + if Some(*id) == set_id { + set.active = true; + } else { + set.active = false; + } + } + } + + Ok(Operation::SetActiveSelections { + set_id, + lamport_timestamp: self.lamport_clock.tick(), + }) + } + + pub fn remove_selection_set(&mut self, set_id: SelectionSetId) -> Result { + self.selections + .remove(&set_id) + .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; + Ok(Operation::UpdateSelections { + set_id, + selections: None, + lamport_timestamp: self.lamport_clock.tick(), + }) + } + + pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator { + let since_2 = since.clone(); + let cursor = if since == self.version { + None + } else { + Some(self.fragments.filter( + move |summary| summary.max_version.changed_since(&since_2), + &None, + )) + }; + + Edits { + visible_text: &self.visible_text, + deleted_text: &self.deleted_text, + cursor, + undos: &self.undo_map, + since, + old_offset: 0, + new_offset: 0, + old_point: Point::zero(), + new_point: Point::zero(), + } + } +} + impl Buffer { pub fn new>>( replica_id: ReplicaId, @@ -622,31 +1506,10 @@ impl Buffer { saved_mtime = UNIX_EPOCH; } - let mut fragments = SumTree::new(); - - let visible_text = Rope::from(history.base_text.as_ref()); - if visible_text.len() > 0 { - fragments.push( - Fragment { - timestamp: Default::default(), - len: visible_text.len(), - visible: true, - deletions: Default::default(), - max_undos: Default::default(), - }, - &None, - ); - } - let mut result = Self { - visible_text, - deleted_text: Rope::new(), - fragments, - version: clock::Global::new(), + buffer: TextBuffer::new(replica_id, remote_id, history), + saved_mtime, saved_version: clock::Global::new(), - last_edit: clock::Local::default(), - undo_map: Default::default(), - history, file, syntax_tree: Mutex::new(None), parsing_in_background: false, @@ -655,14 +1518,6 @@ impl Buffer { autoindent_requests: Default::default(), pending_autoindent: Default::default(), language, - saved_mtime, - selections: HashMap::default(), - deferred_ops: OperationQueue::new(), - deferred_replicas: HashSet::default(), - replica_id, - remote_id, - local_clock: clock::Local::new(replica_id), - lamport_clock: clock::Lamport::new(replica_id), #[cfg(test)] operations: Default::default(), @@ -671,10 +1526,6 @@ impl Buffer { result } - pub fn replica_id(&self) -> ReplicaId { - self.local_clock.replica_id - } - pub fn snapshot(&self) -> Snapshot { Snapshot { visible_text: self.visible_text.clone(), @@ -707,7 +1558,7 @@ impl Buffer { .into_iter() .map(|op| Operation::Edit(op.into())); buffer.apply_ops(ops, cx)?; - buffer.selections = message + buffer.buffer.selections = message .selections .into_iter() .map(|set| { @@ -777,10 +1628,6 @@ impl Buffer { })) } - pub fn as_rope(&self) -> &Rope { - &self.visible_text - } - pub fn set_language(&mut self, language: Option>, cx: &mut ModelContext) { self.language = language; self.reparse(cx); @@ -1131,9 +1978,9 @@ impl Buffer { .selections .iter() .map(|selection| { - let start_point = selection.start.to_point(&*self); + let start_point = selection.start.to_point(&self.buffer); if start_point.column == 0 { - let end_point = selection.end.to_point(&*self); + let end_point = selection.end.to_point(&self.buffer); let delta = Point::new( 0, indent_columns.get(&start_point.row).copied().unwrap_or(0), @@ -1290,114 +2137,12 @@ impl Buffer { .map_or(false, |file| file.mtime() > self.saved_mtime) } - pub fn remote_id(&self) -> u64 { - self.remote_id - } - - pub fn version(&self) -> clock::Global { - self.version.clone() - } - - pub fn text_summary(&self) -> TextSummary { - self.visible_text.summary() - } - - pub fn len(&self) -> usize { - self.content().len() - } - - pub fn line_len(&self, row: u32) -> u32 { - self.content().line_len(row) - } - - pub fn max_point(&self) -> Point { - self.visible_text.max_point() - } - - pub fn row_count(&self) -> u32 { - self.max_point().row + 1 - } - - pub fn text(&self) -> String { - self.text_for_range(0..self.len()).collect() - } - - pub fn text_for_range<'a, T: ToOffset>(&'a self, range: Range) -> Chunks<'a> { - self.content().text_for_range(range) - } - - pub fn chars(&self) -> impl Iterator + '_ { - self.chars_at(0) - } - - pub fn chars_at<'a, T: 'a + ToOffset>( - &'a self, - position: T, - ) -> impl Iterator + 'a { - self.content().chars_at(position) - } - - pub fn reversed_chars_at<'a, T: 'a + ToOffset>( - &'a self, - position: T, - ) -> impl Iterator + 'a { - self.content().reversed_chars_at(position) - } - - pub fn chars_for_range(&self, range: Range) -> impl Iterator + '_ { - self.text_for_range(range).flat_map(str::chars) - } - - pub fn bytes_at(&self, position: T) -> impl Iterator + '_ { - let offset = position.to_offset(self); - self.visible_text.bytes_at(offset) - } - - pub fn contains_str_at(&self, position: T, needle: &str) -> bool - where - T: ToOffset, - { - let position = position.to_offset(self); - position == self.clip_offset(position, Bias::Left) - && self - .bytes_at(position) - .take(needle.len()) - .eq(needle.bytes()) - } - - pub fn edits_since<'a>(&'a self, since: clock::Global) -> impl 'a + Iterator { - let since_2 = since.clone(); - let cursor = if since == self.version { - None - } else { - Some(self.fragments.filter( - move |summary| summary.max_version.changed_since(&since_2), - &None, - )) - }; - - Edits { - visible_text: &self.visible_text, - deleted_text: &self.deleted_text, - cursor, - undos: &self.undo_map, - since, - old_offset: 0, - new_offset: 0, - old_point: Point::zero(), - new_point: Point::zero(), - } - } - - pub fn deferred_ops_len(&self) -> usize { - self.deferred_ops.len() - } - pub fn start_transaction( &mut self, selection_set_ids: impl IntoIterator, ) -> Result<()> { - self.start_transaction_at(selection_set_ids, Instant::now()) + self.start_transaction_at(selection_set_ids, Instant::now())?; + Ok(()) } fn start_transaction_at( @@ -1405,18 +2150,7 @@ impl Buffer { selection_set_ids: impl IntoIterator, now: Instant, ) -> Result<()> { - let selections = selection_set_ids - .into_iter() - .map(|set_id| { - let set = self - .selections - .get(&set_id) - .expect("invalid selection set id"); - (set_id, set.selections.clone()) - }) - .collect(); - self.history - .start_transaction(self.version.clone(), self.is_dirty(), selections, now); + self.buffer.start_transaction_at(selection_set_ids, now)?; Ok(()) } @@ -1434,29 +2168,15 @@ impl Buffer { now: Instant, cx: &mut ModelContext, ) -> Result<()> { - let selections = selection_set_ids - .into_iter() - .map(|set_id| { - let set = self - .selections - .get(&set_id) - .expect("invalid selection set id"); - (set_id, set.selections.clone()) - }) - .collect(); - - if let Some(transaction) = self.history.end_transaction(selections, now) { - let since = transaction.start.clone(); - let was_dirty = transaction.buffer_was_dirty; - self.history.group(); - + if let Some(start_version) = self.buffer.end_transaction_at(selection_set_ids, now) { cx.notify(); - if self.edits_since(since).next().is_some() { + let was_dirty = start_version != self.saved_version; + let edited = self.edits_since(start_version).next().is_some(); + if edited { self.did_edit(was_dirty, cx); self.reparse(cx); } } - Ok(()) } @@ -1515,6 +2235,7 @@ impl Buffer { return; } + self.start_transaction(None).unwrap(); self.pending_autoindent.take(); let autoindent_request = if autoindent && self.language.is_some() { let before_edit = self.snapshot(); @@ -1533,24 +2254,8 @@ impl Buffer { let first_newline_ix = new_text.find('\n'); let new_text_len = new_text.len(); - let new_text = if new_text_len > 0 { - Some(new_text) - } else { - None - }; - self.start_transaction(None).unwrap(); - let timestamp = InsertionTimestamp { - replica_id: self.replica_id, - local: self.local_clock.tick().value, - lamport: self.lamport_clock.tick().value, - }; - let edit = self.apply_local_edit(&ranges, new_text, timestamp); - - self.history.push(edit.clone()); - self.history.push_undo(edit.timestamp.local()); - self.last_edit = edit.timestamp.local(); - self.version.observe(edit.timestamp.local()); + let edit = self.buffer.edit(ranges.iter().cloned(), new_text); if let Some((before_edit, edited)) = autoindent_request { let mut inserted = None; @@ -1565,13 +2270,10 @@ impl Buffer { } let selection_set_ids = self - .history - .undo_stack - .last() + .buffer + .peek_undo_stack() .unwrap() - .selections_before - .keys() - .copied() + .starting_selection_set_ids() .collect(); self.autoindent_requests.push(Arc::new(AutoindentRequest { selection_set_ids, @@ -1597,27 +2299,15 @@ impl Buffer { selections: impl Into>, cx: &mut ModelContext, ) -> SelectionSetId { - let selections = selections.into(); - let lamport_timestamp = self.lamport_clock.tick(); - self.selections.insert( - lamport_timestamp, - SelectionSet { - selections: selections.clone(), - active: false, - }, - ); - cx.notify(); - - self.send_operation( - Operation::UpdateSelections { - set_id: lamport_timestamp, - selections: Some(selections), - lamport_timestamp, - }, - cx, - ); - - lamport_timestamp + let operation = self.buffer.add_selection_set(selections); + if let Operation::UpdateSelections { set_id, .. } = &operation { + let set_id = *set_id; + cx.notify(); + self.send_operation(operation, cx); + set_id + } else { + unreachable!() + } } pub fn update_selection_set( @@ -1626,22 +2316,9 @@ impl Buffer { selections: impl Into>, cx: &mut ModelContext, ) -> Result<()> { - let selections = selections.into(); - let set = self - .selections - .get_mut(&set_id) - .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; - set.selections = selections.clone(); - let lamport_timestamp = self.lamport_clock.tick(); + let operation = self.buffer.update_selection_set(set_id, selections)?; cx.notify(); - self.send_operation( - Operation::UpdateSelections { - set_id, - selections: Some(selections), - lamport_timestamp, - }, - cx, - ); + self.send_operation(operation, cx); Ok(()) } @@ -1650,28 +2327,8 @@ impl Buffer { set_id: Option, cx: &mut ModelContext, ) -> Result<()> { - if let Some(set_id) = set_id { - assert_eq!(set_id.replica_id, self.replica_id()); - } - - for (id, set) in &mut self.selections { - if id.replica_id == self.local_clock.replica_id { - if Some(*id) == set_id { - set.active = true; - } else { - set.active = false; - } - } - } - - let lamport_timestamp = self.lamport_clock.tick(); - self.send_operation( - Operation::SetActiveSelections { - set_id, - lamport_timestamp, - }, - cx, - ); + let operation = self.buffer.set_active_selection_set(set_id)?; + self.send_operation(operation, cx); Ok(()) } @@ -1680,32 +2337,12 @@ impl Buffer { set_id: SelectionSetId, cx: &mut ModelContext, ) -> Result<()> { - self.selections - .remove(&set_id) - .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id))?; - let lamport_timestamp = self.lamport_clock.tick(); + let operation = self.buffer.remove_selection_set(set_id)?; cx.notify(); - self.send_operation( - Operation::UpdateSelections { - set_id, - selections: None, - lamport_timestamp, - }, - cx, - ); + self.send_operation(operation, cx); Ok(()) } - pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> { - self.selections - .get(&set_id) - .ok_or_else(|| anyhow!("invalid selection set id {:?}", set_id)) - } - - pub fn selection_sets(&self) -> impl Iterator { - self.selections.iter() - } - pub fn apply_ops>( &mut self, ops: I, @@ -1716,17 +2353,7 @@ impl Buffer { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); - let mut deferred_ops = Vec::new(); - for op in ops { - if self.can_apply_op(&op) { - self.apply_op(op)?; - } else { - self.deferred_replicas.insert(op.replica_id()); - deferred_ops.push(op); - } - } - self.deferred_ops.insert(deferred_ops); - self.flush_deferred_ops()?; + self.buffer.apply_ops(ops)?; cx.notify(); if self.edits_since(old_version).next().is_some() { @@ -1737,218 +2364,6 @@ impl Buffer { Ok(()) } - fn apply_op(&mut self, op: Operation) -> Result<()> { - match op { - Operation::Edit(edit) => { - if !self.version.observed(edit.timestamp.local()) { - self.apply_remote_edit( - &edit.version, - &edit.ranges, - edit.new_text.as_deref(), - edit.timestamp, - ); - self.version.observe(edit.timestamp.local()); - self.history.push(edit); - } - } - Operation::Undo { - undo, - lamport_timestamp, - } => { - if !self.version.observed(undo.id) { - self.apply_undo(&undo)?; - self.version.observe(undo.id); - self.lamport_clock.observe(lamport_timestamp); - } - } - Operation::UpdateSelections { - set_id, - selections, - lamport_timestamp, - } => { - if let Some(selections) = selections { - if let Some(set) = self.selections.get_mut(&set_id) { - set.selections = selections; - } else { - self.selections.insert( - set_id, - SelectionSet { - selections, - active: false, - }, - ); - } - } else { - self.selections.remove(&set_id); - } - self.lamport_clock.observe(lamport_timestamp); - } - Operation::SetActiveSelections { - set_id, - lamport_timestamp, - } => { - for (id, set) in &mut self.selections { - if id.replica_id == lamport_timestamp.replica_id { - if Some(*id) == set_id { - set.active = true; - } else { - set.active = false; - } - } - } - self.lamport_clock.observe(lamport_timestamp); - } - #[cfg(test)] - Operation::Test(_) => {} - } - Ok(()) - } - - fn apply_remote_edit( - &mut self, - version: &clock::Global, - ranges: &[Range], - new_text: Option<&str>, - timestamp: InsertionTimestamp, - ) { - if ranges.is_empty() { - return; - } - - let cx = Some(version.clone()); - let mut new_ropes = - RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); - let mut old_fragments = self.fragments.cursor::(); - let mut new_fragments = - old_fragments.slice(&VersionedOffset::Offset(ranges[0].start), Bias::Left, &cx); - new_ropes.push_tree(new_fragments.summary().text); - - let mut fragment_start = old_fragments.start().offset(); - for range in ranges { - let fragment_end = old_fragments.end(&cx).offset(); - - // If the current fragment ends before this range, then jump ahead to the first fragment - // that extends past the start of this range, reusing any intervening fragments. - if fragment_end < range.start { - // If the current fragment has been partially consumed, then consume the rest of it - // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().offset() { - if fragment_end > fragment_start { - let mut suffix = old_fragments.item().unwrap().clone(); - suffix.len = fragment_end - fragment_start; - new_ropes.push_fragment(&suffix, suffix.visible); - new_fragments.push(suffix, &None); - } - old_fragments.next(&cx); - } - - let slice = - old_fragments.slice(&VersionedOffset::Offset(range.start), Bias::Left, &cx); - new_ropes.push_tree(slice.summary().text); - new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().offset(); - } - - // If we are at the end of a non-concurrent fragment, advance to the next one. - let fragment_end = old_fragments.end(&cx).offset(); - if fragment_end == range.start && fragment_end > fragment_start { - let mut fragment = old_fragments.item().unwrap().clone(); - fragment.len = fragment_end - fragment_start; - new_ropes.push_fragment(&fragment, fragment.visible); - new_fragments.push(fragment, &None); - old_fragments.next(&cx); - fragment_start = old_fragments.start().offset(); - } - - // Skip over insertions that are concurrent to this edit, but have a lower lamport - // timestamp. - while let Some(fragment) = old_fragments.item() { - if fragment_start == range.start - && fragment.timestamp.lamport() > timestamp.lamport() - { - new_ropes.push_fragment(fragment, fragment.visible); - new_fragments.push(fragment.clone(), &None); - old_fragments.next(&cx); - debug_assert_eq!(fragment_start, range.start); - } else { - break; - } - } - debug_assert!(fragment_start <= range.start); - - // Preserve any portion of the current fragment that precedes this range. - if fragment_start < range.start { - let mut prefix = old_fragments.item().unwrap().clone(); - prefix.len = range.start - fragment_start; - fragment_start = range.start; - new_ropes.push_fragment(&prefix, prefix.visible); - new_fragments.push(prefix, &None); - } - - // Insert the new text before any existing fragments within the range. - if let Some(new_text) = new_text { - new_ropes.push_str(new_text); - new_fragments.push( - Fragment { - timestamp, - len: new_text.len(), - deletions: Default::default(), - max_undos: Default::default(), - visible: true, - }, - &None, - ); - } - - // Advance through every fragment that intersects this range, marking the intersecting - // portions as deleted. - while fragment_start < range.end { - let fragment = old_fragments.item().unwrap(); - let fragment_end = old_fragments.end(&cx).offset(); - let mut intersection = fragment.clone(); - let intersection_end = cmp::min(range.end, fragment_end); - if fragment.was_visible(version, &self.undo_map) { - intersection.len = intersection_end - fragment_start; - intersection.deletions.insert(timestamp.local()); - intersection.visible = false; - } - if intersection.len > 0 { - new_ropes.push_fragment(&intersection, fragment.visible); - new_fragments.push(intersection, &None); - fragment_start = intersection_end; - } - if fragment_end <= range.end { - old_fragments.next(&cx); - } - } - } - - // If the current fragment has been partially consumed, then consume the rest of it - // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().offset() { - let fragment_end = old_fragments.end(&cx).offset(); - if fragment_end > fragment_start { - let mut suffix = old_fragments.item().unwrap().clone(); - suffix.len = fragment_end - fragment_start; - new_ropes.push_fragment(&suffix, suffix.visible); - new_fragments.push(suffix, &None); - } - old_fragments.next(&cx); - } - - let suffix = old_fragments.suffix(&cx); - new_ropes.push_tree(suffix.summary().text); - new_fragments.push_tree(suffix, &None); - let (visible_text, deleted_text) = new_ropes.finish(); - drop(old_fragments); - - self.fragments = new_fragments; - self.visible_text = visible_text; - self.deleted_text = deleted_text; - self.local_clock.observe(timestamp.local()); - self.lamport_clock.observe(timestamp.lamport()); - } - #[cfg(not(test))] pub fn send_operation(&mut self, operation: Operation, cx: &mut ModelContext) { if let Some(file) = &self.file { @@ -1962,8 +2377,7 @@ impl Buffer { } pub fn remove_peer(&mut self, replica_id: ReplicaId, cx: &mut ModelContext) { - self.selections - .retain(|set_id, _| set_id.replica_id != replica_id); + self.buffer.remove_peer(replica_id); cx.notify(); } @@ -1971,12 +2385,8 @@ impl Buffer { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); - if let Some(transaction) = self.history.pop_undo().cloned() { - let selections = transaction.selections_before.clone(); - self.undo_or_redo(transaction, cx).unwrap(); - for (set_id, selections) in selections { - let _ = self.update_selection_set(set_id, selections, cx); - } + for operation in self.buffer.undo() { + self.send_operation(operation, cx); } cx.notify(); @@ -1990,12 +2400,8 @@ impl Buffer { let was_dirty = self.is_dirty(); let old_version = self.version.clone(); - if let Some(transaction) = self.history.pop_redo().cloned() { - let selections = transaction.selections_after.clone(); - self.undo_or_redo(transaction, cx).unwrap(); - for (set_id, selections) in selections { - let _ = self.update_selection_set(set_id, selections, cx); - } + for operation in self.buffer.redo() { + self.send_operation(operation, cx); } cx.notify(); @@ -2004,306 +2410,36 @@ impl Buffer { self.reparse(cx); } } - - fn undo_or_redo( - &mut self, - transaction: Transaction, - cx: &mut ModelContext, - ) -> Result<()> { - let mut counts = HashMap::default(); - for edit_id in transaction.edits { - counts.insert(edit_id, self.undo_map.undo_count(edit_id) + 1); - } - - let undo = UndoOperation { - id: self.local_clock.tick(), - counts, - ranges: transaction.ranges, - version: transaction.start.clone(), - }; - self.apply_undo(&undo)?; - self.version.observe(undo.id); - - let operation = Operation::Undo { - undo, - lamport_timestamp: self.lamport_clock.tick(), - }; - self.send_operation(operation, cx); - - Ok(()) - } - - fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { - self.undo_map.insert(undo); - - let mut cx = undo.version.clone(); - for edit_id in undo.counts.keys().copied() { - cx.observe(edit_id); - } - let cx = Some(cx); - - let mut old_fragments = self.fragments.cursor::(); - let mut new_fragments = old_fragments.slice( - &VersionedOffset::Offset(undo.ranges[0].start), - Bias::Right, - &cx, - ); - let mut new_ropes = - RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); - new_ropes.push_tree(new_fragments.summary().text); - - for range in &undo.ranges { - let mut end_offset = old_fragments.end(&cx).offset(); - - if end_offset < range.start { - let preceding_fragments = - old_fragments.slice(&VersionedOffset::Offset(range.start), Bias::Right, &cx); - new_ropes.push_tree(preceding_fragments.summary().text); - new_fragments.push_tree(preceding_fragments, &None); - } - - while end_offset <= range.end { - if let Some(fragment) = old_fragments.item() { - let mut fragment = fragment.clone(); - let fragment_was_visible = fragment.visible; - - if fragment.was_visible(&undo.version, &self.undo_map) - || undo.counts.contains_key(&fragment.timestamp.local()) - { - 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, &None); - - old_fragments.next(&cx); - if end_offset == old_fragments.end(&cx).offset() { - let unseen_fragments = old_fragments.slice( - &VersionedOffset::Offset(end_offset), - Bias::Right, - &cx, - ); - new_ropes.push_tree(unseen_fragments.summary().text); - new_fragments.push_tree(unseen_fragments, &None); - } - end_offset = old_fragments.end(&cx).offset(); - } else { - break; - } - } - } - - let suffix = old_fragments.suffix(&cx); - new_ropes.push_tree(suffix.summary().text); - new_fragments.push_tree(suffix, &None); - - drop(old_fragments); - let (visible_text, deleted_text) = new_ropes.finish(); - self.fragments = new_fragments; - self.visible_text = visible_text; - self.deleted_text = deleted_text; - Ok(()) - } - - fn flush_deferred_ops(&mut self) -> Result<()> { - self.deferred_replicas.clear(); - let mut deferred_ops = Vec::new(); - for op in self.deferred_ops.drain().cursor().cloned() { - if self.can_apply_op(&op) { - self.apply_op(op)?; - } else { - self.deferred_replicas.insert(op.replica_id()); - deferred_ops.push(op); - } - } - self.deferred_ops.insert(deferred_ops); - Ok(()) - } - - fn can_apply_op(&self, op: &Operation) -> bool { - if self.deferred_replicas.contains(&op.replica_id()) { - false - } else { - match op { - Operation::Edit(edit) => self.version >= edit.version, - Operation::Undo { undo, .. } => self.version >= undo.version, - Operation::UpdateSelections { selections, .. } => { - if let Some(selections) = selections { - selections.iter().all(|selection| { - let contains_start = self.version >= selection.start.version; - let contains_end = self.version >= selection.end.version; - contains_start && contains_end - }) - } else { - true - } - } - Operation::SetActiveSelections { set_id, .. } => { - set_id.map_or(true, |set_id| self.selections.contains_key(&set_id)) - } - #[cfg(test)] - Operation::Test(_) => true, - } - } - } - - fn apply_local_edit( - &mut self, - ranges: &[Range], - new_text: Option, - timestamp: InsertionTimestamp, - ) -> EditOperation { - let mut edit = EditOperation { - timestamp, - version: self.version(), - ranges: Vec::with_capacity(ranges.len()), - new_text: None, - }; - - let mut new_ropes = - RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0)); - let mut old_fragments = self.fragments.cursor::(); - let mut new_fragments = old_fragments.slice(&ranges[0].start, Bias::Right, &None); - new_ropes.push_tree(new_fragments.summary().text); - - let mut fragment_start = old_fragments.start().visible; - for range in ranges { - let fragment_end = old_fragments.end(&None).visible; - - // If the current fragment ends before this range, then jump ahead to the first fragment - // that extends past the start of this range, reusing any intervening fragments. - if fragment_end < range.start { - // If the current fragment has been partially consumed, then consume the rest of it - // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().visible { - if fragment_end > fragment_start { - let mut suffix = old_fragments.item().unwrap().clone(); - suffix.len = fragment_end - fragment_start; - new_ropes.push_fragment(&suffix, suffix.visible); - new_fragments.push(suffix, &None); - } - old_fragments.next(&None); - } - - let slice = old_fragments.slice(&range.start, Bias::Right, &None); - new_ropes.push_tree(slice.summary().text); - new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().visible; - } - - let full_range_start = range.start + old_fragments.start().deleted; - - // Preserve any portion of the current fragment that precedes this range. - if fragment_start < range.start { - let mut prefix = old_fragments.item().unwrap().clone(); - prefix.len = range.start - fragment_start; - new_ropes.push_fragment(&prefix, prefix.visible); - new_fragments.push(prefix, &None); - fragment_start = range.start; - } - - // Insert the new text before any existing fragments within the range. - if let Some(new_text) = new_text.as_deref() { - new_ropes.push_str(new_text); - new_fragments.push( - Fragment { - timestamp, - len: new_text.len(), - deletions: Default::default(), - max_undos: Default::default(), - visible: true, - }, - &None, - ); - } - - // Advance through every fragment that intersects this range, marking the intersecting - // portions as deleted. - while fragment_start < range.end { - let fragment = old_fragments.item().unwrap(); - let fragment_end = old_fragments.end(&None).visible; - let mut intersection = fragment.clone(); - let intersection_end = cmp::min(range.end, fragment_end); - if fragment.visible { - intersection.len = intersection_end - fragment_start; - intersection.deletions.insert(timestamp.local()); - intersection.visible = false; - } - if intersection.len > 0 { - new_ropes.push_fragment(&intersection, fragment.visible); - new_fragments.push(intersection, &None); - fragment_start = intersection_end; - } - if fragment_end <= range.end { - old_fragments.next(&None); - } - } - - let full_range_end = range.end + old_fragments.start().deleted; - edit.ranges.push(full_range_start..full_range_end); - } - - // If the current fragment has been partially consumed, then consume the rest of it - // and advance to the next fragment before slicing. - if fragment_start > old_fragments.start().visible { - let fragment_end = old_fragments.end(&None).visible; - if fragment_end > fragment_start { - let mut suffix = old_fragments.item().unwrap().clone(); - suffix.len = fragment_end - fragment_start; - new_ropes.push_fragment(&suffix, suffix.visible); - new_fragments.push(suffix, &None); - } - old_fragments.next(&None); - } - - let suffix = old_fragments.suffix(&None); - new_ropes.push_tree(suffix.summary().text); - new_fragments.push_tree(suffix, &None); - let (visible_text, deleted_text) = new_ropes.finish(); - drop(old_fragments); - - self.fragments = new_fragments; - self.visible_text = visible_text; - self.deleted_text = deleted_text; - edit.new_text = new_text; - edit - } - - fn content<'a>(&'a self) -> Content<'a> { - self.into() - } - - pub fn text_summary_for_range(&self, range: Range) -> TextSummary { - self.content().text_summary_for_range(range) - } - - pub fn anchor_before(&self, position: T) -> Anchor { - self.anchor_at(position, Bias::Left) - } - - pub fn anchor_after(&self, position: T) -> Anchor { - self.anchor_at(position, Bias::Right) - } - - pub fn anchor_at(&self, position: T, bias: Bias) -> Anchor { - self.content().anchor_at(position, bias) - } - - pub fn point_for_offset(&self, offset: usize) -> Result { - self.content().point_for_offset(offset) - } - - pub fn clip_point(&self, point: Point, bias: Bias) -> Point { - self.visible_text.clip_point(point, bias) - } - - pub fn clip_offset(&self, offset: usize, bias: Bias) -> usize { - self.visible_text.clip_offset(offset, bias) - } } #[cfg(any(test, feature = "test-support"))] impl Buffer { + pub fn randomly_edit( + &mut self, + rng: &mut T, + old_range_count: usize, + _: &mut ModelContext, + ) -> (Vec>, String) + where + T: rand::Rng, + { + self.buffer.randomly_edit(rng, old_range_count) + } + + pub fn randomly_mutate( + &mut self, + rng: &mut T, + _: &mut ModelContext, + ) -> (Vec>, String) + where + T: rand::Rng, + { + self.buffer.randomly_mutate(rng) + } +} + +#[cfg(any(test, feature = "test-support"))] +impl TextBuffer { fn random_byte_range(&mut self, start_offset: usize, rng: &mut impl rand::Rng) -> Range { let end = self.clip_offset(rng.gen_range(start_offset..=self.len()), Bias::Right); let start = self.clip_offset(rng.gen_range(start_offset..=end), Bias::Right); @@ -2314,7 +2450,6 @@ impl Buffer { &mut self, rng: &mut T, old_range_count: usize, - cx: &mut ModelContext, ) -> (Vec>, String) where T: rand::Rng, @@ -2337,21 +2472,17 @@ impl Buffer { old_ranges, new_text ); - self.edit(old_ranges.iter().cloned(), new_text.as_str(), cx); + self.edit(old_ranges.iter().cloned(), new_text.as_str()); (old_ranges, new_text) } - pub fn randomly_mutate( - &mut self, - rng: &mut T, - cx: &mut ModelContext, - ) -> (Vec>, String) + pub fn randomly_mutate(&mut self, rng: &mut T) -> (Vec>, String) where T: rand::Rng, { use rand::prelude::*; - let (old_ranges, new_text) = self.randomly_edit(rng, 5, cx); + let (old_ranges, new_text) = self.randomly_edit(rng, 5); // Randomly add, remove or mutate selection sets. let replica_selection_sets = &self @@ -2361,7 +2492,7 @@ impl Buffer { .collect::>(); let set_id = replica_selection_sets.choose(rng); if set_id.is_some() && rng.gen_bool(1.0 / 6.0) { - self.remove_selection_set(*set_id.unwrap(), cx).unwrap(); + self.remove_selection_set(*set_id.unwrap()).unwrap(); } else { let mut ranges = Vec::new(); for _ in 0..5 { @@ -2370,9 +2501,9 @@ impl Buffer { let new_selections = self.selections_from_ranges(ranges).unwrap(); if set_id.is_none() || rng.gen_bool(1.0 / 5.0) { - self.add_selection_set(new_selections, cx); + self.add_selection_set(new_selections); } else { - self.update_selection_set(*set_id.unwrap(), new_selections, cx) + self.update_selection_set(*set_id.unwrap(), new_selections) .unwrap(); } } @@ -2380,7 +2511,7 @@ impl Buffer { (old_ranges, new_text) } - pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng, cx: &mut ModelContext) { + pub fn randomly_undo_redo(&mut self, rng: &mut impl rand::Rng) { use rand::prelude::*; for _ in 0..rng.gen_range(1..=5) { @@ -2390,7 +2521,7 @@ impl Buffer { self.replica_id, transaction ); - self.undo_or_redo(transaction, cx).unwrap(); + self.undo_or_redo(transaction).unwrap(); } } } @@ -2453,33 +2584,14 @@ impl Buffer { .keys() .map(move |set_id| (*set_id, self.selection_ranges(*set_id).unwrap())) } - - pub fn enclosing_bracket_point_ranges( - &self, - range: Range, - ) -> Option<(Range, Range)> { - self.enclosing_bracket_ranges(range).map(|(start, end)| { - let point_start = start.start.to_point(self)..start.end.to_point(self); - let point_end = end.start.to_point(self)..end.end.to_point(self); - (point_start, point_end) - }) - } } 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(), - version: self.version.clone(), + buffer: self.buffer.clone(), saved_version: self.saved_version.clone(), saved_mtime: self.saved_mtime, - last_edit: self.last_edit.clone(), - undo_map: self.undo_map.clone(), - history: self.history.clone(), - selections: self.selections.clone(), - deferred_ops: self.deferred_ops.clone(), file: self.file.as_ref().map(|f| f.boxed_clone()), language: self.language.clone(), syntax_tree: Mutex::new(self.syntax_tree.lock().clone()), @@ -2488,11 +2600,6 @@ impl Clone for Buffer { parse_count: self.parse_count, autoindent_requests: Default::default(), pending_autoindent: Default::default(), - deferred_replicas: self.deferred_replicas.clone(), - replica_id: self.replica_id, - remote_id: self.remote_id.clone(), - local_clock: self.local_clock.clone(), - lamport_clock: self.lamport_clock.clone(), #[cfg(test)] operations: self.operations.clone(), @@ -2777,6 +2884,26 @@ impl<'a> From<&'a mut Buffer> for Content<'a> { } } +impl<'a> From<&'a TextBuffer> for Content<'a> { + fn from(buffer: &'a TextBuffer) -> Self { + Self { + visible_text: &buffer.visible_text, + fragments: &buffer.fragments, + version: &buffer.version, + } + } +} + +impl<'a> From<&'a mut TextBuffer> for Content<'a> { + fn from(buffer: &'a mut TextBuffer) -> Self { + Self { + visible_text: &buffer.visible_text, + fragments: &buffer.fragments, + version: &buffer.version, + } + } +} + impl<'a> From<&'a Content<'a>> for Content<'a> { fn from(content: &'a Content) -> Self { Self { diff --git a/crates/buffer/src/tests/buffer.rs b/crates/buffer/src/tests/buffer.rs index 7c627a45ed..74c27e99de 100644 --- a/crates/buffer/src/tests/buffer.rs +++ b/crates/buffer/src/tests/buffer.rs @@ -11,23 +11,20 @@ use std::{ time::{Duration, Instant}, }; -#[gpui::test] -fn test_edit(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "abc", cx); - assert_eq!(buffer.text(), "abc"); - buffer.edit(vec![3..3], "def", cx); - assert_eq!(buffer.text(), "abcdef"); - buffer.edit(vec![0..0], "ghi", cx); - assert_eq!(buffer.text(), "ghiabcdef"); - buffer.edit(vec![5..5], "jkl", cx); - assert_eq!(buffer.text(), "ghiabjklcdef"); - buffer.edit(vec![6..7], "", cx); - assert_eq!(buffer.text(), "ghiabjlcdef"); - buffer.edit(vec![4..9], "mno", cx); - assert_eq!(buffer.text(), "ghiamnoef"); - buffer - }); +#[test] +fn test_edit() { + let mut buffer = TextBuffer::new(0, 0, History::new("abc".into())); + assert_eq!(buffer.text(), "abc"); + buffer.edit(vec![3..3], "def"); + assert_eq!(buffer.text(), "abcdef"); + buffer.edit(vec![0..0], "ghi"); + assert_eq!(buffer.text(), "ghiabcdef"); + buffer.edit(vec![5..5], "jkl"); + assert_eq!(buffer.text(), "ghiabjklcdef"); + buffer.edit(vec![6..7], ""); + assert_eq!(buffer.text(), "ghiabjlcdef"); + buffer.edit(vec![4..9], "mno"); + assert_eq!(buffer.text(), "ghiamnoef"); } #[gpui::test] @@ -88,7 +85,7 @@ fn test_edit_events(cx: &mut gpui::MutableAppContext) { } #[gpui::test(iterations = 100)] -fn test_random_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { +fn test_random_edits(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -97,360 +94,336 @@ fn test_random_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let mut reference_string = RandomCharIter::new(&mut rng) .take(reference_string_len) .collect::(); - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, reference_string.as_str(), cx); - buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); - let mut buffer_versions = Vec::new(); + let mut buffer = TextBuffer::new(0, 0, History::new(reference_string.clone().into())); + buffer.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); + let mut buffer_versions = Vec::new(); + log::info!( + "buffer text {:?}, version: {:?}", + buffer.text(), + buffer.version() + ); + + for _i in 0..operations { + let (old_ranges, new_text) = buffer.randomly_mutate(&mut rng); + for old_range in old_ranges.iter().rev() { + reference_string.replace_range(old_range.clone(), &new_text); + } + assert_eq!(buffer.text(), reference_string); log::info!( "buffer text {:?}, version: {:?}", buffer.text(), buffer.version() ); - for _i in 0..operations { - let (old_ranges, new_text) = buffer.randomly_mutate(&mut rng, cx); - for old_range in old_ranges.iter().rev() { - reference_string.replace_range(old_range.clone(), &new_text); - } - assert_eq!(buffer.text(), reference_string); + if rng.gen_bool(0.25) { + buffer.randomly_undo_redo(&mut rng); + reference_string = buffer.text(); log::info!( "buffer text {:?}, version: {:?}", buffer.text(), buffer.version() ); - - if rng.gen_bool(0.25) { - buffer.randomly_undo_redo(&mut rng, cx); - reference_string = buffer.text(); - log::info!( - "buffer text {:?}, version: {:?}", - buffer.text(), - buffer.version() - ); - } - - let range = buffer.random_byte_range(0, &mut rng); - assert_eq!( - buffer.text_summary_for_range(range.clone()), - TextSummary::from(&reference_string[range]) - ); - - if rng.gen_bool(0.3) { - buffer_versions.push(buffer.clone()); - } } - for mut old_buffer in buffer_versions { - let edits = buffer - .edits_since(old_buffer.version.clone()) - .collect::>(); + let range = buffer.random_byte_range(0, &mut rng); + assert_eq!( + buffer.text_summary_for_range(range.clone()), + TextSummary::from(&reference_string[range]) + ); - log::info!( - "mutating old buffer version {:?}, text: {:?}, edits since: {:?}", - old_buffer.version(), - old_buffer.text(), - edits, - ); - - let mut delta = 0_isize; - for edit in edits { - let old_start = (edit.old_bytes.start as isize + delta) as usize; - let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect(); - old_buffer.edit( - Some(old_start..old_start + edit.deleted_bytes()), - new_text, - cx, - ); - delta += edit.delta(); - } - assert_eq!(old_buffer.text(), buffer.text()); + if rng.gen_bool(0.3) { + buffer_versions.push(buffer.clone()); } + } - buffer - }); + for mut old_buffer in buffer_versions { + let edits = buffer + .edits_since(old_buffer.version.clone()) + .collect::>(); + + log::info!( + "mutating old buffer version {:?}, text: {:?}, edits since: {:?}", + old_buffer.version(), + old_buffer.text(), + edits, + ); + + let mut delta = 0_isize; + for edit in edits { + let old_start = (edit.old_bytes.start as isize + delta) as usize; + let new_text: String = buffer.text_for_range(edit.new_bytes.clone()).collect(); + old_buffer.edit(Some(old_start..old_start + edit.deleted_bytes()), new_text); + delta += edit.delta(); + } + assert_eq!(old_buffer.text(), buffer.text()); + } } -#[gpui::test] -fn test_line_len(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "abcd\nefg\nhij", cx); - buffer.edit(vec![12..12], "kl\nmno", cx); - buffer.edit(vec![18..18], "\npqrs\n", cx); - buffer.edit(vec![18..21], "\nPQ", cx); +#[test] +fn test_line_len() { + let mut buffer = TextBuffer::new(0, 0, History::new("".into())); + buffer.edit(vec![0..0], "abcd\nefg\nhij"); + buffer.edit(vec![12..12], "kl\nmno"); + buffer.edit(vec![18..18], "\npqrs\n"); + buffer.edit(vec![18..21], "\nPQ"); - assert_eq!(buffer.line_len(0), 4); - assert_eq!(buffer.line_len(1), 3); - assert_eq!(buffer.line_len(2), 5); - assert_eq!(buffer.line_len(3), 3); - assert_eq!(buffer.line_len(4), 4); - assert_eq!(buffer.line_len(5), 0); - buffer - }); + assert_eq!(buffer.line_len(0), 4); + assert_eq!(buffer.line_len(1), 3); + assert_eq!(buffer.line_len(2), 5); + assert_eq!(buffer.line_len(3), 3); + assert_eq!(buffer.line_len(4), 4); + assert_eq!(buffer.line_len(5), 0); } -#[gpui::test] -fn test_text_summary_for_range(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let buffer = Buffer::new(0, "ab\nefg\nhklm\nnopqrs\ntuvwxyz", cx); - assert_eq!( - buffer.text_summary_for_range(1..3), - TextSummary { - bytes: 2, - lines: Point::new(1, 0), - first_line_chars: 1, - last_line_chars: 0, - longest_row: 0, - longest_row_chars: 1, - } - ); - assert_eq!( - buffer.text_summary_for_range(1..12), - TextSummary { - bytes: 11, - lines: Point::new(3, 0), - first_line_chars: 1, - last_line_chars: 0, - longest_row: 2, - longest_row_chars: 4, - } - ); - assert_eq!( - buffer.text_summary_for_range(0..20), - TextSummary { - bytes: 20, - lines: Point::new(4, 1), - first_line_chars: 2, - last_line_chars: 1, - longest_row: 3, - longest_row_chars: 6, - } - ); - assert_eq!( - buffer.text_summary_for_range(0..22), - TextSummary { - bytes: 22, - lines: Point::new(4, 3), - first_line_chars: 2, - last_line_chars: 3, - longest_row: 3, - longest_row_chars: 6, - } - ); - assert_eq!( - buffer.text_summary_for_range(7..22), - TextSummary { - bytes: 15, - lines: Point::new(2, 3), - first_line_chars: 4, - last_line_chars: 3, - longest_row: 1, - longest_row_chars: 6, - } - ); - buffer - }); +#[test] +fn test_text_summary_for_range() { + let buffer = TextBuffer::new(0, 0, History::new("ab\nefg\nhklm\nnopqrs\ntuvwxyz".into())); + assert_eq!( + buffer.text_summary_for_range(1..3), + TextSummary { + bytes: 2, + lines: Point::new(1, 0), + first_line_chars: 1, + last_line_chars: 0, + longest_row: 0, + longest_row_chars: 1, + } + ); + assert_eq!( + buffer.text_summary_for_range(1..12), + TextSummary { + bytes: 11, + lines: Point::new(3, 0), + first_line_chars: 1, + last_line_chars: 0, + longest_row: 2, + longest_row_chars: 4, + } + ); + assert_eq!( + buffer.text_summary_for_range(0..20), + TextSummary { + bytes: 20, + lines: Point::new(4, 1), + first_line_chars: 2, + last_line_chars: 1, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range(0..22), + TextSummary { + bytes: 22, + lines: Point::new(4, 3), + first_line_chars: 2, + last_line_chars: 3, + longest_row: 3, + longest_row_chars: 6, + } + ); + assert_eq!( + buffer.text_summary_for_range(7..22), + TextSummary { + bytes: 15, + lines: Point::new(2, 3), + first_line_chars: 4, + last_line_chars: 3, + longest_row: 1, + longest_row_chars: 6, + } + ); } -#[gpui::test] -fn test_chars_at(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "abcd\nefgh\nij", cx); - buffer.edit(vec![12..12], "kl\nmno", cx); - buffer.edit(vec![18..18], "\npqrs", cx); - buffer.edit(vec![18..21], "\nPQ", cx); +#[test] +fn test_chars_at() { + let mut buffer = TextBuffer::new(0, 0, History::new("".into())); + buffer.edit(vec![0..0], "abcd\nefgh\nij"); + buffer.edit(vec![12..12], "kl\nmno"); + buffer.edit(vec![18..18], "\npqrs"); + buffer.edit(vec![18..21], "\nPQ"); - let chars = buffer.chars_at(Point::new(0, 0)); - assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); + let chars = buffer.chars_at(Point::new(0, 0)); + assert_eq!(chars.collect::(), "abcd\nefgh\nijkl\nmno\nPQrs"); - let chars = buffer.chars_at(Point::new(1, 0)); - assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); + let chars = buffer.chars_at(Point::new(1, 0)); + assert_eq!(chars.collect::(), "efgh\nijkl\nmno\nPQrs"); - let chars = buffer.chars_at(Point::new(2, 0)); - assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); + let chars = buffer.chars_at(Point::new(2, 0)); + assert_eq!(chars.collect::(), "ijkl\nmno\nPQrs"); - let chars = buffer.chars_at(Point::new(3, 0)); - assert_eq!(chars.collect::(), "mno\nPQrs"); + let chars = buffer.chars_at(Point::new(3, 0)); + assert_eq!(chars.collect::(), "mno\nPQrs"); - let chars = buffer.chars_at(Point::new(4, 0)); - assert_eq!(chars.collect::(), "PQrs"); + let chars = buffer.chars_at(Point::new(4, 0)); + assert_eq!(chars.collect::(), "PQrs"); - // Regression test: - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n", cx); - buffer.edit(vec![60..60], "\n", cx); + // Regression test: + let mut buffer = TextBuffer::new(0, 0, History::new("".into())); + buffer.edit(vec![0..0], "[workspace]\nmembers = [\n \"xray_core\",\n \"xray_server\",\n \"xray_cli\",\n \"xray_wasm\",\n]\n"); + buffer.edit(vec![60..60], "\n"); - let chars = buffer.chars_at(Point::new(6, 0)); - assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); - - buffer - }); + let chars = buffer.chars_at(Point::new(6, 0)); + assert_eq!(chars.collect::(), " \"xray_wasm\",\n]\n"); } -#[gpui::test] -fn test_anchors(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - buffer.edit(vec![0..0], "abc", cx); - let left_anchor = buffer.anchor_before(2); - let right_anchor = buffer.anchor_after(2); +#[test] +fn test_anchors() { + let mut buffer = TextBuffer::new(0, 0, History::new("".into())); + buffer.edit(vec![0..0], "abc"); + let left_anchor = buffer.anchor_before(2); + let right_anchor = buffer.anchor_after(2); - buffer.edit(vec![1..1], "def\n", cx); - assert_eq!(buffer.text(), "adef\nbc"); - assert_eq!(left_anchor.to_offset(&buffer), 6); - assert_eq!(right_anchor.to_offset(&buffer), 6); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + buffer.edit(vec![1..1], "def\n"); + assert_eq!(buffer.text(), "adef\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 6); + assert_eq!(right_anchor.to_offset(&buffer), 6); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - buffer.edit(vec![2..3], "", cx); - assert_eq!(buffer.text(), "adf\nbc"); - assert_eq!(left_anchor.to_offset(&buffer), 5); - assert_eq!(right_anchor.to_offset(&buffer), 5); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + buffer.edit(vec![2..3], ""); + assert_eq!(buffer.text(), "adf\nbc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 5); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - buffer.edit(vec![5..5], "ghi\n", cx); - assert_eq!(buffer.text(), "adf\nbghi\nc"); - assert_eq!(left_anchor.to_offset(&buffer), 5); - assert_eq!(right_anchor.to_offset(&buffer), 9); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); + buffer.edit(vec![5..5], "ghi\n"); + assert_eq!(buffer.text(), "adf\nbghi\nc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 9); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 }); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 2, column: 0 }); - buffer.edit(vec![7..9], "", cx); - assert_eq!(buffer.text(), "adf\nbghc"); - assert_eq!(left_anchor.to_offset(&buffer), 5); - assert_eq!(right_anchor.to_offset(&buffer), 7); - assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); - assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); + buffer.edit(vec![7..9], ""); + assert_eq!(buffer.text(), "adf\nbghc"); + assert_eq!(left_anchor.to_offset(&buffer), 5); + assert_eq!(right_anchor.to_offset(&buffer), 7); + assert_eq!(left_anchor.to_point(&buffer), Point { row: 1, column: 1 },); + assert_eq!(right_anchor.to_point(&buffer), Point { row: 1, column: 3 }); - // Ensure anchoring to a point is equivalent to anchoring to an offset. - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 0 }), - buffer.anchor_before(0) - ); - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 1 }), - buffer.anchor_before(1) - ); - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 2 }), - buffer.anchor_before(2) - ); - assert_eq!( - buffer.anchor_before(Point { row: 0, column: 3 }), - buffer.anchor_before(3) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 0 }), - buffer.anchor_before(4) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 1 }), - buffer.anchor_before(5) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 2 }), - buffer.anchor_before(6) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 3 }), - buffer.anchor_before(7) - ); - assert_eq!( - buffer.anchor_before(Point { row: 1, column: 4 }), - buffer.anchor_before(8) - ); + // Ensure anchoring to a point is equivalent to anchoring to an offset. + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 0 }), + buffer.anchor_before(0) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 1 }), + buffer.anchor_before(1) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 2 }), + buffer.anchor_before(2) + ); + assert_eq!( + buffer.anchor_before(Point { row: 0, column: 3 }), + buffer.anchor_before(3) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 0 }), + buffer.anchor_before(4) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 1 }), + buffer.anchor_before(5) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 2 }), + buffer.anchor_before(6) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 3 }), + buffer.anchor_before(7) + ); + assert_eq!( + buffer.anchor_before(Point { row: 1, column: 4 }), + buffer.anchor_before(8) + ); - // Comparison between anchors. - let anchor_at_offset_0 = buffer.anchor_before(0); - let anchor_at_offset_1 = buffer.anchor_before(1); - let anchor_at_offset_2 = buffer.anchor_before(2); + // Comparison between anchors. + let anchor_at_offset_0 = buffer.anchor_before(0); + let anchor_at_offset_1 = buffer.anchor_before(1); + let anchor_at_offset_2 = buffer.anchor_before(2); - assert_eq!( - anchor_at_offset_0 - .cmp(&anchor_at_offset_0, &buffer) - .unwrap(), - Ordering::Equal - ); - assert_eq!( - anchor_at_offset_1 - .cmp(&anchor_at_offset_1, &buffer) - .unwrap(), - Ordering::Equal - ); - assert_eq!( - anchor_at_offset_2 - .cmp(&anchor_at_offset_2, &buffer) - .unwrap(), - Ordering::Equal - ); + assert_eq!( + anchor_at_offset_0 + .cmp(&anchor_at_offset_0, &buffer) + .unwrap(), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_1 + .cmp(&anchor_at_offset_1, &buffer) + .unwrap(), + Ordering::Equal + ); + assert_eq!( + anchor_at_offset_2 + .cmp(&anchor_at_offset_2, &buffer) + .unwrap(), + Ordering::Equal + ); - assert_eq!( - anchor_at_offset_0 - .cmp(&anchor_at_offset_1, &buffer) - .unwrap(), - Ordering::Less - ); - assert_eq!( - anchor_at_offset_1 - .cmp(&anchor_at_offset_2, &buffer) - .unwrap(), - Ordering::Less - ); - assert_eq!( - anchor_at_offset_0 - .cmp(&anchor_at_offset_2, &buffer) - .unwrap(), - Ordering::Less - ); + assert_eq!( + anchor_at_offset_0 + .cmp(&anchor_at_offset_1, &buffer) + .unwrap(), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_1 + .cmp(&anchor_at_offset_2, &buffer) + .unwrap(), + Ordering::Less + ); + assert_eq!( + anchor_at_offset_0 + .cmp(&anchor_at_offset_2, &buffer) + .unwrap(), + Ordering::Less + ); - assert_eq!( - anchor_at_offset_1 - .cmp(&anchor_at_offset_0, &buffer) - .unwrap(), - Ordering::Greater - ); - assert_eq!( - anchor_at_offset_2 - .cmp(&anchor_at_offset_1, &buffer) - .unwrap(), - Ordering::Greater - ); - assert_eq!( - anchor_at_offset_2 - .cmp(&anchor_at_offset_0, &buffer) - .unwrap(), - Ordering::Greater - ); - buffer - }); + assert_eq!( + anchor_at_offset_1 + .cmp(&anchor_at_offset_0, &buffer) + .unwrap(), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2 + .cmp(&anchor_at_offset_1, &buffer) + .unwrap(), + Ordering::Greater + ); + assert_eq!( + anchor_at_offset_2 + .cmp(&anchor_at_offset_0, &buffer) + .unwrap(), + Ordering::Greater + ); } -#[gpui::test] -fn test_anchors_at_start_and_end(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "", cx); - let before_start_anchor = buffer.anchor_before(0); - let after_end_anchor = buffer.anchor_after(0); +#[test] +fn test_anchors_at_start_and_end() { + let mut buffer = TextBuffer::new(0, 0, History::new("".into())); + let before_start_anchor = buffer.anchor_before(0); + let after_end_anchor = buffer.anchor_after(0); - buffer.edit(vec![0..0], "abc", cx); - assert_eq!(buffer.text(), "abc"); - assert_eq!(before_start_anchor.to_offset(&buffer), 0); - assert_eq!(after_end_anchor.to_offset(&buffer), 3); + buffer.edit(vec![0..0], "abc"); + assert_eq!(buffer.text(), "abc"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_end_anchor.to_offset(&buffer), 3); - let after_start_anchor = buffer.anchor_after(0); - let before_end_anchor = buffer.anchor_before(3); + let after_start_anchor = buffer.anchor_after(0); + let before_end_anchor = buffer.anchor_before(3); - buffer.edit(vec![3..3], "def", cx); - buffer.edit(vec![0..0], "ghi", cx); - assert_eq!(buffer.text(), "ghiabcdef"); - assert_eq!(before_start_anchor.to_offset(&buffer), 0); - assert_eq!(after_start_anchor.to_offset(&buffer), 3); - assert_eq!(before_end_anchor.to_offset(&buffer), 6); - assert_eq!(after_end_anchor.to_offset(&buffer), 9); - buffer - }); + buffer.edit(vec![3..3], "def"); + buffer.edit(vec![0..0], "ghi"); + assert_eq!(buffer.text(), "ghiabcdef"); + assert_eq!(before_start_anchor.to_offset(&buffer), 0); + assert_eq!(after_start_anchor.to_offset(&buffer), 3); + assert_eq!(before_end_anchor.to_offset(&buffer), 6); + assert_eq!(after_end_anchor.to_offset(&buffer), 9); } #[gpui::test] @@ -469,247 +442,221 @@ async fn test_apply_diff(mut cx: gpui::TestAppContext) { cx.read(|cx| assert_eq!(buffer.read(cx).text(), text)); } -#[gpui::test] -fn test_undo_redo(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut buffer = Buffer::new(0, "1234", cx); - // Set group interval to zero so as to not group edits in the undo stack. - buffer.history.group_interval = Duration::from_secs(0); +#[test] +fn test_undo_redo() { + let mut buffer = TextBuffer::new(0, 0, History::new("1234".into())); + // Set group interval to zero so as to not group edits in the undo stack. + buffer.history.group_interval = Duration::from_secs(0); - buffer.edit(vec![1..1], "abx", cx); - buffer.edit(vec![3..4], "yzef", cx); - buffer.edit(vec![3..5], "cd", cx); - assert_eq!(buffer.text(), "1abcdef234"); + buffer.edit(vec![1..1], "abx"); + buffer.edit(vec![3..4], "yzef"); + buffer.edit(vec![3..5], "cd"); + assert_eq!(buffer.text(), "1abcdef234"); - let transactions = buffer.history.undo_stack.clone(); - assert_eq!(transactions.len(), 3); + let transactions = buffer.history.undo_stack.clone(); + assert_eq!(transactions.len(), 3); - buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1cdef234"); - buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abcdef234"); + buffer.undo_or_redo(transactions[0].clone()).unwrap(); + assert_eq!(buffer.text(), "1cdef234"); + buffer.undo_or_redo(transactions[0].clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); - buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abcdx234"); - buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abx234"); - buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abyzef234"); - buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abcdef234"); + buffer.undo_or_redo(transactions[1].clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdx234"); + buffer.undo_or_redo(transactions[2].clone()).unwrap(); + assert_eq!(buffer.text(), "1abx234"); + buffer.undo_or_redo(transactions[1].clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(transactions[2].clone()).unwrap(); + assert_eq!(buffer.text(), "1abcdef234"); - buffer.undo_or_redo(transactions[2].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1abyzef234"); - buffer.undo_or_redo(transactions[0].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1yzef234"); - buffer.undo_or_redo(transactions[1].clone(), cx).unwrap(); - assert_eq!(buffer.text(), "1234"); - - buffer - }); + buffer.undo_or_redo(transactions[2].clone()).unwrap(); + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(transactions[0].clone()).unwrap(); + assert_eq!(buffer.text(), "1yzef234"); + buffer.undo_or_redo(transactions[1].clone()).unwrap(); + assert_eq!(buffer.text(), "1234"); } -#[gpui::test] -fn test_history(cx: &mut gpui::MutableAppContext) { - cx.add_model(|cx| { - let mut now = Instant::now(); - let mut buffer = Buffer::new(0, "123456", cx); +#[test] +fn test_history() { + let mut now = Instant::now(); + let mut buffer = TextBuffer::new(0, 0, History::new("123456".into())); - let set_id = - buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap(), cx); - buffer.start_transaction_at(Some(set_id), now).unwrap(); - buffer.edit(vec![2..4], "cd", cx); - buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); - assert_eq!(buffer.text(), "12cd56"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); + let set_id = if let Operation::UpdateSelections { set_id, .. } = + buffer.add_selection_set(buffer.selections_from_ranges(vec![4..4]).unwrap()) + { + set_id + } else { + unreachable!() + }; + buffer.start_transaction_at(Some(set_id), now).unwrap(); + buffer.edit(vec![2..4], "cd"); + buffer.end_transaction_at(Some(set_id), now).unwrap(); + assert_eq!(buffer.text(), "12cd56"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); - buffer.start_transaction_at(Some(set_id), now).unwrap(); - buffer - .update_selection_set( - set_id, - buffer.selections_from_ranges(vec![1..3]).unwrap(), - cx, - ) - .unwrap(); - buffer.edit(vec![4..5], "e", cx); - buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); - assert_eq!(buffer.text(), "12cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); + buffer.start_transaction_at(Some(set_id), now).unwrap(); + buffer + .update_selection_set(set_id, buffer.selections_from_ranges(vec![1..3]).unwrap()) + .unwrap(); + buffer.edit(vec![4..5], "e"); + buffer.end_transaction_at(Some(set_id), now).unwrap(); + assert_eq!(buffer.text(), "12cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - now += buffer.history.group_interval + Duration::from_millis(1); - buffer.start_transaction_at(Some(set_id), now).unwrap(); - buffer - .update_selection_set( - set_id, - buffer.selections_from_ranges(vec![2..2]).unwrap(), - cx, - ) - .unwrap(); - buffer.edit(vec![0..1], "a", cx); - buffer.edit(vec![1..1], "b", cx); - buffer.end_transaction_at(Some(set_id), now, cx).unwrap(); - assert_eq!(buffer.text(), "ab2cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); + now += buffer.history.group_interval + Duration::from_millis(1); + buffer.start_transaction_at(Some(set_id), now).unwrap(); + buffer + .update_selection_set(set_id, buffer.selections_from_ranges(vec![2..2]).unwrap()) + .unwrap(); + buffer.edit(vec![0..1], "a"); + buffer.edit(vec![1..1], "b"); + buffer.end_transaction_at(Some(set_id), now).unwrap(); + assert_eq!(buffer.text(), "ab2cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); - // Last transaction happened past the group interval, undo it on its - // own. - buffer.undo(cx); - assert_eq!(buffer.text(), "12cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); + // Last transaction happened past the group interval, undo it on its + // own. + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - // First two transactions happened within the group interval, undo them - // together. - buffer.undo(cx); - assert_eq!(buffer.text(), "123456"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); + // First two transactions happened within the group interval, undo them + // together. + buffer.undo(); + assert_eq!(buffer.text(), "123456"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![4..4]); - // Redo the first two transactions together. - buffer.redo(cx); - assert_eq!(buffer.text(), "12cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); + // Redo the first two transactions together. + buffer.redo(); + assert_eq!(buffer.text(), "12cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![1..3]); - // Redo the last transaction on its own. - buffer.redo(cx); - assert_eq!(buffer.text(), "ab2cde6"); - assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); + // Redo the last transaction on its own. + buffer.redo(); + assert_eq!(buffer.text(), "ab2cde6"); + assert_eq!(buffer.selection_ranges(set_id).unwrap(), vec![3..3]); - buffer.start_transaction_at(None, now).unwrap(); - buffer.end_transaction_at(None, now, cx).unwrap(); - buffer.undo(cx); - assert_eq!(buffer.text(), "12cde6"); - - buffer - }); + buffer.start_transaction_at(None, now).unwrap(); + assert!(buffer.end_transaction_at(None, now).is_none()); + buffer.undo(); + assert_eq!(buffer.text(), "12cde6"); } -#[gpui::test] -fn test_concurrent_edits(cx: &mut gpui::MutableAppContext) { +#[test] +fn test_concurrent_edits() { let text = "abcdef"; - let buffer1 = cx.add_model(|cx| Buffer::new(1, text, cx)); - let buffer2 = cx.add_model(|cx| Buffer::new(2, text, cx)); - let buffer3 = cx.add_model(|cx| Buffer::new(3, text, cx)); + let mut buffer1 = TextBuffer::new(1, 0, History::new(text.into())); + let mut buffer2 = TextBuffer::new(2, 0, History::new(text.into())); + let mut buffer3 = TextBuffer::new(3, 0, History::new(text.into())); - let buf1_op = buffer1.update(cx, |buffer, cx| { - buffer.edit(vec![1..2], "12", cx); - assert_eq!(buffer.text(), "a12cdef"); - buffer.operations.last().unwrap().clone() - }); - let buf2_op = buffer2.update(cx, |buffer, cx| { - buffer.edit(vec![3..4], "34", cx); - assert_eq!(buffer.text(), "abc34ef"); - buffer.operations.last().unwrap().clone() - }); - let buf3_op = buffer3.update(cx, |buffer, cx| { - buffer.edit(vec![5..6], "56", cx); - assert_eq!(buffer.text(), "abcde56"); - buffer.operations.last().unwrap().clone() - }); + let buf1_op = buffer1.edit(vec![1..2], "12"); + assert_eq!(buffer1.text(), "a12cdef"); + let buf2_op = buffer2.edit(vec![3..4], "34"); + assert_eq!(buffer2.text(), "abc34ef"); + let buf3_op = buffer3.edit(vec![5..6], "56"); + assert_eq!(buffer3.text(), "abcde56"); - buffer1.update(cx, |buffer, _| { - buffer.apply_op(buf2_op.clone()).unwrap(); - buffer.apply_op(buf3_op.clone()).unwrap(); - }); - buffer2.update(cx, |buffer, _| { - buffer.apply_op(buf1_op.clone()).unwrap(); - buffer.apply_op(buf3_op.clone()).unwrap(); - }); - buffer3.update(cx, |buffer, _| { - buffer.apply_op(buf1_op.clone()).unwrap(); - buffer.apply_op(buf2_op.clone()).unwrap(); - }); + buffer1.apply_op(Operation::Edit(buf2_op.clone())).unwrap(); + buffer1.apply_op(Operation::Edit(buf3_op.clone())).unwrap(); + buffer2.apply_op(Operation::Edit(buf1_op.clone())).unwrap(); + buffer2.apply_op(Operation::Edit(buf3_op.clone())).unwrap(); + buffer3.apply_op(Operation::Edit(buf1_op.clone())).unwrap(); + buffer3.apply_op(Operation::Edit(buf2_op.clone())).unwrap(); - assert_eq!(buffer1.read(cx).text(), "a12c34e56"); - assert_eq!(buffer2.read(cx).text(), "a12c34e56"); - assert_eq!(buffer3.read(cx).text(), "a12c34e56"); + assert_eq!(buffer1.text(), "a12c34e56"); + assert_eq!(buffer2.text(), "a12c34e56"); + assert_eq!(buffer3.text(), "a12c34e56"); } -#[gpui::test(iterations = 100)] -fn test_random_concurrent_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { - let peers = env::var("PEERS") - .map(|i| i.parse().expect("invalid `PEERS` variable")) - .unwrap_or(5); - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); +// #[gpui::test(iterations = 100)] +// fn test_random_concurrent_edits(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { +// let peers = env::var("PEERS") +// .map(|i| i.parse().expect("invalid `PEERS` variable")) +// .unwrap_or(5); +// let operations = env::var("OPERATIONS") +// .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) +// .unwrap_or(10); - let base_text_len = rng.gen_range(0..10); - let base_text = RandomCharIter::new(&mut rng) - .take(base_text_len) - .collect::(); - let mut replica_ids = Vec::new(); - let mut buffers = Vec::new(); - let mut network = Network::new(rng.clone()); +// let base_text_len = rng.gen_range(0..10); +// let base_text = RandomCharIter::new(&mut rng) +// .take(base_text_len) +// .collect::(); +// let mut replica_ids = Vec::new(); +// let mut buffers = Vec::new(); +// let mut network = Network::new(rng.clone()); - for i in 0..peers { - let buffer = cx.add_model(|cx| { - let mut buf = Buffer::new(i as ReplicaId, base_text.as_str(), cx); - buf.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); - buf - }); - buffers.push(buffer); - replica_ids.push(i as u16); - network.add_peer(i as u16); - } +// for i in 0..peers { +// let buffer = cx.add_model(|cx| { +// let mut buf = Buffer::new(i as ReplicaId, base_text.as_str(), cx); +// buf.history.group_interval = Duration::from_millis(rng.gen_range(0..=200)); +// buf +// }); +// buffers.push(buffer); +// replica_ids.push(i as u16); +// network.add_peer(i as u16); +// } - log::info!("initial text: {:?}", base_text); +// log::info!("initial text: {:?}", base_text); - let mut mutation_count = operations; - loop { - let replica_index = rng.gen_range(0..peers); - let replica_id = replica_ids[replica_index]; - buffers[replica_index].update(cx, |buffer, cx| match rng.gen_range(0..=100) { - 0..=50 if mutation_count != 0 => { - buffer.randomly_mutate(&mut rng, cx); - network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); - log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); - mutation_count -= 1; - } - 51..=70 if mutation_count != 0 => { - buffer.randomly_undo_redo(&mut rng, cx); - network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); - mutation_count -= 1; - } - 71..=100 if network.has_unreceived(replica_id) => { - let ops = network.receive(replica_id); - if !ops.is_empty() { - log::info!( - "peer {} applying {} ops from the network.", - replica_id, - ops.len() - ); - buffer.apply_ops(ops, cx).unwrap(); - } - } - _ => {} - }); +// let mut mutation_count = operations; +// loop { +// let replica_index = rng.gen_range(0..peers); +// let replica_id = replica_ids[replica_index]; +// buffers[replica_index].update(cx, |buffer, cx| match rng.gen_range(0..=100) { +// 0..=50 if mutation_count != 0 => { +// buffer.randomly_mutate(&mut rng, cx); +// network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); +// log::info!("buffer {} text: {:?}", buffer.replica_id, buffer.text()); +// mutation_count -= 1; +// } +// 51..=70 if mutation_count != 0 => { +// buffer.randomly_undo_redo(&mut rng, cx); +// network.broadcast(buffer.replica_id, mem::take(&mut buffer.operations)); +// mutation_count -= 1; +// } +// 71..=100 if network.has_unreceived(replica_id) => { +// let ops = network.receive(replica_id); +// if !ops.is_empty() { +// log::info!( +// "peer {} applying {} ops from the network.", +// replica_id, +// ops.len() +// ); +// buffer.apply_ops(ops, cx).unwrap(); +// } +// } +// _ => {} +// }); - if mutation_count == 0 && network.is_idle() { - break; - } - } +// if mutation_count == 0 && network.is_idle() { +// break; +// } +// } - let first_buffer = buffers[0].read(cx); - for buffer in &buffers[1..] { - let buffer = buffer.read(cx); - assert_eq!( - buffer.text(), - first_buffer.text(), - "Replica {} text != Replica 0 text", - buffer.replica_id - ); - assert_eq!( - buffer.selection_sets().collect::>(), - first_buffer.selection_sets().collect::>() - ); - assert_eq!( - buffer.all_selection_ranges().collect::>(), - first_buffer - .all_selection_ranges() - .collect::>() - ); - } -} +// let first_buffer = buffers[0].read(cx); +// for buffer in &buffers[1..] { +// let buffer = buffer.read(cx); +// assert_eq!( +// buffer.text(), +// first_buffer.text(), +// "Replica {} text != Replica 0 text", +// buffer.replica_id +// ); +// assert_eq!( +// buffer.selection_sets().collect::>(), +// first_buffer.selection_sets().collect::>() +// ); +// assert_eq!( +// buffer.all_selection_ranges().collect::>(), +// first_buffer +// .all_selection_ranges() +// .collect::>() +// ); +// } +// } #[derive(Clone)] struct Envelope { diff --git a/crates/buffer/src/tests/syntax.rs b/crates/buffer/src/tests/syntax.rs index 4b897dd942..e61f8ffd5e 100644 --- a/crates/buffer/src/tests/syntax.rs +++ b/crates/buffer/src/tests/syntax.rs @@ -351,6 +351,19 @@ fn test_contiguous_ranges() { ); } +impl Buffer { + pub fn enclosing_bracket_point_ranges( + &self, + range: Range, + ) -> Option<(Range, Range)> { + self.enclosing_bracket_ranges(range).map(|(start, end)| { + let point_start = start.start.to_point(self)..start.end.to_point(self); + let point_end = end.start.to_point(self)..end.end.to_point(self); + (point_start, point_end) + }) + } +} + fn rust_lang() -> Arc { Arc::new( Language::new( diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d9655d9a9c..209d639407 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -436,7 +436,7 @@ mod tests { } } _ => { - buffer.update(&mut cx, |buffer, cx| buffer.randomly_mutate(&mut rng, cx)); + buffer.update(&mut cx, |buffer, cx| buffer.randomly_edit(&mut rng, 5, cx)); } }