Implement MultiBuffer::remove_excerpts

Max Brunsfeld created

We'll need this for updating project diagnostics

Change summary

crates/editor/src/multi_buffer.rs | 104 ++++++++++++++++++++++++++++++--
1 file changed, 96 insertions(+), 8 deletions(-)

Detailed changes

crates/editor/src/multi_buffer.rs 🔗

@@ -641,6 +641,57 @@ impl MultiBuffer {
         id
     }
 
+    pub fn excerpt_ids_for_buffer(&self, buffer: &ModelHandle<Buffer>) -> Vec<ExcerptId> {
+        self.buffers
+            .borrow()
+            .get(&buffer.id())
+            .map_or(Vec::new(), |state| state.excerpts.clone())
+    }
+
+    pub fn remove_excerpts<'a>(
+        &mut self,
+        excerpt_ids: impl IntoIterator<Item = &'a ExcerptId>,
+        cx: &mut ModelContext<Self>,
+    ) {
+        let mut buffers = self.buffers.borrow_mut();
+        let mut snapshot = self.snapshot.borrow_mut();
+        let mut new_excerpts = SumTree::new();
+        let mut cursor = snapshot.excerpts.cursor::<(Option<&ExcerptId>, usize)>();
+        let mut edits = Vec::new();
+        for excerpt_id in excerpt_ids {
+            new_excerpts.push_tree(cursor.slice(&Some(excerpt_id), Bias::Left, &()), &());
+            if let Some(excerpt) = cursor.item() {
+                if excerpt.id == *excerpt_id {
+                    let mut old_start = cursor.start().1;
+                    let old_end = cursor.end(&()).1;
+                    cursor.next(&());
+
+                    if let Some(buffer_state) = buffers.get_mut(&excerpt.buffer_id) {
+                        buffer_state.excerpts.retain(|id| id != excerpt_id);
+                    }
+
+                    // When removing the last excerpt, remove the trailing newline from
+                    // the previous excerpt.
+                    if cursor.item().is_none() && old_start > 0 {
+                        old_start -= 1;
+                        new_excerpts.update_last(|e| e.has_trailing_newline = false, &());
+                    }
+
+                    let new_start = new_excerpts.summary().text.bytes;
+                    edits.push(Edit {
+                        old: old_start..old_end,
+                        new: new_start..new_start,
+                    });
+                }
+            }
+        }
+        new_excerpts.push_tree(cursor.suffix(&()), &());
+        drop(cursor);
+        snapshot.excerpts = new_excerpts;
+        self.subscriptions.publish_mut(edits);
+        cx.notify();
+    }
+
     fn on_buffer_event(
         &mut self,
         _: ModelHandle<Buffer>,
@@ -2063,8 +2114,9 @@ mod tests {
             );
         });
 
+        let snapshot = multibuffer.read(cx).snapshot(cx);
         assert_eq!(
-            multibuffer.read(cx).snapshot(cx).text(),
+            snapshot.text(),
             concat!(
                 "bbbb\n", // Preserve newlines
                 "c\n",    //
@@ -2083,27 +2135,44 @@ mod tests {
             }]
         );
 
-        let multibuffer = multibuffer.read(cx).snapshot(cx);
+        let snapshot = multibuffer.read(cx).snapshot(cx);
         assert_eq!(
-            multibuffer.clip_point(Point::new(0, 5), Bias::Left),
+            snapshot.clip_point(Point::new(0, 5), Bias::Left),
             Point::new(0, 4)
         );
         assert_eq!(
-            multibuffer.clip_point(Point::new(0, 5), Bias::Right),
+            snapshot.clip_point(Point::new(0, 5), Bias::Right),
             Point::new(0, 4)
         );
         assert_eq!(
-            multibuffer.clip_point(Point::new(5, 1), Bias::Right),
+            snapshot.clip_point(Point::new(5, 1), Bias::Right),
             Point::new(5, 1)
         );
         assert_eq!(
-            multibuffer.clip_point(Point::new(5, 2), Bias::Right),
+            snapshot.clip_point(Point::new(5, 2), Bias::Right),
             Point::new(5, 2)
         );
         assert_eq!(
-            multibuffer.clip_point(Point::new(5, 3), Bias::Right),
+            snapshot.clip_point(Point::new(5, 3), Bias::Right),
             Point::new(5, 2)
         );
+
+        let snapshot = multibuffer.update(cx, |multibuffer, cx| {
+            let buffer_2_excerpt_id = multibuffer.excerpt_ids_for_buffer(&buffer_2)[0].clone();
+            multibuffer.remove_excerpts(&[buffer_2_excerpt_id], cx);
+            multibuffer.snapshot(cx)
+        });
+
+        assert_eq!(
+            snapshot.text(),
+            concat!(
+                "bbbb\n", // Preserve newlines
+                "c\n",    //
+                "cc\n",   //
+                "ddd\n",  //
+                "eeee",   //
+            )
+        );
     }
 
     #[gpui::test]
@@ -2201,7 +2270,7 @@ mod tests {
         let mut buffers: Vec<ModelHandle<Buffer>> = Vec::new();
         let list = cx.add_model(|_| MultiBuffer::new(0));
         let mut excerpt_ids = Vec::new();
-        let mut expected_excerpts = Vec::new();
+        let mut expected_excerpts = Vec::<(ModelHandle<Buffer>, Range<text::Anchor>)>::new();
         let mut old_versions = Vec::new();
 
         for _ in 0..operations {
@@ -2210,6 +2279,20 @@ mod tests {
                     let buffer = buffers.choose(&mut rng).unwrap();
                     buffer.update(cx, |buf, cx| buf.randomly_edit(&mut rng, 5, cx));
                 }
+                20..=29 if !expected_excerpts.is_empty() => {
+                    let ix = rng.gen_range(0..expected_excerpts.len());
+                    let id = excerpt_ids.remove(ix);
+                    let (buffer, range) = expected_excerpts.remove(ix);
+                    let buffer = buffer.read(cx);
+                    log::info!(
+                        "Removing excerpt {}: {:?}",
+                        ix,
+                        buffer
+                            .text_for_range(range.to_offset(&buffer))
+                            .collect::<String>(),
+                    );
+                    list.update(cx, |list, cx| list.remove_excerpts(&[id], cx));
+                }
                 _ => {
                     let buffer_handle = if buffers.is_empty() || rng.gen_bool(0.4) {
                         let base_text = RandomCharIter::new(&mut rng).take(10).collect::<String>();
@@ -2275,6 +2358,11 @@ mod tests {
                 expected_text.pop();
             }
 
+            // Always report one buffer row
+            if expected_buffer_rows.is_empty() {
+                expected_buffer_rows.push(Some(0));
+            }
+
             assert_eq!(snapshot.text(), expected_text);
             log::info!("MultiBuffer text: {:?}", expected_text);