History

Clear search results on invalid query input (#7958)

Fixes a bug in the buffer search bar: Clears results of a previous successfull search when the user enters invalid search request. Steps to reproduce the bug: 1. Switch to Regex search mode. 2. Enter a valid search query that produces several matches. 3. Add a symbol to the search query that makes it an invalid regexp. 4. Switch to the editor and walk through the code back and forth. Expected result: All the match highlightings after step 2 are cleared, search bar indicates absence of the last search matches. Actual: The results from the last valid search are highlighted, search bar indicates presence of matches. Potentially, the same effect may occur when searching in the simple text mode, or when clearing the search query in some circumstances, so I made the fix for all those cases, though I wasn't able to reproduce them manually. The bug: https://github.com/zed-industries/zed/assets/2101250/1c50b98c-ae8e-4a9c-8ff5-1e5c63027ec3 After the fix: https://github.com/zed-industries/zed/assets/2101250/e3eedf8c-2e3e-41ee-81cc-c2c9d919fba3 Release Notes: - Clear search results on invalid query input

Andrew Lygin committed 2 years ago

Commit 6d9b8cc1 parent 4c781b61 file · +57 −7Browse files

Unified · Split

crates/search/src/buffer_search.rs+57−7
@@ -182,11 +182,13 @@ impl Render for BufferSearchBar { · 181 unmodified lines
182 if self.query(cx).is_empty() {182 if self.query(cx).is_empty() {
183 return None;183 return None;
184 }184 }
185 let matches = self185 let matches_count = self
186 .searchable_items_with_matches186 .searchable_items_with_matches
187 .get(&searchable_item.downgrade())?;187 .get(&searchable_item.downgrade())
188 .map(Vec::len)
189 .unwrap_or(0);
188 if let Some(match_ix) = self.active_match_index {190 if let Some(match_ix) = self.active_match_index {
189 Some(format!("{}/{}", match_ix + 1, matches.len()))191 Some(format!("{}/{}", match_ix + 1, matches_count))
190 } else {192 } else {
191 match_color = Color::Error; // No matches found193 match_color = Color::Error; // No matches found
192 None194 None
@@ -843,6 +845,16 @@ impl BufferSearchBar { · 650 unmodified lines
843 fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {845 fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
844 self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)846 self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
845 }847 }
848
849 fn clear_active_searchable_item_matches(&mut self, cx: &mut WindowContext) {
850 if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
851 self.active_match_index = None;
852 self.searchable_items_with_matches
853 .remove(&active_searchable_item.downgrade());
854 active_searchable_item.clear_matches(cx);
855 }
856 }
857
846 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {858 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
847 let mut active_item_matches = None;859 let mut active_item_matches = None;
848 for (searchable_item, matches) in self.searchable_items_with_matches.drain() {860 for (searchable_item, matches) in self.searchable_items_with_matches.drain() {
@@ -869,8 +881,7 @@ impl BufferSearchBar { · 20 unmodified lines
869 if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {881 if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
870 self.query_contains_error = false;882 self.query_contains_error = false;
871 if query.is_empty() {883 if query.is_empty() {
872 self.active_match_index.take();884 self.clear_active_searchable_item_matches(cx);
873 active_searchable_item.clear_matches(cx);
874 let _ = done_tx.send(());885 let _ = done_tx.send(());
875 cx.notify();886 cx.notify();
876 } else {887 } else {
@@ -886,7 +897,7 @@ impl BufferSearchBar { · 9 unmodified lines
886 Ok(query) => query.with_replacement(self.replacement(cx)),897 Ok(query) => query.with_replacement(self.replacement(cx)),
887 Err(_) => {898 Err(_) => {
888 self.query_contains_error = true;899 self.query_contains_error = true;
889 self.active_match_index = None;900 self.clear_active_searchable_item_matches(cx);
890 cx.notify();901 cx.notify();
891 return done_rx;902 return done_rx;
892 }903 }
@@ -903,7 +914,7 @@ impl BufferSearchBar { · 10 unmodified lines
903 Ok(query) => query.with_replacement(self.replacement(cx)),914 Ok(query) => query.with_replacement(self.replacement(cx)),
904 Err(_) => {915 Err(_) => {
905 self.query_contains_error = true;916 self.query_contains_error = true;
906 self.active_match_index = None;917 self.clear_active_searchable_item_matches(cx);
907 cx.notify();918 cx.notify();
908 return done_rx;919 return done_rx;
909 }920 }
@@ -1910,4 +1921,43 @@ mod tests { · 1000 unmodified lines
1910 .unindent()1921 .unindent()
1911 );1922 );
1912 }1923 }
1924
1925 #[gpui::test]
1926 async fn test_invalid_regexp_search_after_valid(cx: &mut TestAppContext) {
1927 let (editor, search_bar, cx) = init_test(cx);
1928 let display_points_of = |background_highlights: Vec<(Range<DisplayPoint>, Hsla)>| {
1929 background_highlights
1930 .into_iter()
1931 .map(|(range, _)| range)
1932 .collect::<Vec<_>>()
1933 };
1934 // Search using valid regexp
1935 search_bar
1936 .update(cx, |search_bar, cx| {
1937 search_bar.activate_search_mode(SearchMode::Regex, cx);
1938 search_bar.search("expression", None, cx)
1939 })
1940 .await
1941 .unwrap();
1942 editor.update(cx, |editor, cx| {
1943 assert_eq!(
1944 display_points_of(editor.all_text_background_highlights(cx)),
1945 &[
1946 DisplayPoint::new(0, 10)..DisplayPoint::new(0, 20),
1947 DisplayPoint::new(1, 9)..DisplayPoint::new(1, 19),
1948 ],
1949 );
1950 });
1951
1952 // Now, the expression is invalid
1953 search_bar
1954 .update(cx, |search_bar, cx| {
1955 search_bar.search("expression (", None, cx)
1956 })
1957 .await
1958 .unwrap_err();
1959 editor.update(cx, |editor, cx| {
1960 assert!(display_points_of(editor.all_text_background_highlights(cx)).is_empty(),);
1961 });
1962 }
1913}1963}