Highlight regex in the project search input (#25147)

Kirill Bulatov created

Follow-up of https://github.com/zed-industries/zed/pull/25005

Project search also has regex search option, highlight it the same way
too.

Release Notes:

- N/A

Change summary

crates/search/src/project_search.rs | 79 ++++++++++++++++++++++++------
1 file changed, 62 insertions(+), 17 deletions(-)

Detailed changes

crates/search/src/project_search.rs 🔗

@@ -3,6 +3,7 @@ use crate::{
     ReplaceAll, ReplaceNext, SearchOptions, SelectNextMatch, SelectPrevMatch, ToggleCaseSensitive,
     ToggleIncludeIgnored, ToggleRegex, ToggleReplace, ToggleWholeWord,
 };
+use anyhow::Context as _;
 use collections::{HashMap, HashSet};
 use editor::{
     actions::SelectAll, items::active_match_index, scroll::Autoscroll, Anchor, Editor,
@@ -15,7 +16,7 @@ use gpui::{
     ParentElement, Point, Render, SharedString, Styled, Subscription, Task, TextStyle,
     UpdateGlobal, WeakEntity, Window,
 };
-use language::Buffer;
+use language::{Buffer, Language};
 use menu::Confirm;
 use project::{
     search::{SearchInputKind, SearchQuery},
@@ -29,6 +30,7 @@ use std::{
     ops::{Not, Range},
     path::Path,
     pin::pin,
+    sync::Arc,
 };
 use theme::ThemeSettings;
 use ui::{
@@ -167,6 +169,7 @@ pub struct ProjectSearchView {
     filters_enabled: bool,
     replace_enabled: bool,
     included_opened_only: bool,
+    regex_language: Option<Arc<Language>>,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -613,6 +616,7 @@ impl ProjectSearchView {
                 self.current_settings(),
             );
         });
+        self.adjust_query_regex_language(cx);
     }
 
     fn toggle_opened_only(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
@@ -791,6 +795,22 @@ impl ProjectSearchView {
             }
         }));
 
+        let languages = project.read(cx).languages().clone();
+        cx.spawn(|project_search_view, mut cx| async move {
+            let regex_language = languages
+                .language_for_name("regex")
+                .await
+                .context("loading regex language")?;
+            project_search_view
+                .update(&mut cx, |project_search_view, cx| {
+                    project_search_view.regex_language = Some(regex_language);
+                    project_search_view.adjust_query_regex_language(cx);
+                })
+                .ok();
+            anyhow::Ok(())
+        })
+        .detach_and_log_err(cx);
+
         // Check if Worktrees have all been previously indexed
         let mut this = ProjectSearchView {
             workspace,
@@ -808,6 +828,7 @@ impl ProjectSearchView {
             filters_enabled,
             replace_enabled: false,
             included_opened_only: false,
+            regex_language: None,
             _subscriptions: subscriptions,
         };
         this.entity_changed(window, cx);
@@ -1338,6 +1359,28 @@ impl ProjectSearchView {
     pub fn results_editor(&self) -> &Entity<Editor> {
         &self.results_editor
     }
+
+    fn adjust_query_regex_language(&self, cx: &mut App) {
+        let enable = self.search_options.contains(SearchOptions::REGEX);
+        let query_buffer = self
+            .query_editor
+            .read(cx)
+            .buffer()
+            .read(cx)
+            .as_singleton()
+            .expect("query editor should be backed by a singleton buffer");
+        if enable {
+            if let Some(regex_language) = self.regex_language.clone() {
+                query_buffer.update(cx, |query_buffer, cx| {
+                    query_buffer.set_language(Some(regex_language), cx);
+                })
+            }
+        } else {
+            query_buffer.update(cx, |query_buffer, cx| {
+                query_buffer.set_language(None, cx);
+            })
+        }
+    }
 }
 
 fn buffer_search_query(
@@ -1461,7 +1504,6 @@ impl ProjectSearchBar {
                     search_view.search(cx);
                 }
             });
-
             cx.notify();
             true
         } else {
@@ -1666,31 +1708,34 @@ impl ProjectSearchBar {
     }
 
     fn render_text_input(&self, editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
+        let (color, use_syntax) = if editor.read(cx).read_only(cx) {
+            (cx.theme().colors().text_disabled, false)
+        } else {
+            (cx.theme().colors().text, true)
+        };
         let settings = ThemeSettings::get_global(cx);
         let text_style = TextStyle {
-            color: if editor.read(cx).read_only(cx) {
-                cx.theme().colors().text_disabled
-            } else {
-                cx.theme().colors().text
-            },
+            color,
             font_family: settings.buffer_font.family.clone(),
             font_features: settings.buffer_font.features.clone(),
             font_fallbacks: settings.buffer_font.fallbacks.clone(),
             font_size: rems(0.875).into(),
             font_weight: settings.buffer_font.weight,
             line_height: relative(1.3),
-            ..Default::default()
+            ..TextStyle::default()
         };
 
-        EditorElement::new(
-            editor,
-            EditorStyle {
-                background: cx.theme().colors().editor_background,
-                local_player: cx.theme().players().local(),
-                text: text_style,
-                ..Default::default()
-            },
-        )
+        let mut editor_style = EditorStyle {
+            background: cx.theme().colors().editor_background,
+            local_player: cx.theme().players().local(),
+            text: text_style,
+            ..EditorStyle::default()
+        };
+        if use_syntax {
+            editor_style.syntax = cx.theme().syntax().clone();
+        }
+
+        EditorElement::new(editor, editor_style)
     }
 }