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
This commit is contained in:
Piotr Osiewicz 2024-06-28 19:06:44 +02:00 committed by GitHub
parent b616f9c27f
commit 0761383752
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 58 additions and 6 deletions

View File

@ -1102,6 +1102,35 @@ impl SearchableItem for Editor {
});
}
}
fn replace_all(
&mut self,
matches: &mut dyn Iterator<Item = &Self::Match>,
query: &SearchQuery,
cx: &mut ViewContext<Self>,
) {
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::<Vec<_>>();
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<Anchor>],

View File

@ -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);
}
}
}

View File

@ -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| {

View File

@ -71,6 +71,16 @@ pub trait SearchableItem: Item + EventEmitter<SearchEvent> {
fn activate_match(&mut self, index: usize, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>);
fn replace(&mut self, _: &Self::Match, _: &SearchQuery, _: &mut ViewContext<Self>);
fn replace_all(
&mut self,
matches: &mut dyn Iterator<Item = &Self::Match>,
query: &SearchQuery,
cx: &mut ViewContext<Self>,
) {
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<Item = any_vec::element::ElementRef<'_, dyn Send>>,
query: &SearchQuery,
cx: &mut WindowContext,
);
fn match_index_for_direction(
&self,
matches: &AnyVec<dyn Send>,
@ -241,6 +257,17 @@ impl<T: SearchableItem> SearchableItemHandle for View<T> {
self.update(cx, |this, cx| this.replace(mat, query, cx))
}
fn replace_all(
&self,
matches: &mut dyn Iterator<Item = any_vec::element::ElementRef<'_, dyn Send>>,
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)