From 88bfe5acb05a17aab797ef7a45218ea2455b7af6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 25 Feb 2022 16:20:02 +0100 Subject: [PATCH] Allow splitting project find and maintain the searches in sync --- crates/find/src/project_find.rs | 71 ++++++++++++++++++++++++++++----- crates/project/src/search.rs | 56 +++++++++++++++++++++----- 2 files changed, 107 insertions(+), 20 deletions(-) diff --git a/crates/find/src/project_find.rs b/crates/find/src/project_find.rs index 99f5105a38..37cda8a7cb 100644 --- a/crates/find/src/project_find.rs +++ b/crates/find/src/project_find.rs @@ -30,6 +30,7 @@ pub fn init(cx: &mut MutableAppContext) { struct ProjectFind { project: ModelHandle, excerpts: ModelHandle, + query: Option, pending_search: Option>>, highlighted_ranges: Vec>, } @@ -55,7 +56,8 @@ impl ProjectFind { Self { project, excerpts: cx.add_model(|_| MultiBuffer::new(replica_id)), - pending_search: None, + query: Default::default(), + pending_search: Default::default(), highlighted_ranges: Default::default(), } } @@ -63,7 +65,8 @@ impl ProjectFind { fn search(&mut self, query: SearchQuery, cx: &mut ModelContext) { let search = self .project - .update(cx, |project, cx| project.search(query, cx)); + .update(cx, |project, cx| project.search(query.clone(), cx)); + self.query = Some(query.clone()); self.highlighted_ranges.clear(); self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move { let matches = search.await; @@ -106,7 +109,7 @@ impl workspace::Item for ProjectFind { ) -> Self::View { let settings = workspace.settings(); let excerpts = model.read(cx).excerpts.clone(); - cx.observe(&model, ProjectFindView::on_model_changed) + cx.observe(&model, |this, _, cx| this.model_changed(true, cx)) .detach(); ProjectFindView { model, @@ -236,6 +239,36 @@ impl workspace::ItemView for ProjectFindView { ) -> Task> { unreachable!("save_as should not have been called") } + + fn clone_on_split(&self, cx: &mut ViewContext) -> Option + where + Self: Sized, + { + let query_editor = cx.add_view(|cx| { + Editor::single_line( + self.settings.clone(), + Some(|theme| theme.find.editor.input.clone()), + cx, + ) + }); + let results_editor = self.results_editor.update(cx, |editor, cx| { + cx.add_view(|cx| editor.clone_on_split(cx).unwrap()) + }); + cx.observe(&self.model, |this, _, cx| this.model_changed(true, cx)) + .detach(); + let mut view = Self { + model: self.model.clone(), + query_editor, + results_editor, + case_sensitive: self.case_sensitive, + whole_word: self.whole_word, + regex: self.regex, + query_contains_error: self.query_contains_error, + settings: self.settings.clone(), + }; + view.model_changed(false, cx); + Some(view) + } } impl ProjectFindView { @@ -247,7 +280,7 @@ impl ProjectFindView { fn search(&mut self, _: &Search, cx: &mut ViewContext) { let text = self.query_editor.read(cx).text(cx); let query = if self.regex { - match SearchQuery::regex(text, self.case_sensitive, self.whole_word) { + match SearchQuery::regex(text, self.whole_word, self.case_sensitive) { Ok(query) => query, Err(_) => { self.query_contains_error = true; @@ -256,7 +289,7 @@ impl ProjectFindView { } } } else { - SearchQuery::text(text, self.case_sensitive, self.whole_word) + SearchQuery::text(text, self.whole_word, self.case_sensitive) }; self.model.update(cx, |model, cx| model.search(query, cx)); @@ -285,16 +318,36 @@ impl ProjectFindView { } } - fn on_model_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { - let highlighted_ranges = self.model.read(cx).highlighted_ranges.clone(); + fn model_changed(&mut self, reset_selections: bool, cx: &mut ViewContext) { + let model = self.model.read(cx); + let highlighted_ranges = model.highlighted_ranges.clone(); + if let Some(query) = model.query.clone() { + self.case_sensitive = query.case_sensitive(); + self.whole_word = query.whole_word(); + self.regex = query.is_regex(); + self.query_editor.update(cx, |query_editor, cx| { + if query_editor.text(cx) != query.as_str() { + query_editor.buffer().update(cx, |query_buffer, cx| { + let len = query_buffer.read(cx).len(); + query_buffer.edit([0..len], query.as_str(), cx); + }); + } + }); + } + if !highlighted_ranges.is_empty() { let theme = &self.settings.borrow().theme.find; self.results_editor.update(cx, |editor, cx| { editor.highlight_ranges::(highlighted_ranges, theme.match_background, cx); - editor.select_ranges([0..0], Some(Autoscroll::Fit), cx); + if reset_selections { + editor.select_ranges([0..0], Some(Autoscroll::Fit), cx); + } }); - cx.focus(&self.results_editor); + if self.query_editor.is_focused(cx) { + cx.focus(&self.results_editor); + } } + cx.notify(); } diff --git a/crates/project/src/search.rs b/crates/project/src/search.rs index dd2699238d..4f89c68aa2 100644 --- a/crates/project/src/search.rs +++ b/crates/project/src/search.rs @@ -13,12 +13,16 @@ use std::{ pub enum SearchQuery { Text { search: Arc>, - query: String, + query: Arc, whole_word: bool, + case_sensitive: bool, }, Regex { - multiline: bool, regex: Regex, + query: Arc, + multiline: bool, + whole_word: bool, + case_sensitive: bool, }, } @@ -31,13 +35,15 @@ impl SearchQuery { .build(&[&query]); Self::Text { search: Arc::new(search), - query, + query: Arc::from(query), whole_word, + case_sensitive, } } pub fn regex(query: impl ToString, whole_word: bool, case_sensitive: bool) -> Result { let mut query = query.to_string(); + let initial_query = Arc::from(query.as_str()); if whole_word { let mut word_query = String::new(); word_query.push_str("\\b"); @@ -51,7 +57,13 @@ impl SearchQuery { .case_insensitive(!case_sensitive) .multi_line(multiline) .build()?; - Ok(Self::Regex { multiline, regex }) + Ok(Self::Regex { + regex, + query: initial_query, + multiline, + whole_word, + case_sensitive, + }) } pub fn detect(&self, stream: T) -> Result { @@ -60,7 +72,7 @@ impl SearchQuery { } match self { - SearchQuery::Text { search, .. } => { + Self::Text { search, .. } => { let mat = search.stream_find_iter(stream).next(); match mat { Some(Ok(_)) => Ok(true), @@ -68,7 +80,9 @@ impl SearchQuery { None => Ok(false), } } - SearchQuery::Regex { multiline, regex } => { + Self::Regex { + regex, multiline, .. + } => { let mut reader = BufReader::new(stream); if *multiline { let mut text = String::new(); @@ -99,7 +113,7 @@ impl SearchQuery { let mut matches = Vec::new(); match self { - SearchQuery::Text { + Self::Text { search, whole_word, .. } => { for (ix, mat) in search @@ -123,7 +137,9 @@ impl SearchQuery { matches.push(mat.start()..mat.end()) } } - SearchQuery::Regex { multiline, regex } => { + Self::Regex { + regex, multiline, .. + } => { if *multiline { let text = rope.to_string(); for (ix, mat) in regex.find_iter(&text).enumerate() { @@ -161,10 +177,28 @@ impl SearchQuery { matches } - fn as_str(&self) -> &str { + pub fn as_str(&self) -> &str { match self { - SearchQuery::Text { query, .. } => query.as_str(), - SearchQuery::Regex { regex, .. } => regex.as_str(), + Self::Text { query, .. } => query.as_ref(), + Self::Regex { query, .. } => query.as_ref(), } } + + pub fn whole_word(&self) -> bool { + match self { + Self::Text { whole_word, .. } => *whole_word, + Self::Regex { whole_word, .. } => *whole_word, + } + } + + pub fn case_sensitive(&self) -> bool { + match self { + Self::Text { case_sensitive, .. } => *case_sensitive, + Self::Regex { case_sensitive, .. } => *case_sensitive, + } + } + + pub fn is_regex(&self) -> bool { + matches!(self, Self::Regex { .. }) + } }