Reuse buffer search queries on tab switch (#18281)

Before this change, with a large chunk of text as a search query (N*10^5
in my experiments) and the buffer search bar visible, switching between
editor tabs was very slow, even if the editors were N*10^2 lines long.

The slow switch was caused by Zed always re-creating the Aho-Corasick
queries, which is now reused.

Release Notes:

- Improved buffer search performance when switching tabs

Co-authored-by: Piotr Osiewicz <piotr@zed.dev>
This commit is contained in:
Kirill Bulatov 2024-09-24 18:21:26 +03:00 committed by GitHub
parent e87d6da2a6
commit 2470db4901
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -440,7 +440,7 @@ impl ToolbarItemView for BufferSearchBar {
));
self.active_searchable_item = Some(searchable_item_handle);
drop(self.update_matches(cx));
drop(self.update_matches(true, cx));
if !self.dismissed {
return ToolbarItemLocation::Secondary;
}
@ -701,7 +701,8 @@ impl BufferSearchBar {
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<()> {
let options = options.unwrap_or(self.default_options);
if query != self.query(cx) || self.search_options != options {
let updated = query != self.query(cx) || self.search_options != options;
if updated {
self.query_editor.update(cx, |query_editor, cx| {
query_editor.buffer().update(cx, |query_buffer, cx| {
let len = query_buffer.len(cx);
@ -712,7 +713,7 @@ impl BufferSearchBar {
self.clear_matches(cx);
cx.notify();
}
self.update_matches(cx)
self.update_matches(!updated, cx)
}
fn render_search_option_button(
@ -738,7 +739,7 @@ impl BufferSearchBar {
) {
self.search_options.toggle(search_option);
self.default_options = self.search_options;
drop(self.update_matches(cx));
drop(self.update_matches(false, cx));
cx.notify();
}
@ -841,7 +842,7 @@ impl BufferSearchBar {
editor::EditorEvent::Edited { .. } => {
self.smartcase(cx);
self.clear_matches(cx);
let search = self.update_matches(cx);
let search = self.update_matches(false, cx);
let width = editor.update(cx, |editor, cx| {
let text_layout_details = editor.text_layout_details(cx);
@ -879,7 +880,7 @@ impl BufferSearchBar {
fn on_active_searchable_item_event(&mut self, event: &SearchEvent, cx: &mut ViewContext<Self>) {
match event {
SearchEvent::MatchesInvalidated => {
drop(self.update_matches(cx));
drop(self.update_matches(false, cx));
}
SearchEvent::ActiveMatchChanged => self.update_match_index(cx),
}
@ -897,7 +898,7 @@ impl BufferSearchBar {
if let Some(active_item) = self.active_searchable_item.as_mut() {
self.selection_search_enabled = !self.selection_search_enabled;
active_item.toggle_filtered_search_ranges(self.selection_search_enabled, cx);
drop(self.update_matches(cx));
drop(self.update_matches(false, cx));
cx.notify();
}
}
@ -937,7 +938,11 @@ impl BufferSearchBar {
.extend(active_item_matches);
}
fn update_matches(&mut self, cx: &mut ViewContext<Self>) -> oneshot::Receiver<()> {
fn update_matches(
&mut self,
reuse_existing_query: bool,
cx: &mut ViewContext<Self>,
) -> oneshot::Receiver<()> {
let (done_tx, done_rx) = oneshot::channel();
let query = self.query(cx);
self.pending_search.take();
@ -949,44 +954,51 @@ impl BufferSearchBar {
let _ = done_tx.send(());
cx.notify();
} else {
let query: Arc<_> = if self.search_options.contains(SearchOptions::REGEX) {
match SearchQuery::regex(
query,
self.search_options.contains(SearchOptions::WHOLE_WORD),
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
false,
Default::default(),
Default::default(),
None,
) {
Ok(query) => query.with_replacement(self.replacement(cx)),
Err(_) => {
self.query_contains_error = true;
self.clear_active_searchable_item_matches(cx);
cx.notify();
return done_rx;
}
}
let query: Arc<_> = if let Some(search) =
self.active_search.take().filter(|_| reuse_existing_query)
{
search
} else {
match SearchQuery::text(
query,
self.search_options.contains(SearchOptions::WHOLE_WORD),
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
false,
Default::default(),
Default::default(),
None,
) {
Ok(query) => query.with_replacement(self.replacement(cx)),
Err(_) => {
self.query_contains_error = true;
self.clear_active_searchable_item_matches(cx);
cx.notify();
return done_rx;
if self.search_options.contains(SearchOptions::REGEX) {
match SearchQuery::regex(
query,
self.search_options.contains(SearchOptions::WHOLE_WORD),
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
false,
Default::default(),
Default::default(),
None,
) {
Ok(query) => query.with_replacement(self.replacement(cx)),
Err(_) => {
self.query_contains_error = true;
self.clear_active_searchable_item_matches(cx);
cx.notify();
return done_rx;
}
}
} else {
match SearchQuery::text(
query,
self.search_options.contains(SearchOptions::WHOLE_WORD),
self.search_options.contains(SearchOptions::CASE_SENSITIVE),
false,
Default::default(),
Default::default(),
None,
) {
Ok(query) => query.with_replacement(self.replacement(cx)),
Err(_) => {
self.query_contains_error = true;
self.clear_active_searchable_item_matches(cx);
cx.notify();
return done_rx;
}
}
}
}
.into();
.into()
};
self.active_search = Some(query.clone());
let query_text = query.as_str().to_string();