Show regex query error under the search bar (#33638)

Mikal Sande and Danilo Leal created

Closes #17223

Release Notes:

- Show regex parsing errors under the search bar for buffer and project
search.

---------

Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

crates/search/src/buffer_search.rs  | 25 +++++++++++++++++--------
crates/search/src/project_search.rs | 15 ++++++++++++++-
2 files changed, 31 insertions(+), 9 deletions(-)

Detailed changes

crates/search/src/buffer_search.rs 🔗

@@ -101,7 +101,7 @@ pub struct BufferSearchBar {
     search_options: SearchOptions,
     default_options: SearchOptions,
     configured_options: SearchOptions,
-    query_contains_error: bool,
+    query_error: Option<String>,
     dismissed: bool,
     search_history: SearchHistory,
     search_history_cursor: SearchHistoryCursor,
@@ -217,7 +217,7 @@ impl Render for BufferSearchBar {
         if in_replace {
             key_context.add("in_replace");
         }
-        let editor_border = if self.query_contains_error {
+        let editor_border = if self.query_error.is_some() {
             Color::Error.color(cx)
         } else {
             cx.theme().colors().border
@@ -469,6 +469,14 @@ impl Render for BufferSearchBar {
                 )
         });
 
+        let query_error_line = self.query_error.as_ref().map(|error| {
+            Label::new(error)
+                .size(LabelSize::Small)
+                .color(Color::Error)
+                .mt_neg_1()
+                .ml_2()
+        });
+
         v_flex()
             .id("buffer_search")
             .gap_2()
@@ -524,6 +532,7 @@ impl Render for BufferSearchBar {
                     .w_full()
                 },
             ))
+            .children(query_error_line)
             .children(replace_line)
     }
 }
@@ -728,7 +737,7 @@ impl BufferSearchBar {
             configured_options: search_options,
             search_options,
             pending_search: None,
-            query_contains_error: false,
+            query_error: None,
             dismissed: true,
             search_history: SearchHistory::new(
                 Some(MAX_BUFFER_SEARCH_HISTORY_SIZE),
@@ -1230,7 +1239,7 @@ impl BufferSearchBar {
         self.pending_search.take();
 
         if let Some(active_searchable_item) = self.active_searchable_item.as_ref() {
-            self.query_contains_error = false;
+            self.query_error = None;
             if query.is_empty() {
                 self.clear_active_searchable_item_matches(window, cx);
                 let _ = done_tx.send(());
@@ -1255,8 +1264,8 @@ impl BufferSearchBar {
                             None,
                         ) {
                             Ok(query) => query.with_replacement(self.replacement(cx)),
-                            Err(_) => {
-                                self.query_contains_error = true;
+                            Err(e) => {
+                                self.query_error = Some(e.to_string());
                                 self.clear_active_searchable_item_matches(window, cx);
                                 cx.notify();
                                 return done_rx;
@@ -1274,8 +1283,8 @@ impl BufferSearchBar {
                             None,
                         ) {
                             Ok(query) => query.with_replacement(self.replacement(cx)),
-                            Err(_) => {
-                                self.query_contains_error = true;
+                            Err(e) => {
+                                self.query_error = Some(e.to_string());
                                 self.clear_active_searchable_item_matches(window, cx);
                                 cx.notify();
                                 return done_rx;

crates/search/src/project_search.rs 🔗

@@ -208,6 +208,7 @@ pub struct ProjectSearchView {
     included_opened_only: bool,
     regex_language: Option<Arc<Language>>,
     _subscriptions: Vec<Subscription>,
+    query_error: Option<String>,
 }
 
 #[derive(Debug, Clone)]
@@ -876,6 +877,7 @@ impl ProjectSearchView {
             included_opened_only: false,
             regex_language: None,
             _subscriptions: subscriptions,
+            query_error: None,
         };
         this.entity_changed(window, cx);
         this
@@ -1209,14 +1211,16 @@ impl ProjectSearchView {
                     if should_unmark_error {
                         cx.notify();
                     }
+                    self.query_error = None;
 
                     Some(query)
                 }
-                Err(_e) => {
+                Err(e) => {
                     let should_mark_error = self.panels_with_errors.insert(InputPanel::Query);
                     if should_mark_error {
                         cx.notify();
                     }
+                    self.query_error = Some(e.to_string());
 
                     None
                 }
@@ -2291,6 +2295,14 @@ impl Render for ProjectSearchBar {
             key_context.add("in_replace");
         }
 
+        let query_error_line = search.query_error.as_ref().map(|error| {
+            Label::new(error)
+                .size(LabelSize::Small)
+                .color(Color::Error)
+                .mt_neg_1()
+                .ml_2()
+        });
+
         v_flex()
             .py(px(1.0))
             .key_context(key_context)
@@ -2342,6 +2354,7 @@ impl Render for ProjectSearchBar {
             .gap_2()
             .w_full()
             .child(search_line)
+            .children(query_error_line)
             .children(replace_line)
             .children(filter_line)
     }