From b50ad4fdf88711a206a607799fae476af97e28a2 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Thu, 19 Mar 2026 15:23:17 +0100 Subject: [PATCH] text: batch fragment insertions before turning them into a SumTree 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 --- crates/editor_benchmarks/src/main.rs | 2 -- crates/text/src/text.rs | 47 +++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/crates/editor_benchmarks/src/main.rs b/crates/editor_benchmarks/src/main.rs index 0a6888bf30d390c1c975912ff39bdba556937779..52bf9137fd8419b9137eaf4d970ea9acf881a961 100644 --- a/crates/editor_benchmarks/src/main.rs +++ b/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, cx: &mut AsyncWindowContext| { - dbg!("A"); let find_task = weak.update_in(cx, |editor, window, cx| { editor.find_matches(query.clone(), window, cx) })?; diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index 026f1272790740c9c2277004e8e96800d87bab15..d65753802f35669d84cd150f84afce3ab335d466 100644 --- a/crates/text/src/text.rs +++ b/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::(&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::>(&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, + new_fragments: &mut FragmentBuilder, new_insertions: &mut Vec>, insertion_slices: &mut Vec, new_ropes: &mut RopeBuilder, @@ -2836,6 +2838,37 @@ impl BufferSnapshot { } } +struct FragmentBuilder { + fragments: Vec, + summary: FragmentSummary, +} + +impl FragmentBuilder { + fn new(init: SumTree) -> Self { + Self { + summary: init.summary().clone(), + fragments: init.iter().cloned().collect(), + } + } + fn append(&mut self, items: SumTree, cx: &Option) { + self.summary.add_summary(items.summary(), cx); + self.fragments.extend(items.iter().cloned()); + } + fn push(&mut self, fragment: Fragment, cx: &Option) { + self.append(SumTree::from_item(fragment, cx), cx); + } + fn to_sum_tree(self, cx: &Option) -> SumTree { + 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>,