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