From 3b536f153f191f7251b87c48816dbc0c948272bc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 1 Dec 2021 15:55:05 +0100 Subject: [PATCH] Introduce `text::Buffer::subscribe` Co-Authored-By: Nathan Sobo --- Cargo.lock | 3 + crates/editor/src/display_map/wrap_map.rs | 12 +- crates/language/src/buffer.rs | 27 +---- crates/text/Cargo.toml | 3 + crates/text/src/patch.rs | 21 ++-- crates/text/src/tests.rs | 36 ++++-- crates/text/src/text.rs | 130 ++++++++++++++++++---- 7 files changed, 159 insertions(+), 73 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d84236e1cf710068a7c88c84dae4b4b9f2f1f667..f6227543798b09ad0afa62b93631e863fb7cdb2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4849,8 +4849,11 @@ dependencies = [ "arrayvec 0.7.1", "clock", "collections", + "ctor", + "env_logger", "gpui", "log", + "parking_lot", "rand 0.8.3", "smallvec", "sum_tree", diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6d8ddcece0f3c26f0b8f600b4f1def40807ae252..174e9df98eb7f35be03360973ab7512ed0ed6789 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -204,12 +204,10 @@ impl WrapMap { } let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; - self.edits_since_sync = self.edits_since_sync.compose(&unsafe { - Patch::new_unchecked(vec![Edit { - old: 0..old_rows, - new: 0..new_rows, - }]) - }); + self.edits_since_sync = self.edits_since_sync.compose(&Patch::new(vec![Edit { + old: 0..old_rows, + new: 0..new_rows, + }])); } } @@ -559,7 +557,7 @@ impl Snapshot { } consolidate_wrap_edits(&mut wrap_edits); - unsafe { Patch::new_unchecked(wrap_edits) } + Patch::new(wrap_edits) } pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 1c45bf15a7cefbef630f86da2c95abfd84daf262..bfe842246c3f440f356c015ae258d56ae081739f 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -1322,8 +1322,7 @@ impl Buffer { was_dirty: bool, cx: &mut ModelContext, ) { - let patch = - unsafe { Patch::new_unchecked(self.edits_since::(old_version).collect()) }; + let patch = Patch::new(self.edits_since::(old_version).collect()); if patch.is_empty() { return; } @@ -1502,30 +1501,6 @@ impl Entity for Buffer { } } -// TODO: Do we need to clone a buffer? -impl Clone for Buffer { - fn clone(&self) -> Self { - Self { - text: self.text.clone(), - saved_version: self.saved_version.clone(), - saved_mtime: self.saved_mtime, - file: self.file.as_ref().map(|f| f.boxed_clone()), - language: self.language.clone(), - syntax_tree: Mutex::new(self.syntax_tree.lock().clone()), - parsing_in_background: false, - sync_parse_timeout: self.sync_parse_timeout, - parse_count: self.parse_count, - autoindent_requests: Default::default(), - pending_autoindent: Default::default(), - diagnostics: self.diagnostics.clone(), - diagnostics_update_count: self.diagnostics_update_count, - language_server: None, - #[cfg(test)] - operations: self.operations.clone(), - } - } -} - impl Deref for Buffer { type Target = TextBuffer; diff --git a/crates/text/Cargo.toml b/crates/text/Cargo.toml index bda69006bec88003ec9804cbe951a3ea4b591198..2b68a71a080ea412ee534d662b85e656fa51edc3 100644 --- a/crates/text/Cargo.toml +++ b/crates/text/Cargo.toml @@ -16,10 +16,13 @@ sum_tree = { path = "../sum_tree" } anyhow = "1.0.38" arrayvec = "0.7.1" log = "0.4" +parking_lot = "0.11" rand = { version = "0.8.3", optional = true } smallvec = { version = "1.6", features = ["union"] } [dev-dependencies] collections = { path = "../collections", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } +ctor = "0.1" +env_logger = "0.8" rand = "0.8.3" diff --git a/crates/text/src/patch.rs b/crates/text/src/patch.rs index 266ad04f25b61cc99a1fa656514b9d7e587659ed..85d354d3b01ddc4a33bd4da298a0dfa7a0809399 100644 --- a/crates/text/src/patch.rs +++ b/crates/text/src/patch.rs @@ -18,18 +18,17 @@ where + Default + PartialEq, { - pub unsafe fn new_unchecked(edits: Vec>) -> Self { - Self(edits) - } - pub fn new(edits: Vec>) -> Self { - let mut last_edit: Option<&Edit> = None; - for edit in &edits { - if let Some(last_edit) = last_edit { - assert!(edit.old.start > last_edit.old.end); - assert!(edit.new.start > last_edit.new.end); + #[cfg(debug_assertions)] + { + let mut last_edit: Option<&Edit> = None; + for edit in &edits { + if let Some(last_edit) = last_edit { + assert!(edit.old.start > last_edit.old.end); + assert!(edit.new.start > last_edit.new.end); + } + last_edit = Some(edit); } - last_edit = Some(edit); } Self(edits) } @@ -179,7 +178,7 @@ where self.0.is_empty() } - fn push(&mut self, edit: Edit) { + pub fn push(&mut self, edit: Edit) { if edit.is_empty() { return; } diff --git a/crates/text/src/tests.rs b/crates/text/src/tests.rs index 9c681b1071f8b329a33e8fe73075dedae6962cd1..a13273b898b9d6febd865add8cfee83d30f81fc1 100644 --- a/crates/text/src/tests.rs +++ b/crates/text/src/tests.rs @@ -8,6 +8,13 @@ use std::{ time::{Duration, Instant}, }; +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + // std::env::set_var("RUST_LOG", "info"); + env_logger::init(); +} + #[test] fn test_edit() { let mut buffer = Buffer::new(0, 0, History::new("abc".into())); @@ -72,30 +79,43 @@ fn test_random_edits(mut rng: StdRng) { ); if rng.gen_bool(0.3) { - buffer_versions.push(buffer.clone()); + buffer_versions.push((buffer.clone(), buffer.subscribe())); } } - for mut old_buffer in buffer_versions { + for (old_buffer, subscription) in buffer_versions { let edits = buffer .edits_since::(&old_buffer.version) .collect::>(); log::info!( - "mutating old buffer version {:?}, text: {:?}, edits since: {:?}", + "applying edits since version {:?} to old text: {:?}: {:?}", old_buffer.version(), old_buffer.text(), edits, ); + let mut text = old_buffer.visible_text.clone(); for edit in edits { let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); - old_buffer.edit( - Some(edit.new.start..edit.new.start + edit.old.len()), - new_text, - ); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); + } + assert_eq!(text.to_string(), buffer.text()); + + let subscription_edits = subscription.consume(); + log::info!( + "applying subscription edits since version {:?} to old text: {:?}: {:?}", + old_buffer.version(), + old_buffer.text(), + subscription_edits, + ); + + let mut text = old_buffer.visible_text.clone(); + for edit in subscription_edits.into_inner() { + let new_text: String = buffer.text_for_range(edit.new.clone()).collect(); + text.replace(edit.new.start..edit.new.start + edit.old.len(), &new_text); } - assert_eq!(old_buffer.text(), buffer.text()); + assert_eq!(text.to_string(), buffer.text()); } } diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index a5f014274b464d984c98057f70144036a4be96e6..50b8dd10fe5e47daba0ad5456f20d25154147337 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -15,6 +15,7 @@ use anyhow::{anyhow, Result}; use clock::ReplicaId; use collections::{HashMap, HashSet}; use operation_queue::OperationQueue; +use parking_lot::Mutex; pub use patch::Patch; pub use point::*; pub use point_utf16::*; @@ -28,13 +29,12 @@ use std::{ iter::Iterator, ops::{self, Deref, Range, Sub}, str, - sync::Arc, + sync::{Arc, Weak}, time::{Duration, Instant}, }; pub use sum_tree::Bias; use sum_tree::{FilterCursor, SumTree}; -#[derive(Clone)] pub struct Buffer { snapshot: Snapshot, last_edit: clock::Local, @@ -46,6 +46,7 @@ pub struct Buffer { remote_id: u64, local_clock: clock::Local, lamport_clock: clock::Lamport, + subscriptions: Vec>, } #[derive(Clone)] @@ -341,6 +342,25 @@ impl Edit<(D1, D2)> { } } +#[derive(Default)] +pub struct Subscription(Arc>>>); + +impl Subscription { + pub fn consume(&self) -> Patch { + let mut patches = self.0.lock(); + let mut changes = Patch::default(); + for patch in patches.drain(..) { + changes = changes.compose(&patch); + } + changes + } + + pub fn publish(&self, patch: Patch) { + let mut changes = self.0.lock(); + changes.push(patch); + } +} + #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] pub struct InsertionTimestamp { pub replica_id: ReplicaId, @@ -473,13 +493,14 @@ impl Buffer { }, last_edit: clock::Local::default(), history, - selections: HashMap::default(), + selections: Default::default(), deferred_ops: OperationQueue::new(), deferred_replicas: HashSet::default(), replica_id, remote_id, local_clock, lamport_clock, + subscriptions: Default::default(), } } @@ -546,7 +567,8 @@ impl Buffer { new_text: Option, timestamp: InsertionTimestamp, ) -> EditOperation { - let mut edit = EditOperation { + let mut edits = Patch::default(); + let mut edit_op = EditOperation { timestamp, version: self.version(), ranges: Vec::with_capacity(ranges.len()), @@ -602,6 +624,11 @@ impl Buffer { // Insert the new text before any existing fragments within the range. if let Some(new_text) = new_text.as_deref() { + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: fragment_start..fragment_start, + new: new_start..new_start + new_text.len(), + }); new_ropes.push_str(new_text); new_fragments.push( Fragment { @@ -628,6 +655,13 @@ impl Buffer { intersection.visible = false; } if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: fragment_start..intersection_end, + new: new_start..new_start, + }); + } new_ropes.push_fragment(&intersection, fragment.visible); new_fragments.push(intersection, &None); fragment_start = intersection_end; @@ -638,7 +672,7 @@ impl Buffer { } let full_range_end = FullOffset(range.end + old_fragments.start().deleted); - edit.ranges.push(full_range_start..full_range_end); + edit_op.ranges.push(full_range_start..full_range_end); } // If the current fragment has been partially consumed, then consume the rest of it @@ -663,8 +697,9 @@ impl Buffer { self.snapshot.fragments = new_fragments; self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; - edit.new_text = new_text; - edit + self.update_subscriptions(edits); + edit_op.new_text = new_text; + edit_op } pub fn apply_ops>(&mut self, ops: I) -> Result<()> { @@ -764,10 +799,11 @@ impl Buffer { return; } + let mut edits = Patch::default(); 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 old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); let mut new_fragments = old_fragments.slice( &VersionedFullOffset::Offset(ranges[0].start), Bias::Left, @@ -775,16 +811,16 @@ impl Buffer { ); new_ropes.push_tree(new_fragments.summary().text); - let mut fragment_start = old_fragments.start().full_offset(); + let mut fragment_start = old_fragments.start().0.full_offset(); for range in ranges { - let fragment_end = old_fragments.end(&cx).full_offset(); + let fragment_end = old_fragments.end(&cx).0.full_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().full_offset() { + if fragment_start > old_fragments.start().0.full_offset() { if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end.0 - fragment_start.0; @@ -798,18 +834,18 @@ impl Buffer { old_fragments.slice(&VersionedFullOffset::Offset(range.start), Bias::Left, &cx); new_ropes.push_tree(slice.summary().text); new_fragments.push_tree(slice, &None); - fragment_start = old_fragments.start().full_offset(); + fragment_start = old_fragments.start().0.full_offset(); } // If we are at the end of a non-concurrent fragment, advance to the next one. - let fragment_end = old_fragments.end(&cx).full_offset(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); if fragment_end == range.start && fragment_end > fragment_start { let mut fragment = old_fragments.item().unwrap().clone(); fragment.len = fragment_end.0 - fragment_start.0; new_ropes.push_fragment(&fragment, fragment.visible); new_fragments.push(fragment, &None); old_fragments.next(&cx); - fragment_start = old_fragments.start().full_offset(); + fragment_start = old_fragments.start().0.full_offset(); } // Skip over insertions that are concurrent to this edit, but have a lower lamport @@ -839,6 +875,15 @@ impl Buffer { // Insert the new text before any existing fragments within the range. if let Some(new_text) = new_text { + let mut old_start = old_fragments.start().1; + if old_fragments.item().map_or(false, |f| f.visible) { + old_start += fragment_start.0 - old_fragments.start().0.full_offset().0; + } + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + new_text.len(), + }); new_ropes.push_str(new_text); new_fragments.push( Fragment { @@ -856,7 +901,7 @@ impl Buffer { // portions as deleted. while fragment_start < range.end { let fragment = old_fragments.item().unwrap(); - let fragment_end = old_fragments.end(&cx).full_offset(); + let fragment_end = old_fragments.end(&cx).0.full_offset(); let mut intersection = fragment.clone(); let intersection_end = cmp::min(range.end, fragment_end); if fragment.was_visible(version, &self.undo_map) { @@ -865,6 +910,15 @@ impl Buffer { intersection.visible = false; } if intersection.len > 0 { + if fragment.visible && !intersection.visible { + let old_start = old_fragments.start().1 + + (fragment_start.0 - old_fragments.start().0.full_offset().0); + let new_start = new_fragments.summary().text.visible; + edits.push(Edit { + old: old_start..old_start + intersection.len, + new: new_start..new_start, + }); + } new_ropes.push_fragment(&intersection, fragment.visible); new_fragments.push(intersection, &None); fragment_start = intersection_end; @@ -877,8 +931,8 @@ impl Buffer { // 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().full_offset() { - let fragment_end = old_fragments.end(&cx).full_offset(); + if fragment_start > old_fragments.start().0.full_offset() { + let fragment_end = old_fragments.end(&cx).0.full_offset(); if fragment_end > fragment_start { let mut suffix = old_fragments.item().unwrap().clone(); suffix.len = fragment_end.0 - fragment_start.0; @@ -899,9 +953,11 @@ impl Buffer { self.snapshot.deleted_text = deleted_text; self.local_clock.observe(timestamp.local()); self.lamport_clock.observe(timestamp.lamport()); + self.update_subscriptions(edits); } fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> { + let mut edits = Patch::default(); self.snapshot.undo_map.insert(undo); let mut cx = undo.version.clone(); @@ -910,7 +966,7 @@ impl Buffer { } let cx = Some(cx); - let mut old_fragments = self.fragments.cursor::(); + let mut old_fragments = self.fragments.cursor::<(VersionedFullOffset, usize)>(); let mut new_fragments = old_fragments.slice( &VersionedFullOffset::Offset(undo.ranges[0].start), Bias::Right, @@ -921,7 +977,7 @@ impl Buffer { new_ropes.push_tree(new_fragments.summary().text); for range in &undo.ranges { - let mut end_offset = old_fragments.end(&cx).full_offset(); + let mut end_offset = old_fragments.end(&cx).0.full_offset(); if end_offset < range.start { let preceding_fragments = old_fragments.slice( @@ -944,11 +1000,25 @@ impl Buffer { fragment.visible = fragment.is_visible(&self.undo_map); fragment.max_undos.observe(undo.id); } + + let old_start = old_fragments.start().1; + let new_start = new_fragments.summary().text.visible; + if fragment_was_visible && !fragment.visible { + edits.push(Edit { + old: old_start..old_start + fragment.len, + new: new_start..new_start, + }); + } else if !fragment_was_visible && fragment.visible { + edits.push(Edit { + old: old_start..old_start, + new: new_start..new_start + fragment.len, + }); + } new_ropes.push_fragment(&fragment, fragment_was_visible); new_fragments.push(fragment, &None); old_fragments.next(&cx); - if end_offset == old_fragments.end(&cx).full_offset() { + if end_offset == old_fragments.end(&cx).0.full_offset() { let unseen_fragments = old_fragments.slice( &VersionedFullOffset::Offset(end_offset), Bias::Right, @@ -957,7 +1027,7 @@ impl Buffer { new_ropes.push_tree(unseen_fragments.summary().text); new_fragments.push_tree(unseen_fragments, &None); } - end_offset = old_fragments.end(&cx).full_offset(); + end_offset = old_fragments.end(&cx).0.full_offset(); } else { break; } @@ -973,6 +1043,7 @@ impl Buffer { self.snapshot.fragments = new_fragments; self.snapshot.visible_text = visible_text; self.snapshot.deleted_text = deleted_text; + self.update_subscriptions(edits); Ok(()) } @@ -1129,6 +1200,23 @@ impl Buffer { }) } + pub fn subscribe(&mut self) -> Arc { + let subscription = Arc::new(Default::default()); + self.subscriptions.push(Arc::downgrade(&subscription)); + subscription + } + + fn update_subscriptions(&mut self, edits: Patch) { + self.subscriptions.retain(|subscription| { + if let Some(subscription) = subscription.upgrade() { + subscription.publish(edits.clone()); + true + } else { + false + } + }); + } + pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> { self.selections .get(&set_id)