text: batch fragment insertions before turning them into a SumTree

Piotr Osiewicz and Smit Barmase created

This commit introduces a FragmentBuilder which reduces the time needed
to run a replace_all on large files by 30%. It does pretty much what a
SumTree would, except that it doesn't have to rebalance the tree on each
insertion

Co-authored-by: Smit Barmase <heysmitbarmase@gmail.com>

Change summary

crates/editor_benchmarks/src/main.rs |  2 -
crates/text/src/text.rs              | 47 +++++++++++++++++++++++++----
2 files changed, 40 insertions(+), 9 deletions(-)

Detailed changes

crates/editor_benchmarks/src/main.rs 🔗

@@ -70,7 +70,6 @@ fn parse_args() -> Args {
 fn main() {
     let args = parse_args();
 
-    dbg!(&args);
     let file_contents = std::fs::read_to_string(&args.file).expect("failed to read input file");
     let file_len = file_contents.len();
     println!("Read {} ({file_len} bytes)", args.file);
@@ -140,7 +139,6 @@ fn main() {
             cx.spawn_in(
                 window,
                 async move |weak: WeakEntity<Editor>, cx: &mut AsyncWindowContext| {
-                    dbg!("A");
                     let find_task = weak.update_in(cx, |editor, window, cx| {
                         editor.find_matches(query.clone(), window, cx)
                     })?;

crates/text/src/text.rs 🔗

@@ -38,7 +38,7 @@ use std::{
 };
 pub use subscription::*;
 pub use sum_tree::Bias;
-use sum_tree::{Dimensions, FilterCursor, SumTree, TreeMap, TreeSet};
+use sum_tree::{Dimensions, FilterCursor, SumTree, Summary, TreeMap, TreeSet};
 use undo_map::UndoMap;
 use util::debug_panic;
 
@@ -912,7 +912,8 @@ impl Buffer {
         let mut new_ropes =
             RopeBuilder::new(self.visible_text.cursor(0), self.deleted_text.cursor(0));
         let mut old_fragments = self.fragments.cursor::<FragmentTextSummary>(&None);
-        let mut new_fragments = old_fragments.slice(&edits.peek().unwrap().0.start, Bias::Right);
+        let mut new_fragments =
+            FragmentBuilder::new(old_fragments.slice(&edits.peek().unwrap().0.start, Bias::Right));
         new_ropes.append(new_fragments.summary().text);
 
         let mut fragment_start = old_fragments.start().visible;
@@ -1044,7 +1045,7 @@ impl Buffer {
         let (visible_text, deleted_text) = new_ropes.finish();
         drop(old_fragments);
 
-        self.snapshot.fragments = new_fragments;
+        self.snapshot.fragments = new_fragments.to_sum_tree(&None);
         self.snapshot.insertions.edit(new_insertions, ());
         self.snapshot.visible_text = visible_text;
         self.snapshot.deleted_text = deleted_text;
@@ -1127,8 +1128,9 @@ impl Buffer {
         let mut old_fragments = self
             .fragments
             .cursor::<Dimensions<VersionedFullOffset, usize>>(&cx);
-        let mut new_fragments =
-            old_fragments.slice(&VersionedFullOffset::Offset(ranges[0].start), Bias::Left);
+        let mut new_fragments = FragmentBuilder::new(
+            old_fragments.slice(&VersionedFullOffset::Offset(ranges[0].start), Bias::Left),
+        );
         new_ropes.append(new_fragments.summary().text);
 
         let mut fragment_start = old_fragments.start().0.full_offset();
@@ -1291,7 +1293,7 @@ impl Buffer {
         let (visible_text, deleted_text) = new_ropes.finish();
         drop(old_fragments);
 
-        self.snapshot.fragments = new_fragments;
+        self.snapshot.fragments = new_fragments.to_sum_tree(&None);
         self.snapshot.visible_text = visible_text;
         self.snapshot.deleted_text = deleted_text;
         self.snapshot.insertions.edit(new_insertions, ());
@@ -1303,7 +1305,7 @@ impl Buffer {
         new_text: &str,
         timestamp: clock::Lamport,
         insertion_offset: &mut u32,
-        new_fragments: &mut SumTree<Fragment>,
+        new_fragments: &mut FragmentBuilder,
         new_insertions: &mut Vec<sum_tree::Edit<InsertionFragment>>,
         insertion_slices: &mut Vec<InsertionSlice>,
         new_ropes: &mut RopeBuilder,
@@ -2836,6 +2838,37 @@ impl BufferSnapshot {
     }
 }
 
+struct FragmentBuilder {
+    fragments: Vec<Fragment>,
+    summary: FragmentSummary,
+}
+
+impl FragmentBuilder {
+    fn new(init: SumTree<Fragment>) -> Self {
+        Self {
+            summary: init.summary().clone(),
+            fragments: init.iter().cloned().collect(),
+        }
+    }
+    fn append(&mut self, items: SumTree<Fragment>, cx: &Option<clock::Global>) {
+        self.summary.add_summary(items.summary(), cx);
+        self.fragments.extend(items.iter().cloned());
+    }
+    fn push(&mut self, fragment: Fragment, cx: &Option<clock::Global>) {
+        self.append(SumTree::from_item(fragment, cx), cx);
+    }
+    fn to_sum_tree(self, cx: &Option<clock::Global>) -> SumTree<Fragment> {
+        if self.fragments.len() > 1024 {
+            SumTree::from_par_iter(self.fragments, cx)
+        } else {
+            SumTree::from_iter(self.fragments.into_iter(), cx)
+        }
+    }
+    fn summary(&self) -> &FragmentSummary {
+        &self.summary
+    }
+}
+
 struct RopeBuilder<'a> {
     old_visible_cursor: rope::Cursor<'a>,
     old_deleted_cursor: rope::Cursor<'a>,