Fix search on input behavior (#49150)

Kirill Bulatov created

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

* disable by default
* add a configurable debounce, 200ms default

Release Notes:

- N/A

Change summary

assets/settings/default.json          |  7 +++++--
crates/editor/src/editor_settings.rs  |  6 +++++-
crates/search/src/buffer_search.rs    |  8 ++++++++
crates/search/src/project_search.rs   | 26 ++++++++++++++++++++------
crates/settings_content/src/editor.rs |  8 +++++++-
crates/settings_ui/src/page_data.rs   | 27 +++++++++++++++++++++++++--
docs/src/reference/all-settings.md    | 12 +++++++++---
7 files changed, 79 insertions(+), 15 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -667,8 +667,11 @@
     "regex": false,
     // Whether to center the cursor on each search match when navigating.
     "center_on_match": false,
-    // Whether to search on input.
-    "search_on_input": true,
+    // Whether to search on input in project search.
+    "search_on_input": false,
+    // Debounce time in milliseconds for search on input in project search.
+    // Set to 0 to disable debouncing.
+    "search_on_input_debounce_ms": 200,
   },
   // When to populate a new search's query based on the text under the cursor.
   // This setting can take the following three values:

crates/editor/src/editor_settings.rs 🔗

@@ -175,8 +175,11 @@ pub struct SearchSettings {
     pub regex: bool,
     /// Whether to center the cursor on each search match when navigating.
     pub center_on_match: bool,
-    /// Whether to search on input.
+    /// Whether to search on input in project search.
     pub search_on_input: bool,
+    /// Debounce time in milliseconds for search on input in project search.
+    /// Set to 0 to disable debouncing.
+    pub search_on_input_debounce_ms: u64,
 }
 
 impl EditorSettings {
@@ -274,6 +277,7 @@ impl Settings for EditorSettings {
                 regex: search.regex.unwrap(),
                 center_on_match: search.center_on_match.unwrap(),
                 search_on_input: search.search_on_input.unwrap(),
+                search_on_input_debounce_ms: search.search_on_input_debounce_ms.unwrap(),
             },
             auto_signature_help: editor.auto_signature_help.unwrap(),
             show_signature_help_after_edits: editor.show_signature_help_after_edits.unwrap(),

crates/search/src/buffer_search.rs 🔗

@@ -3422,6 +3422,7 @@ mod tests {
                 regex: false,
                 center_on_match: false,
                 search_on_input: false,
+                search_on_input_debounce_ms: 0,
             },
             cx,
         );
@@ -3486,6 +3487,7 @@ mod tests {
                 regex: false,
                 center_on_match: false,
                 search_on_input: false,
+                search_on_input_debounce_ms: 0,
             },
             cx,
         );
@@ -3525,6 +3527,7 @@ mod tests {
                 regex: false,
                 center_on_match: false,
                 search_on_input: false,
+                search_on_input_debounce_ms: 0,
             },
             cx,
         );
@@ -3608,6 +3611,9 @@ mod tests {
                         regex: Some(search_settings.regex),
                         center_on_match: Some(search_settings.center_on_match),
                         search_on_input: Some(search_settings.search_on_input),
+                        search_on_input_debounce_ms: Some(
+                            search_settings.search_on_input_debounce_ms,
+                        ),
                     });
                 });
             });
@@ -3626,6 +3632,7 @@ mod tests {
                 regex: false,
                 center_on_match: false,
                 search_on_input: false,
+                search_on_input_debounce_ms: 0,
             },
             cx,
         );
@@ -3662,6 +3669,7 @@ mod tests {
                 regex: false,
                 center_on_match: false,
                 search_on_input: true,
+                search_on_input_debounce_ms: 0,
             },
             cx,
         );

crates/search/src/project_search.rs 🔗

@@ -39,6 +39,7 @@ use std::{
     ops::{Not, Range},
     pin::pin,
     sync::Arc,
+    time::Duration,
 };
 use ui::{
     CommonAnimationExt, IconButtonShape, KeyBinding, Toggleable, Tooltip, prelude::*,
@@ -270,6 +271,7 @@ pub struct ProjectSearchView {
     included_opened_only: bool,
     regex_language: Option<Arc<Language>>,
     results_collapsed: bool,
+    current_search_on_input: Task<()>,
     _subscriptions: Vec<Subscription>,
 }
 
@@ -879,11 +881,11 @@ impl ProjectSearchView {
                             this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
                         }
                     }
-                    // Trigger search on input:
-                    if EditorSettings::get_global(cx).search.search_on_input {
-                        let query = this.search_query_text(cx);
-                        if query.is_empty() {
-                            // Clear results immediately when query is empty and abort ongoing search
+
+                    let search_settings = &EditorSettings::get_global(cx).search;
+                    if search_settings.search_on_input {
+                        if this.query_editor.read(cx).is_empty(cx) {
+                            this.current_search_on_input = Task::ready(());
                             this.entity.update(cx, |model, cx| {
                                 model.pending_search = None;
                                 model.match_ranges.clear();
@@ -893,7 +895,18 @@ impl ProjectSearchView {
                                 cx.notify();
                             });
                         } else {
-                            this.search(cx);
+                            let debounce = search_settings.search_on_input_debounce_ms;
+                            this.current_search_on_input = cx.spawn(async move |this, cx| {
+                                if debounce > 0 {
+                                    cx.background_executor()
+                                        .timer(Duration::from_millis(debounce))
+                                        .await;
+                                }
+                                this.update(cx, |this, cx| {
+                                    this.search(cx);
+                                })
+                                .ok();
+                            });
                         }
                     }
                 }
@@ -1015,6 +1028,7 @@ impl ProjectSearchView {
             included_opened_only: false,
             regex_language: None,
             results_collapsed: false,
+            current_search_on_input: Task::ready(()),
             _subscriptions: subscriptions,
         };
 

crates/settings_content/src/editor.rs 🔗

@@ -828,8 +828,14 @@ pub struct SearchSettingsContent {
     pub regex: Option<bool>,
     /// Whether to center the cursor on each search match when navigating.
     pub center_on_match: Option<bool>,
-    /// Whether to search on input.
+    /// Whether to search on input in project search.
     pub search_on_input: Option<bool>,
+    /// Debounce time in milliseconds for search on input in project search.
+    ///
+    /// Set to 0 to disable debouncing.
+    ///
+    /// Default: 200
+    pub search_on_input_debounce_ms: Option<u64>,
 }
 
 #[with_fallible_options]

crates/settings_ui/src/page_data.rs 🔗

@@ -2999,7 +2999,7 @@ fn languages_and_tools_page(cx: &App) -> SettingsPage {
 }
 
 fn search_and_files_page() -> SettingsPage {
-    fn search_section() -> [SettingsPageItem; 10] {
+    fn search_section() -> [SettingsPageItem; 11] {
         [
             SettingsPageItem::SectionHeader("Search"),
             SettingsPageItem::SettingItem(SettingItem {
@@ -3135,7 +3135,7 @@ fn search_and_files_page() -> SettingsPage {
             }),
             SettingsPageItem::SettingItem(SettingItem {
                 title: "Search on Input",
-                description: "Whether to search on input.",
+                description: "Whether to search on input in project search.",
                 field: Box::new(SettingField {
                     json_path: Some("editor.search.search_on_input"),
                     pick: |settings_content| {
@@ -3156,6 +3156,29 @@ fn search_and_files_page() -> SettingsPage {
                 metadata: None,
                 files: USER,
             }),
+            SettingsPageItem::SettingItem(SettingItem {
+                title: "Search on Input Debounce",
+                description: "Debounce time in milliseconds for search on input (set to 0 to disable debouncing).",
+                field: Box::new(SettingField {
+                    json_path: Some("editor.search.search_on_input_debounce_ms"),
+                    pick: |settings_content| {
+                        settings_content
+                            .editor
+                            .search
+                            .as_ref()
+                            .and_then(|search| search.search_on_input_debounce_ms.as_ref())
+                    },
+                    write: |settings_content, value| {
+                        settings_content
+                            .editor
+                            .search
+                            .get_or_insert_default()
+                            .search_on_input_debounce_ms = value;
+                    },
+                }),
+                metadata: None,
+                files: USER,
+            }),
             SettingsPageItem::SettingItem(SettingItem {
                 title: "Seed Search Query From Cursor",
                 description: "When to populate a new search's query based on the text under the cursor.",

docs/src/reference/all-settings.md 🔗

@@ -3272,9 +3272,15 @@ Non-negative `integer` values
 
 ### Search On Input
 
-- Description: Whether to search on input.
-- Setting: `search_on_input
-- Default: `true`
+- Description: Whether to search on input in project search.
+- Setting: `search_on_input`
+- Default: `false`
+
+### Search On Input Debounce Ms
+
+- Description: Debounce time in milliseconds for search on input in project search. Set to 0 to disable debouncing.
+- Setting: `search_on_input_debounce_ms`
+- Default: `200`
 
 ### Center On Match