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
182182 if self.query(cx).is_empty() {
183183 return None;
184184 }
185− let matches = self
185+ let matches_count = self
186186 .searchable_items_with_matches
187− .get(&searchable_item.downgrade())?;
187+ .get(&searchable_item.downgrade())
188+ .map(Vec::len)
189+ .unwrap_or(0);
188190 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))
190192 } else {
191193 match_color = Color::Error; // No matches found
192194 None
@@ -843,6 +845,16 @@ impl BufferSearchBar { · 650 unmodified lines
843845 fn toggle_whole_word(&mut self, _: &ToggleWholeWord, cx: &mut ViewContext<Self>) {
844846 self.toggle_search_option(SearchOptions::WHOLE_WORD, cx)
845847 }
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+
846858 fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
847859 let mut active_item_matches = None;
848860 for (searchable_item, matches) in self.searchable_items_with_matches.drain() {
@@ -869,8 +881,7 @@ impl BufferSearchBar { · 20 unmodified lines
869881 if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
870882 self.query_contains_error = false;
871883 if query.is_empty() {
872− self.active_match_index.take();
873− active_searchable_item.clear_matches(cx);
884+ self.clear_active_searchable_item_matches(cx);
874885 let _ = done_tx.send(());
875886 cx.notify();
876887 } else {
@@ -886,7 +897,7 @@ impl BufferSearchBar { · 9 unmodified lines
886897 Ok(query) => query.with_replacement(self.replacement(cx)),
887898 Err(_) => {
888899 self.query_contains_error = true;
889− self.active_match_index = None;
900+ self.clear_active_searchable_item_matches(cx);
890901 cx.notify();
891902 return done_rx;
892903 }
@@ -903,7 +914,7 @@ impl BufferSearchBar { · 10 unmodified lines
903914 Ok(query) => query.with_replacement(self.replacement(cx)),
904915 Err(_) => {
905916 self.query_contains_error = true;
906− self.active_match_index = None;
917+ self.clear_active_searchable_item_matches(cx);
907918 cx.notify();
908919 return done_rx;
909920 }
@@ -1910,4 +1921,43 @@ mod tests { · 1000 unmodified lines
19101921 .unindent()
19111922 );
19121923 }
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+ }
19131963 }