diff --git a/Cargo.lock b/Cargo.lock index 66b0d88b22..bbe77d8c75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5520,6 +5520,7 @@ dependencies = [ "serde_json", "settings", "smallvec", + "smol", "theme", "unindent", "util", diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index 1f6c9582be..30802801a6 100644 --- a/crates/search/Cargo.toml +++ b/crates/search/Cargo.toml @@ -23,6 +23,7 @@ log = { version = "0.4.16", features = ["kv_unstable_serde"] } postage = { version = "0.4.1", features = ["futures-traits"] } serde = { version = "1.0", features = ["derive", "rc"] } smallvec = { version = "1.6", features = ["union"] } +smol = "1.2" [dev-dependencies] editor = { path = "../editor", features = ["test-support"] } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 9d2f31e269..a0f29738b0 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -18,6 +18,7 @@ use settings::Settings; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, + mem, ops::Range, path::PathBuf, sync::Arc, @@ -67,6 +68,7 @@ struct ProjectSearch { pending_search: Option>>, match_ranges: Vec>, active_query: Option, + search_id: usize, } pub struct ProjectSearchView { @@ -78,6 +80,7 @@ pub struct ProjectSearchView { regex: bool, query_contains_error: bool, active_match_index: Option, + search_id: usize, } pub struct ProjectSearchBar { @@ -98,6 +101,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: Default::default(), active_query: None, + search_id: 0, } } @@ -110,6 +114,7 @@ impl ProjectSearch { pending_search: Default::default(), match_ranges: self.match_ranges.clone(), active_query: self.active_query.clone(), + search_id: self.search_id, }) } @@ -117,21 +122,25 @@ impl ProjectSearch { let search = self .project .update(cx, |project, cx| project.search(query.clone(), cx)); + self.search_id += 1; self.active_query = Some(query); self.match_ranges.clear(); self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { let matches = search.await.log_err()?; - if let Some(this) = this.upgrade(&cx) { + let this = this.upgrade(&cx)?; + let mut matches = matches.into_iter().collect::>(); + this.update(&mut cx, |this, cx| { + this.match_ranges.clear(); + matches.sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); + this.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx)); + }); + + for matches_chunk in matches.chunks(100) { this.update(&mut cx, |this, cx| { - this.match_ranges.clear(); - let mut matches = matches.into_iter().collect::>(); - matches - .sort_by_key(|(buffer, _)| buffer.read(cx).file().map(|file| file.path())); this.excerpts.update(cx, |excerpts, cx| { - excerpts.clear(cx); - for (buffer, buffer_matches) in matches { + for (buffer, buffer_matches) in matches_chunk { let ranges_to_highlight = excerpts.push_excerpts_with_context_lines( - buffer, + buffer.clone(), buffer_matches.clone(), 1, cx, @@ -139,10 +148,19 @@ impl ProjectSearch { this.match_ranges.extend(ranges_to_highlight); } }); - this.pending_search.take(); + cx.notify(); }); + + // Don't starve the main thread when adding lots of excerpts. + smol::future::yield_now().await; } + + this.update(&mut cx, |this, cx| { + this.pending_search.take(); + cx.notify(); + }); + None })); cx.notify(); @@ -398,7 +416,7 @@ impl ProjectSearchView { whole_word = active_query.whole_word(); } } - cx.observe(&model, |this, _, cx| this.model_changed(true, cx)) + cx.observe(&model, |this, _, cx| this.model_changed(cx)) .detach(); let query_editor = cx.add_view(|cx| { @@ -433,6 +451,7 @@ impl ProjectSearchView { .detach(); let mut this = ProjectSearchView { + search_id: model.read(cx).search_id, model, query_editor, results_editor, @@ -442,7 +461,7 @@ impl ProjectSearchView { query_contains_error: false, active_match_index: None, }; - this.model_changed(false, cx); + this.model_changed(cx); this } @@ -562,11 +581,13 @@ impl ProjectSearchView { cx.focus(&self.results_editor); } - fn model_changed(&mut self, reset_selections: bool, cx: &mut ViewContext) { + fn model_changed(&mut self, cx: &mut ViewContext) { let match_ranges = self.model.read(cx).match_ranges.clone(); if match_ranges.is_empty() { self.active_match_index = None; } else { + let prev_search_id = mem::replace(&mut self.search_id, self.model.read(cx).search_id); + let reset_selections = self.search_id != prev_search_id; self.results_editor.update(cx, |editor, cx| { if reset_selections { editor.change_selections(Some(Autoscroll::fit()), cx, |s| {