Implement ExcerptList::subscribe

Nathan Sobo and Max Brunsfeld created

Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>

Change summary

crates/language/src/buffer.rs       |  1 
crates/language/src/excerpt_list.rs | 73 ++++++++++++++++++++++--------
crates/text/src/patch.rs            |  9 +++
crates/text/src/subscription.rs     | 48 ++++++++++++++++++++
crates/text/src/text.rs             | 42 +++--------------
5 files changed, 119 insertions(+), 54 deletions(-)

Detailed changes

crates/language/src/buffer.rs 🔗

@@ -28,6 +28,7 @@ use std::{
     time::{Duration, Instant, SystemTime, UNIX_EPOCH},
     vec,
 };
+use text::subscription::Subscription;
 pub use text::{Buffer as TextBuffer, Operation as _, *};
 use theme::SyntaxTheme;
 use tree_sitter::{InputEdit, Parser, QueryCursor, Tree};

crates/language/src/excerpt_list.rs 🔗

@@ -1,12 +1,14 @@
 use crate::{buffer, Buffer, Chunk};
 use collections::HashMap;
 use gpui::{AppContext, Entity, ModelContext, ModelHandle};
-use lsp::TextDocumentSaveReason;
 use parking_lot::Mutex;
 use smallvec::{smallvec, SmallVec};
-use std::{cmp, iter, mem, ops::Range};
+use std::{cmp, iter, ops::Range};
 use sum_tree::{Bias, Cursor, SumTree};
-use text::{Anchor, AnchorRangeExt, Patch, TextSummary};
+use text::{
+    subscription::{Subscription, Topic},
+    Anchor, AnchorRangeExt, Edit, Patch, TextSummary,
+};
 use theme::SyntaxTheme;
 
 const NEWLINES: &'static [u8] = &[b'\n'; u8::MAX as usize];
@@ -21,6 +23,7 @@ pub type ExcerptId = Location;
 pub struct ExcerptList {
     snapshot: Mutex<Snapshot>,
     buffers: HashMap<usize, BufferState>,
+    subscriptions: Topic,
 }
 
 #[derive(Debug)]
@@ -77,6 +80,10 @@ impl ExcerptList {
         self.snapshot.lock().clone()
     }
 
+    pub fn subscribe(&mut self) -> Subscription {
+        self.subscriptions.subscribe()
+    }
+
     pub fn push<O>(&mut self, props: ExcerptProperties<O>, cx: &mut ModelContext<Self>) -> ExcerptId
     where
         O: text::ToOffset,
@@ -89,10 +96,13 @@ impl ExcerptList {
         let prev_id = snapshot.excerpts.last().map(|e| &e.id);
         let id = ExcerptId::between(prev_id.unwrap_or(&ExcerptId::min()), &ExcerptId::max());
 
-        snapshot.excerpts.push(
-            Excerpt::new(id.clone(), buffer.snapshot(), range, props.header_height),
-            &(),
-        );
+        let edit_start = snapshot.excerpts.summary().text.bytes;
+        let excerpt = Excerpt::new(id.clone(), buffer.snapshot(), range, props.header_height);
+        let edit = Edit {
+            old: edit_start..edit_start,
+            new: edit_start..edit_start + excerpt.text_summary.bytes,
+        };
+        snapshot.excerpts.push(excerpt, &());
         self.buffers
             .entry(props.buffer.id())
             .or_insert_with(|| BufferState {
@@ -103,6 +113,8 @@ impl ExcerptList {
             .excerpts
             .push(id.clone());
 
+        self.subscriptions.publish_mut([edit]);
+
         id
     }
 
@@ -126,8 +138,6 @@ impl ExcerptList {
         }
         excerpts_to_edit.sort_unstable_by_key(|(excerpt_id, _)| *excerpt_id);
 
-        dbg!(&excerpts_to_edit);
-
         let mut patch = Patch::<usize>::default();
         let mut new_excerpts = SumTree::new();
         let mut cursor = snapshot.excerpts.cursor::<(ExcerptId, usize)>();
@@ -168,6 +178,8 @@ impl ExcerptList {
 
         drop(cursor);
         snapshot.excerpts = new_excerpts;
+
+        self.subscriptions.publish(&patch);
     }
 }
 
@@ -357,12 +369,11 @@ impl Location {
 
 #[cfg(test)]
 mod tests {
-    use std::env;
-
     use super::*;
     use crate::Buffer;
     use gpui::MutableAppContext;
     use rand::prelude::*;
+    use std::{env, mem};
     use text::{Point, RandomCharIter};
     use util::test::sample_text;
 
@@ -371,12 +382,10 @@ mod tests {
         let buffer_1 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'a'), cx));
         let buffer_2 = cx.add_model(|cx| Buffer::new(0, sample_text(6, 6, 'g'), cx));
 
-        let list = cx.add_model(|cx| {
-            let mut list = ExcerptList::new();
-            // aaaaaa
-            // bbbbbb
-            // cccccc
-            // dddddd
+        let list = cx.add_model(|cx| ExcerptList::new());
+
+        let subscription = list.update(cx, |list, cx| {
+            let subscription = list.subscribe();
             list.push(
                 ExcerptProperties {
                     buffer: &buffer_1,
@@ -385,6 +394,14 @@ mod tests {
                 },
                 cx,
             );
+            assert_eq!(
+                subscription.consume().into_inner(),
+                [Edit {
+                    old: 0..0,
+                    new: 0..12
+                }]
+            );
+
             list.push(
                 ExcerptProperties {
                     buffer: &buffer_1,
@@ -401,7 +418,15 @@ mod tests {
                 },
                 cx,
             );
-            list
+            assert_eq!(
+                subscription.consume().into_inner(),
+                [Edit {
+                    old: 12..12,
+                    new: 12..26
+                }]
+            );
+
+            subscription
         });
 
         assert_eq!(
@@ -425,7 +450,7 @@ mod tests {
             buffer.edit(
                 [
                     Point::new(0, 0)..Point::new(0, 0),
-                    Point::new(2, 1)..Point::new(2, 2),
+                    Point::new(2, 1)..Point::new(2, 3),
                 ],
                 "\n",
                 cx,
@@ -439,7 +464,7 @@ mod tests {
                 "\n",     //
                 "bbbb\n", //
                 "c\n",    //
-                "ccc\n",  //
+                "cc\n",   //
                 "\n",     //
                 "ddd\n",  //
                 "eeee\n", //
@@ -449,6 +474,14 @@ mod tests {
                 "jj"      //
             )
         );
+
+        assert_eq!(
+            subscription.consume().into_inner(),
+            [Edit {
+                old: 17..19,
+                new: 17..18
+            }]
+        );
     }
 
     #[gpui::test(iterations = 100)]

crates/text/src/patch.rs 🔗

@@ -210,6 +210,15 @@ impl<'a, T: Clone> IntoIterator for &'a Patch<T> {
     }
 }
 
+impl<'a, T: Clone> IntoIterator for &'a mut Patch<T> {
+    type Item = Edit<T>;
+    type IntoIter = std::iter::Cloned<std::slice::Iter<'a, Edit<T>>>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.0.iter().cloned()
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

crates/text/src/subscription.rs 🔗

@@ -0,0 +1,48 @@
+use crate::{Edit, Patch};
+use parking_lot::Mutex;
+use std::{
+    mem,
+    sync::{Arc, Weak},
+};
+
+#[derive(Default)]
+pub struct Topic(Mutex<Vec<Weak<Mutex<Patch<usize>>>>>);
+
+pub struct Subscription(Arc<Mutex<Patch<usize>>>);
+
+impl Topic {
+    pub fn subscribe(&mut self) -> Subscription {
+        let subscription = Subscription(Default::default());
+        self.0.get_mut().push(Arc::downgrade(&subscription.0));
+        subscription
+    }
+
+    pub fn publish(&self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
+        publish(&mut *self.0.lock(), edits);
+    }
+
+    pub fn publish_mut(&mut self, edits: impl Clone + IntoIterator<Item = Edit<usize>>) {
+        publish(self.0.get_mut(), edits);
+    }
+}
+
+impl Subscription {
+    pub fn consume(&self) -> Patch<usize> {
+        mem::take(&mut *self.0.lock())
+    }
+}
+
+fn publish(
+    subscriptions: &mut Vec<Weak<Mutex<Patch<usize>>>>,
+    edits: impl Clone + IntoIterator<Item = Edit<usize>>,
+) {
+    subscriptions.retain(|subscription| {
+        if let Some(subscription) = subscription.upgrade() {
+            let mut patch = subscription.lock();
+            *patch = patch.compose(edits.clone());
+            true
+        } else {
+            false
+        }
+    });
+}

crates/text/src/text.rs 🔗

@@ -7,6 +7,7 @@ mod point_utf16;
 pub mod random_char_iter;
 pub mod rope;
 mod selection;
+pub mod subscription;
 #[cfg(test)]
 mod tests;
 
@@ -15,7 +16,6 @@ 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::*;
@@ -29,9 +29,10 @@ use std::{
     iter::Iterator,
     ops::{self, Deref, Range, Sub},
     str,
-    sync::{Arc, Weak},
+    sync::Arc,
     time::{Duration, Instant},
 };
+use subscription::{Subscription, Topic};
 pub use sum_tree::Bias;
 use sum_tree::{FilterCursor, SumTree};
 
@@ -46,7 +47,7 @@ pub struct Buffer {
     remote_id: u64,
     local_clock: clock::Local,
     lamport_clock: clock::Lamport,
-    subscriptions: Vec<Weak<Mutex<Vec<Patch<usize>>>>>,
+    subscriptions: Topic,
 }
 
 #[derive(Clone, Debug)]
@@ -343,20 +344,6 @@ impl<D1, D2> Edit<(D1, D2)> {
     }
 }
 
-#[derive(Clone, Default)]
-pub struct Subscription(Arc<Mutex<Vec<Patch<usize>>>>);
-
-impl Subscription {
-    pub fn consume(&self) -> Patch<usize> {
-        let mut patches = self.0.lock();
-        let mut changes = Patch::default();
-        for patch in patches.drain(..) {
-            changes = changes.compose(&patch);
-        }
-        changes
-    }
-}
-
 #[derive(Copy, Clone, Debug, Default, Eq, PartialEq)]
 pub struct InsertionTimestamp {
     pub replica_id: ReplicaId,
@@ -699,7 +686,7 @@ impl Buffer {
         self.snapshot.fragments = new_fragments;
         self.snapshot.visible_text = visible_text;
         self.snapshot.deleted_text = deleted_text;
-        self.update_subscriptions(edits);
+        self.subscriptions.publish_mut(&edits);
         edit_op.new_text = new_text;
         edit_op
     }
@@ -955,7 +942,7 @@ impl Buffer {
         self.snapshot.deleted_text = deleted_text;
         self.local_clock.observe(timestamp.local());
         self.lamport_clock.observe(timestamp.lamport());
-        self.update_subscriptions(edits);
+        self.subscriptions.publish_mut(&edits);
     }
 
     fn apply_undo(&mut self, undo: &UndoOperation) -> Result<()> {
@@ -1045,7 +1032,7 @@ impl Buffer {
         self.snapshot.fragments = new_fragments;
         self.snapshot.visible_text = visible_text;
         self.snapshot.deleted_text = deleted_text;
-        self.update_subscriptions(edits);
+        self.subscriptions.publish_mut(&edits);
         Ok(())
     }
 
@@ -1203,20 +1190,7 @@ impl Buffer {
     }
 
     pub fn subscribe(&mut self) -> Subscription {
-        let subscription = Subscription(Default::default());
-        self.subscriptions.push(Arc::downgrade(&subscription.0));
-        subscription
-    }
-
-    fn update_subscriptions(&mut self, edits: Patch<usize>) {
-        self.subscriptions.retain(|subscription| {
-            if let Some(subscription) = subscription.upgrade() {
-                subscription.lock().push(edits.clone());
-                true
-            } else {
-                false
-            }
-        });
+        self.subscriptions.subscribe()
     }
 
     pub fn selection_set(&self, set_id: SelectionSetId) -> Result<&SelectionSet> {