diff --git a/Cargo.lock b/Cargo.lock index 07e3c4e13d..710ad3f14f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1692,6 +1692,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "semver" version = "0.9.0" @@ -2270,6 +2276,7 @@ dependencies = [ "parking_lot", "rand 0.8.3", "rust-embed", + "seahash", "serde_json", "simplelog", "smallvec", diff --git a/zed/Cargo.toml b/zed/Cargo.toml index 61864372cd..720ef00ab8 100644 --- a/zed/Cargo.toml +++ b/zed/Cargo.toml @@ -28,6 +28,7 @@ num_cpus = "1.13.0" parking_lot = "0.11.1" rand = "0.8.3" rust-embed = "5.9.0" +seahash = "4.1" simplelog = "0.9" smallvec = "1.6.1" smol = "1.2.5" diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 973fbf6a2d..827da6be9d 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -5,6 +5,7 @@ mod text; pub use anchor::*; use futures_core::future::LocalBoxFuture; pub use point::*; +use seahash::SeaHasher; pub use text::*; use crate::{ @@ -20,7 +21,7 @@ use lazy_static::lazy_static; use rand::prelude::*; use std::{ cmp::{self, Ordering}, - collections::{HashMap, HashSet}, + hash::BuildHasher, iter::{self, Iterator}, mem, ops::{AddAssign, Range}, @@ -32,13 +33,38 @@ use std::{ pub type SelectionSetId = time::Lamport; pub type SelectionsVersion = usize; +#[derive(Clone, Default)] +struct DeterministicState; + +impl BuildHasher for DeterministicState { + type Hasher = SeaHasher; + + fn build_hasher(&self) -> Self::Hasher { + SeaHasher::new() + } +} + +#[cfg(test)] +type HashMap = std::collections::HashMap; + +#[cfg(test)] +type HashSet = std::collections::HashSet; + +#[cfg(not(test))] +type HashMap = std::collections::HashMap; + +#[cfg(not(test))] +type HashSet = std::collections::HashSet; + pub struct Buffer { file: Option, fragments: SumTree, insertion_splits: HashMap>, + edit_ops: HashMap, pub version: time::Global, saved_version: time::Global, last_edit: time::Local, + undo_map: UndoMap, selections: HashMap>, pub selections_last_update: SelectionsVersion, deferred_ops: OperationQueue, @@ -64,6 +90,42 @@ pub struct Selection { pub reversed: bool, } +#[derive(Clone, Default, Debug)] +struct UndoMap(HashMap>); + +impl UndoMap { + fn insert(&mut self, undo: UndoOperation) { + self.0.entry(undo.edit_id).or_default().push(undo); + } + + fn is_undone(&self, edit_id: time::Local) -> bool { + self.undo_count(edit_id) % 2 == 1 + } + + fn was_undone(&self, edit_id: time::Local, version: &time::Global) -> bool { + let undo_count = self + .0 + .get(&edit_id) + .unwrap_or(&Vec::new()) + .iter() + .filter(|undo| version.observed(undo.id)) + .map(|undo| undo.count) + .max() + .unwrap_or(0); + undo_count % 2 == 1 + } + + fn undo_count(&self, edit_id: time::Local) -> u32 { + self.0 + .get(&edit_id) + .unwrap_or(&Vec::new()) + .iter() + .map(|undo| undo.count) + .max() + .unwrap_or(0) + } +} + #[derive(Clone)] pub struct CharIter<'a> { fragments_cursor: Cursor<'a, Fragment, usize, usize>, @@ -78,6 +140,7 @@ pub struct FragmentIter<'a> { struct Edits<'a, F: Fn(&FragmentSummary) -> bool> { cursor: FilterCursor<'a, F, Fragment, usize>, + undos: &'a UndoMap, since: time::Global, delta: isize, } @@ -114,6 +177,8 @@ struct Fragment { insertion: Insertion, text: Text, deletions: HashSet, + max_undos: time::Global, + visible: bool, } #[derive(Eq, PartialEq, Clone, Debug)] @@ -143,13 +208,11 @@ struct InsertionSplitSummary { #[derive(Clone, Debug, Eq, PartialEq)] pub enum Operation { Edit { - start_id: time::Local, - start_offset: usize, - end_id: time::Local, - end_offset: usize, - version_in_range: time::Global, - new_text: Option, - local_timestamp: time::Local, + edit: EditOperation, + lamport_timestamp: time::Lamport, + }, + Undo { + undo: UndoOperation, lamport_timestamp: time::Lamport, }, UpdateSelections { @@ -159,6 +222,24 @@ pub enum Operation { }, } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct EditOperation { + id: time::Local, + start_id: time::Local, + start_offset: usize, + end_id: time::Local, + end_offset: usize, + version_in_range: time::Global, + new_text: Option, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct UndoOperation { + id: time::Local, + edit_id: time::Local, + count: u32, +} + impl Buffer { pub fn new>(replica_id: ReplicaId, base_text: T) -> Self { Self::build(replica_id, None, base_text.into()) @@ -169,7 +250,7 @@ impl Buffer { } fn build(replica_id: ReplicaId, file: Option, base_text: String) -> Self { - let mut insertion_splits = HashMap::new(); + let mut insertion_splits = HashMap::default(); let mut fragments = SumTree::new(); let base_insertion = Insertion { @@ -191,7 +272,9 @@ impl Buffer { id: FragmentId::min_value().clone(), insertion: base_insertion.clone(), text: base_insertion.text.slice(0..0), - deletions: HashSet::new(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, }); if base_insertion.text.len() > 0 { @@ -209,7 +292,9 @@ impl Buffer { id: base_fragment_id, text: base_insertion.text.clone(), insertion: base_insertion, - deletions: HashSet::new(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, }); } @@ -217,13 +302,15 @@ impl Buffer { file, fragments, insertion_splits, + edit_ops: HashMap::default(), version: time::Global::new(), saved_version: time::Global::new(), last_edit: time::Local::default(), + undo_map: Default::default(), selections: HashMap::default(), selections_last_update: 0, deferred_ops: OperationQueue::new(), - deferred_replicas: HashSet::new(), + deferred_replicas: HashSet::default(), replica_id, local_clock: time::Local::new(replica_id), lamport_clock: time::Lamport::new(replica_id), @@ -391,6 +478,7 @@ impl Buffer { Edits { cursor, + undos: &self.undo_map, since, delta: 0, } @@ -432,6 +520,12 @@ impl Buffer { new_text.clone(), ); + for op in &ops { + if let Operation::Edit { edit, .. } = op { + self.edit_ops.insert(edit.id, edit.clone()); + } + } + if let Some(op) = ops.last() { if let Some(ctx) = ctx { ctx.notify(); @@ -441,12 +535,9 @@ impl Buffer { } } - if let Operation::Edit { - local_timestamp, .. - } = op - { - self.last_edit = *local_timestamp; - self.version.observe(*local_timestamp); + if let Operation::Edit { edit, .. } = op { + self.last_edit = edit.id; + self.version.observe(edit.id); } else { unreachable!() } @@ -676,27 +767,33 @@ impl Buffer { fn apply_op(&mut self, op: Operation) -> Result<()> { match op { Operation::Edit { - start_id, - start_offset, - end_id, - end_offset, - new_text, - version_in_range, - local_timestamp, + edit, lamport_timestamp, + .. } => { - if !self.version.observed(local_timestamp) { + if !self.version.observed(edit.id) { self.apply_edit( - start_id, - start_offset, - end_id, - end_offset, - new_text.as_ref().cloned(), - &version_in_range, - local_timestamp, + edit.start_id, + edit.start_offset, + edit.end_id, + edit.end_offset, + edit.new_text.as_ref().cloned(), + &edit.version_in_range, + edit.id, lamport_timestamp, )?; - self.version.observe(local_timestamp); + self.version.observe(edit.id); + self.edit_ops.insert(edit.id, 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 { @@ -785,8 +882,9 @@ impl Buffer { new_fragments.push(fragment); } if let Some(mut fragment) = within_range { - if version_in_range.observed(fragment.insertion.id) { + if fragment.was_visible(&version_in_range, &self.undo_map) { fragment.deletions.insert(local_timestamp); + fragment.visible = false; } new_fragments.push(fragment); } @@ -804,9 +902,11 @@ impl Buffer { )); } - if fragment.id < end_fragment_id && version_in_range.observed(fragment.insertion.id) + if fragment.id < end_fragment_id + && fragment.was_visible(&version_in_range, &self.undo_map) { fragment.deletions.insert(local_timestamp); + fragment.visible = false; } new_fragments.push(fragment); } @@ -831,6 +931,76 @@ impl Buffer { Ok(()) } + fn undo_or_redo(&mut self, edit_id: time::Local) -> Result { + let undo = UndoOperation { + id: self.local_clock.tick(), + edit_id, + count: self.undo_map.undo_count(edit_id) + 1, + }; + self.apply_undo(undo)?; + self.version.observe(undo.id); + + Ok(Operation::Undo { + undo, + lamport_timestamp: self.lamport_clock.tick(), + }) + } + + fn apply_undo(&mut self, undo: UndoOperation) -> Result<()> { + let mut new_fragments; + + self.undo_map.insert(undo); + let edit = &self.edit_ops[&undo.edit_id]; + let start_fragment_id = self.resolve_fragment_id(edit.start_id, edit.start_offset)?; + let end_fragment_id = self.resolve_fragment_id(edit.end_id, edit.end_offset)?; + let mut cursor = self.fragments.cursor::(); + + if edit.start_id == edit.end_id && edit.start_offset == edit.end_offset { + let splits = &self.insertion_splits[&undo.edit_id]; + let mut insertion_splits = splits.cursor::<(), ()>().map(|s| &s.fragment_id).peekable(); + + let first_split_id = insertion_splits.next().unwrap(); + new_fragments = cursor.slice(&FragmentIdRef::new(first_split_id), SeekBias::Left); + + loop { + let mut fragment = cursor.item().unwrap().clone(); + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.id); + new_fragments.push(fragment); + cursor.next(); + if let Some(split_id) = insertion_splits.next() { + new_fragments + .push_tree(cursor.slice(&FragmentIdRef::new(split_id), SeekBias::Left)); + } else { + break; + } + } + } else { + new_fragments = cursor.slice(&FragmentIdRef::new(&start_fragment_id), SeekBias::Left); + while let Some(fragment) = cursor.item() { + if fragment.id > end_fragment_id { + break; + } else { + let mut fragment = fragment.clone(); + if edit.version_in_range.observed(fragment.insertion.id) + || fragment.insertion.id == undo.edit_id + { + fragment.visible = fragment.is_visible(&self.undo_map); + fragment.max_undos.observe(undo.id); + } + new_fragments.push(fragment); + cursor.next(); + } + } + } + + new_fragments.push_tree(cursor.suffix()); + drop(cursor); + self.fragments = new_fragments; + + Ok(()) + } + fn flush_deferred_ops(&mut self) -> Result<()> { self.deferred_replicas.clear(); let mut deferred_ops = Vec::new(); @@ -851,16 +1021,12 @@ impl Buffer { false } else { match op { - Operation::Edit { - start_id, - end_id, - version_in_range, - .. - } => { - self.version.observed(*start_id) - && self.version.observed(*end_id) - && *version_in_range <= self.version + Operation::Edit { edit, .. } => { + self.version.observed(edit.start_id) + && self.version.observed(edit.end_id) + && edit.version_in_range <= self.version } + Operation::Undo { undo, .. } => self.version.observed(undo.edit_id), Operation::UpdateSelections { selections, .. } => { if let Some(selections) = selections { selections.iter().all(|selection| { @@ -927,6 +1093,7 @@ impl Buffer { while cur_range.is_some() && cursor.item().is_some() { let mut fragment = cursor.item().unwrap().clone(); + let fragment_summary = cursor.item_summary().unwrap(); let mut fragment_start = *cursor.start(); let mut fragment_end = fragment_start + fragment.visible_len(); @@ -986,8 +1153,10 @@ impl Buffer { prefix.set_end_offset(prefix.start_offset() + (range.end - fragment_start)); prefix.id = FragmentId::between(&new_fragments.last().unwrap().id, &fragment.id); - if fragment.is_visible() { + version_in_range.observe_all(&fragment_summary.max_version); + if fragment.visible { prefix.deletions.insert(local_timestamp); + prefix.visible = false; } fragment.set_start_offset(prefix.end_offset()); new_fragments.push(prefix.clone()); @@ -998,12 +1167,12 @@ impl Buffer { fragment_start = range.end; end_id = Some(fragment.insertion.id); end_offset = Some(fragment.start_offset()); - version_in_range.observe(fragment.insertion.id); } } else { - version_in_range.observe(fragment.insertion.id); - if fragment.is_visible() { + version_in_range.observe_all(&fragment_summary.max_version); + if fragment.visible { fragment.deletions.insert(local_timestamp); + fragment.visible = false; } } @@ -1012,13 +1181,15 @@ impl Buffer { // loop and find the first fragment that the splice does not contain fully. if range.end <= fragment_end { ops.push(Operation::Edit { - start_id: start_id.unwrap(), - start_offset: start_offset.unwrap(), - end_id: end_id.unwrap(), - end_offset: end_offset.unwrap(), - version_in_range, - new_text: new_text.clone(), - local_timestamp, + edit: EditOperation { + id: local_timestamp, + start_id: start_id.unwrap(), + start_offset: start_offset.unwrap(), + end_id: end_id.unwrap(), + end_offset: end_offset.unwrap(), + version_in_range, + new_text: new_text.clone(), + }, lamport_timestamp, }); @@ -1051,14 +1222,16 @@ impl Buffer { cursor.next(); if let Some(range) = cur_range.clone() { while let Some(fragment) = cursor.item() { + let fragment_summary = cursor.item_summary().unwrap(); fragment_start = *cursor.start(); fragment_end = fragment_start + fragment.visible_len(); if range.start < fragment_start && range.end >= fragment_end { let mut new_fragment = fragment.clone(); - if new_fragment.is_visible() { + version_in_range.observe_all(&fragment_summary.max_version); + if new_fragment.visible { new_fragment.deletions.insert(local_timestamp); + new_fragment.visible = false; } - version_in_range.observe(new_fragment.insertion.id); new_fragments.push(new_fragment); cursor.next(); @@ -1066,13 +1239,15 @@ impl Buffer { end_id = Some(fragment.insertion.id); end_offset = Some(fragment.end_offset()); ops.push(Operation::Edit { - start_id: start_id.unwrap(), - start_offset: start_offset.unwrap(), - end_id: end_id.unwrap(), - end_offset: end_offset.unwrap(), - version_in_range, - new_text: new_text.clone(), - local_timestamp, + edit: EditOperation { + id: local_timestamp, + start_id: start_id.unwrap(), + start_offset: start_offset.unwrap(), + end_id: end_id.unwrap(), + end_offset: end_offset.unwrap(), + version_in_range, + new_text: new_text.clone(), + }, lamport_timestamp, }); @@ -1111,13 +1286,15 @@ impl Buffer { debug_assert_eq!(old_ranges.next(), None); let last_fragment = new_fragments.last().unwrap(); ops.push(Operation::Edit { - start_id: last_fragment.insertion.id, - start_offset: last_fragment.end_offset(), - end_id: last_fragment.insertion.id, - end_offset: last_fragment.end_offset(), - version_in_range: time::Global::new(), - new_text: new_text.clone(), - local_timestamp, + edit: EditOperation { + id: local_timestamp, + start_id: last_fragment.insertion.id, + start_offset: last_fragment.end_offset(), + end_id: last_fragment.insertion.id, + end_offset: last_fragment.end_offset(), + version_in_range: time::Global::new(), + new_text: new_text.clone(), + }, lamport_timestamp, }); @@ -1365,7 +1542,7 @@ impl Buffer { .ok_or_else(|| anyhow!("fragment id does not exist"))?; let mut summary = fragments_cursor.start().clone(); - if fragment.is_visible() { + if fragment.visible { summary += fragment .text .slice(..offset - fragment.start_offset()) @@ -1398,9 +1575,11 @@ impl Clone for Buffer { file: self.file.clone(), fragments: self.fragments.clone(), insertion_splits: self.insertion_splits.clone(), + edit_ops: self.edit_ops.clone(), version: self.version.clone(), saved_version: self.saved_version.clone(), last_edit: self.last_edit.clone(), + undo_map: self.undo_map.clone(), selections: self.selections.clone(), selections_last_update: self.selections_last_update.clone(), deferred_ops: self.deferred_ops.clone(), @@ -1464,7 +1643,7 @@ impl<'a> Iterator for CharIter<'a> { loop { self.fragments_cursor.next(); if let Some(fragment) = self.fragments_cursor.item() { - if fragment.is_visible() { + if fragment.visible { self.fragment_chars = fragment.text.as_str().chars(); return self.fragment_chars.next(); } @@ -1498,7 +1677,7 @@ impl<'a> Iterator for FragmentIter<'a> { self.started = true; } if let Some(fragment) = self.cursor.item() { - if fragment.is_visible() { + if fragment.visible { return Some(fragment.text.as_str()); } } else { @@ -1518,7 +1697,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { let new_offset = *self.cursor.start(); let old_offset = (new_offset as isize - self.delta) as usize; - if !fragment.was_visible(&self.since) && fragment.is_visible() { + if !fragment.was_visible(&self.since, &self.undos) && fragment.visible { if let Some(ref mut change) = change { if change.new_range.end == new_offset { change.new_range.end += fragment.len(); @@ -1533,7 +1712,7 @@ impl<'a, F: Fn(&FragmentSummary) -> bool> Iterator for Edits<'a, F> { }); self.delta += fragment.len() as isize; } - } else if fragment.was_visible(&self.since) && !fragment.is_visible() { + } else if fragment.was_visible(&self.since, &self.undos) && !fragment.visible { if let Some(ref mut change) = change { if change.new_range.end == new_offset { change.old_range.end += fragment.len(); @@ -1732,7 +1911,9 @@ impl Fragment { id, text: insertion.text.clone(), insertion, - deletions: HashSet::new(), + deletions: Default::default(), + max_undos: Default::default(), + visible: true, } } @@ -1753,7 +1934,7 @@ impl Fragment { } fn visible_len(&self) -> usize { - if self.is_visible() { + if self.visible { self.len() } else { 0 @@ -1764,12 +1945,16 @@ impl Fragment { self.text.len() } - fn is_visible(&self) -> bool { - self.deletions.is_empty() + fn is_visible(&self, undos: &UndoMap) -> bool { + !undos.is_undone(self.insertion.id) && self.deletions.iter().all(|d| undos.is_undone(*d)) } - fn was_visible(&self, version: &time::Global) -> bool { - version.observed(self.insertion.id) && self.deletions.iter().all(|d| !version.observed(*d)) + fn was_visible(&self, version: &time::Global, undos: &UndoMap) -> bool { + (version.observed(self.insertion.id) && !undos.was_undone(self.insertion.id, version)) + && self + .deletions + .iter() + .all(|d| !version.observed(*d) || undos.was_undone(*d, version)) } fn point_for_offset(&self, offset: usize) -> Result { @@ -1790,8 +1975,9 @@ impl sum_tree::Item for Fragment { for deletion in &self.deletions { max_version.observe(*deletion); } + max_version.observe_all(&self.max_undos); - if self.is_visible() { + if self.visible { FragmentSummary { text_summary: self.text.summary(), max_fragment_id: self.id.clone(), @@ -1899,6 +2085,9 @@ impl Operation { Operation::Edit { lamport_timestamp, .. } => *lamport_timestamp, + Operation::Undo { + lamport_timestamp, .. + } => *lamport_timestamp, Operation::UpdateSelections { lamport_timestamp, .. } => *lamport_timestamp, @@ -2077,6 +2266,11 @@ mod tests { } assert_eq!(buffer.text(), reference_string); + if rng.gen_bool(0.25) { + buffer.randomly_undo_redo(rng); + reference_string = buffer.text(); + } + { let line_lengths = line_lengths_in_range(&buffer, 0..buffer.len()); @@ -2607,13 +2801,46 @@ mod tests { Ok(()) } + #[test] + fn test_undo_redo() -> Result<()> { + let mut buffer = Buffer::new(0, "1234"); + + let edit1 = buffer.edit(vec![1..1], "abx", None)?; + let edit2 = buffer.edit(vec![3..4], "yzef", None)?; + let edit3 = buffer.edit(vec![3..5], "cd", None)?; + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(edit1[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1cdef234"); + buffer.undo_or_redo(edit1[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(edit2[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1abcdx234"); + buffer.undo_or_redo(edit3[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1abx234"); + buffer.undo_or_redo(edit2[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(edit3[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1abcdef234"); + + buffer.undo_or_redo(edit3[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1abyzef234"); + buffer.undo_or_redo(edit1[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1yzef234"); + buffer.undo_or_redo(edit2[0].edit_id().unwrap())?; + assert_eq!(buffer.text(), "1234"); + + Ok(()) + } + #[test] fn test_random_concurrent_edits() { use crate::test::Network; - const PEERS: usize = 3; + const PEERS: usize = 5; - for seed in 0..50 { + for seed in 0..100 { println!("{:?}", seed); let mut rng = &mut StdRng::seed_from_u64(seed); @@ -2636,14 +2863,24 @@ mod tests { let replica_index = rng.gen_range(0..PEERS); let replica_id = replica_ids[replica_index]; let buffer = &mut buffers[replica_index]; - if mutation_count > 0 && rng.gen() { - let (_, _, ops) = buffer.randomly_mutate(&mut rng, None); - network.broadcast(replica_id, ops, &mut rng); - mutation_count -= 1; - } else if network.has_unreceived(replica_id) { - buffer - .apply_ops(network.receive(replica_id, &mut rng), None) - .unwrap(); + + match rng.gen_range(0..=100) { + 0..=50 if mutation_count != 0 => { + let (_, _, ops) = buffer.randomly_mutate(&mut rng, None); + network.broadcast(replica_id, ops, &mut rng); + mutation_count -= 1; + } + 51..=70 if mutation_count != 0 => { + let ops = buffer.randomly_undo_redo(&mut rng); + network.broadcast(replica_id, ops, &mut rng); + mutation_count -= 1; + } + 71..=100 if network.has_unreceived(replica_id) => { + buffer + .apply_ops(network.receive(replica_id, &mut rng), None) + .unwrap(); + } + _ => {} } if mutation_count == 0 && network.is_idle() { @@ -2669,13 +2906,14 @@ mod tests { pub fn randomly_mutate( &mut self, rng: &mut T, - ctx: Option<&mut ModelContext>, + mut ctx: Option<&mut ModelContext>, ) -> (Vec>, String, Vec) where T: Rng, { // Randomly edit - let (old_ranges, new_text, mut operations) = self.randomly_edit(rng, 5, ctx); + let (old_ranges, new_text, mut operations) = + self.randomly_edit(rng, 5, ctx.as_deref_mut()); // Randomly add, remove or mutate selection sets. let replica_selection_sets = &self @@ -2708,6 +2946,26 @@ mod tests { (old_ranges, new_text, operations) } + + pub fn randomly_undo_redo(&mut self, rng: &mut impl Rng) -> Vec { + let mut ops = Vec::new(); + for _ in 0..rng.gen_range(1..5) { + if let Some(edit_id) = self.edit_ops.keys().choose(rng).copied() { + ops.push(self.undo_or_redo(edit_id).unwrap()); + } + } + ops + } + } + + impl Operation { + fn edit_id(&self) -> Option { + match self { + Operation::Edit { edit, .. } => Some(edit.id), + Operation::Undo { undo, .. } => Some(undo.edit_id), + Operation::UpdateSelections { .. } => None, + } + } } fn line_lengths_in_range(buffer: &Buffer, range: Range) -> BTreeMap> { @@ -2715,11 +2973,11 @@ mod tests { for (row, line) in buffer.text()[range].lines().enumerate() { lengths .entry(line.len() as u32) - .or_insert(HashSet::new()) + .or_insert(HashSet::default()) .insert(row as u32); } if lengths.is_empty() { - let mut rows = HashSet::new(); + let mut rows = HashSet::default(); rows.insert(0); lengths.insert(0, rows); } diff --git a/zed/src/editor/buffer/text.rs b/zed/src/editor/buffer/text.rs index 1a074d4a75..147ffdd86a 100644 --- a/zed/src/editor/buffer/text.rs +++ b/zed/src/editor/buffer/text.rs @@ -162,7 +162,7 @@ impl<'a> From<&'a str> for Text { impl Debug for Text { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Text").field(&self.text).finish() + f.debug_tuple("Text").field(&self.as_str()).finish() } } diff --git a/zed/src/sum_tree/cursor.rs b/zed/src/sum_tree/cursor.rs index da33bc1179..440e99ba90 100644 --- a/zed/src/sum_tree/cursor.rs +++ b/zed/src/sum_tree/cursor.rs @@ -77,7 +77,7 @@ where } } - fn item_summary(&self) -> Option<&'a T::Summary> { + pub fn item_summary(&self) -> Option<&'a T::Summary> { assert!(self.did_seek, "Must seek before calling this method"); if let Some(entry) = self.stack.last() { match *entry.tree.0 { diff --git a/zed/src/time.rs b/zed/src/time.rs index 3c088fc4d1..4836ed921c 100644 --- a/zed/src/time.rs +++ b/zed/src/time.rs @@ -4,19 +4,20 @@ use std::mem; use std::ops::{Add, AddAssign}; use std::sync::Arc; +use lazy_static::lazy_static; + pub type ReplicaId = u16; +pub type Seq = u64; + #[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, Ord, PartialOrd)] pub struct Local { pub replica_id: ReplicaId, - pub value: u64, + pub value: Seq, } -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Global(Arc>); - #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] pub struct Lamport { - pub value: u64, + pub value: Seq, pub replica_id: ReplicaId, } @@ -57,12 +58,25 @@ impl<'a> AddAssign<&'a Local> for Local { } } +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Global(Arc>); + +lazy_static! { + static ref DEFAULT_GLOBAL: Global = Global(Arc::new(HashMap::new())); +} + +impl Default for Global { + fn default() -> Self { + DEFAULT_GLOBAL.clone() + } +} + impl Global { pub fn new() -> Self { - Global(Arc::new(HashMap::new())) + Self::default() } - pub fn get(&self, replica_id: ReplicaId) -> u64 { + pub fn get(&self, replica_id: ReplicaId) -> Seq { *self.0.get(&replica_id).unwrap_or(&0) }