From 076138375211d24e66c7642d4c845a01b81c5421 Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 28 Jun 2024 19:06:44 +0200 Subject: [PATCH] search: Improve performance of `replace_all` (#13654) Previously replace_all amounted to what could be achieved by repeatedly mashing "Replace" button, which had a bunch of overhead related to buffer state syncing. This commit gets rid of the automated button mashing, processing all of the replacements in one go. Fixes #13455 Release Notes: - Improved performance of "replace all" in buffer search and project search --- crates/editor/src/items.rs | 29 +++++++++++++++++++++++++++++ crates/search/src/buffer_search.rs | 4 +--- crates/search/src/project_search.rs | 4 +--- crates/workspace/src/searchable.rs | 27 +++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 5874789ddf..9c70a75e79 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -1102,6 +1102,35 @@ impl SearchableItem for Editor { }); } } + fn replace_all( + &mut self, + matches: &mut dyn Iterator, + query: &SearchQuery, + cx: &mut ViewContext, + ) { + let text = self.buffer.read(cx); + let text = text.snapshot(cx); + let mut edits = vec![]; + for m in matches { + let text = text.text_for_range(m.clone()).collect::>(); + let text: Cow<_> = if text.len() == 1 { + text.first().cloned().unwrap().into() + } else { + let joined_chunks = text.join(""); + joined_chunks.into() + }; + + if let Some(replacement) = query.replacement_for(&text) { + edits.push((m.clone(), Arc::from(&*replacement))); + } + } + + if !edits.is_empty() { + self.transact(cx, |this, cx| { + this.edit(edits, cx); + }); + } + } fn match_index_for_direction( &mut self, matches: &[Range], diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 2dd2b98b64..216d5b9f6f 100644 --- a/crates/search/src/buffer_search.rs +++ b/crates/search/src/buffer_search.rs @@ -1122,9 +1122,7 @@ impl BufferSearchBar { .as_ref() .clone() .with_replacement(self.replacement(cx)); - for m in matches { - searchable_item.replace(m, &query, cx); - } + searchable_item.replace_all(&mut matches.iter(), &query, cx); } } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 372f25db01..d879be9ab9 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -591,9 +591,7 @@ impl ProjectSearchView { } self.results_editor.update(cx, |editor, cx| { - for item in &match_ranges { - editor.replace(item, &query, cx); - } + editor.replace_all(&mut match_ranges.iter(), &query, cx); }); self.model.update(cx, |model, _cx| { diff --git a/crates/workspace/src/searchable.rs b/crates/workspace/src/searchable.rs index 0d037f6bed..b30b986c50 100644 --- a/crates/workspace/src/searchable.rs +++ b/crates/workspace/src/searchable.rs @@ -71,6 +71,16 @@ pub trait SearchableItem: Item + EventEmitter { fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext); fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext); fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext); + fn replace_all( + &mut self, + matches: &mut dyn Iterator, + query: &SearchQuery, + cx: &mut ViewContext, + ) { + for item in matches { + self.replace(item, query, cx); + } + } fn match_index_for_direction( &mut self, matches: &[Self::Match], @@ -123,6 +133,12 @@ pub trait SearchableItemHandle: ItemHandle { _: &SearchQuery, _: &mut WindowContext, ); + fn replace_all( + &self, + matches: &mut dyn Iterator>, + query: &SearchQuery, + cx: &mut WindowContext, + ); fn match_index_for_direction( &self, matches: &AnyVec, @@ -241,6 +257,17 @@ impl SearchableItemHandle for View { self.update(cx, |this, cx| this.replace(mat, query, cx)) } + fn replace_all( + &self, + matches: &mut dyn Iterator>, + query: &SearchQuery, + cx: &mut WindowContext, + ) { + self.update(cx, |this, cx| { + this.replace_all(&mut matches.map(|m| m.downcast_ref().unwrap()), query, cx); + }) + } + fn search_bar_visibility_changed(&self, visible: bool, cx: &mut WindowContext) { self.update(cx, |this, cx| { this.search_bar_visibility_changed(visible, cx)