Revert "Enable type on search by default for the project search (#49374)" (#49619)

Kirill Bulatov created

This reverts commit 1d66bbe06f6e02008560064302c44a57ed4cff41.

Needs 2 more fixes:

* enter does not move to the first excerpt anymore
* there could be situations when a narrowed search does not decrease the
excerpt enough to see the result onscreen

Release Notes:

- N/A

Change summary

Cargo.lock                              |   1 
assets/settings/default.json            |   2 
crates/editor/src/editor_settings.rs    |   3 
crates/editor/src/split.rs              |  47 +
crates/multi_buffer/src/multi_buffer.rs |   2 
crates/multi_buffer/src/path_key.rs     | 122 +----
crates/search/Cargo.toml                |   1 
crates/search/src/buffer_search.rs      |   4 
crates/search/src/project_search.rs     | 553 ++++----------------------
crates/settings/src/vscode_import.rs    |   3 
crates/settings_content/src/editor.rs   |   2 
crates/settings_ui/src/page_data.rs     |  25 -
docs/src/reference/all-settings.md      |   8 
13 files changed, 143 insertions(+), 630 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -14770,7 +14770,6 @@ dependencies = [
  "language",
  "lsp",
  "menu",
- "multi_buffer",
  "pretty_assertions",
  "project",
  "serde",

assets/settings/default.json 🔗

@@ -671,8 +671,6 @@
     "regex": false,
     // Whether to center the cursor on each search match when navigating.
     "center_on_match": false,
-    // Whether to search on input in project search.
-    "search_on_input": true,
   },
   // 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,6 @@ 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 in project search.
-    pub search_on_input: bool,
 }
 
 impl EditorSettings {
@@ -279,7 +277,6 @@ impl Settings for EditorSettings {
                 include_ignored: search.include_ignored.unwrap(),
                 regex: search.regex.unwrap(),
                 center_on_match: search.center_on_match.unwrap(),
-                search_on_input: search.search_on_input.unwrap(),
             },
             auto_signature_help: editor.auto_signature_help.unwrap(),
             show_signature_help_after_edits: editor.show_signature_help_after_edits.unwrap(),

crates/editor/src/split.rs 🔗

@@ -2037,7 +2037,7 @@ impl LhsEditor {
             base_text_buffer_snapshot = base_text_buffer.snapshot();
             remote_id = base_text_buffer.remote_id();
         }
-        let excerpt_ranges = rhs_multibuffer
+        let new = rhs_multibuffer
             .excerpts_for_buffer(main_buffer.remote_id(), lhs_cx)
             .into_iter()
             .map(|(_, excerpt_range)| {
@@ -2061,26 +2061,13 @@ impl LhsEditor {
                     context: point_range_to_base_text_point_range(context),
                 }
             })
-            .collect::<Vec<_>>();
+            .collect();
 
-        let (new, counts) = MultiBuffer::merge_excerpt_ranges(&excerpt_ranges);
-        let mut total = 0;
-        let rhs_merge_groups = counts
-            .iter()
-            .copied()
-            .map(|count| {
-                let group = rhs_excerpt_ids[total..total + count].to_vec();
-                total += count;
-                group
-            })
-            .collect::<Vec<_>>();
-        let lhs_result = lhs_multibuffer.set_merged_excerpt_ranges_for_path(
+        let lhs_result = lhs_multibuffer.update_path_excerpts(
             path_key,
             diff.read(lhs_cx).base_text_buffer().clone(),
-            excerpt_ranges,
             &base_text_buffer_snapshot,
             new,
-            counts,
             lhs_cx,
         );
         if !lhs_result.excerpt_ids.is_empty()
@@ -2090,7 +2077,33 @@ impl LhsEditor {
         {
             lhs_multibuffer.add_inverted_diff(diff, lhs_cx);
         }
-        Some((lhs_result.excerpt_ids, rhs_merge_groups))
+
+        let rhs_merge_groups: Vec<Vec<ExcerptId>> = {
+            let mut groups = Vec::new();
+            let mut current_group = Vec::new();
+            let mut last_id = None;
+
+            for (i, &lhs_id) in lhs_result.excerpt_ids.iter().enumerate() {
+                if last_id == Some(lhs_id) {
+                    current_group.push(rhs_excerpt_ids[i]);
+                } else {
+                    if !current_group.is_empty() {
+                        groups.push(current_group);
+                    }
+                    current_group = vec![rhs_excerpt_ids[i]];
+                    last_id = Some(lhs_id);
+                }
+            }
+            if !current_group.is_empty() {
+                groups.push(current_group);
+            }
+            groups
+        };
+
+        let deduplicated_lhs_ids: Vec<ExcerptId> =
+            lhs_result.excerpt_ids.iter().dedup().copied().collect();
+
+        Some((deduplicated_lhs_ids, rhs_merge_groups))
     }
 
     fn sync_path_excerpts(

crates/multi_buffer/src/multi_buffer.rs 🔗

@@ -1734,7 +1734,7 @@ impl MultiBuffer {
     }
 
     #[instrument(skip_all)]
-    pub fn merge_excerpt_ranges<'a>(
+    fn merge_excerpt_ranges<'a>(
         expanded_ranges: impl IntoIterator<Item = &'a ExcerptRange<Point>> + 'a,
     ) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
         let mut merged_ranges: Vec<ExcerptRange<Point>> = Vec::new();

crates/multi_buffer/src/path_key.rs 🔗

@@ -15,7 +15,6 @@ use crate::{
 
 #[derive(Debug, Clone)]
 pub struct PathExcerptInsertResult {
-    pub inserted_ranges: Vec<Range<Anchor>>,
     pub excerpt_ids: Vec<ExcerptId>,
     pub added_new_excerpt: bool,
 }
@@ -101,7 +100,7 @@ impl MultiBuffer {
         let excerpt_ranges = build_excerpt_ranges(ranges, context_line_count, &buffer_snapshot);
 
         let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
-        let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(
+        self.set_merged_excerpt_ranges_for_path(
             path,
             buffer,
             excerpt_ranges,
@@ -109,10 +108,6 @@ impl MultiBuffer {
             new,
             counts,
             cx,
-        );
-        (
-            excerpt_insertion_result.inserted_ranges,
-            excerpt_insertion_result.added_new_excerpt,
         )
     }
 
@@ -125,7 +120,7 @@ impl MultiBuffer {
         cx: &mut Context<Self>,
     ) -> (Vec<Range<Anchor>>, bool) {
         let (new, counts) = Self::merge_excerpt_ranges(&excerpt_ranges);
-        let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(
+        self.set_merged_excerpt_ranges_for_path(
             path,
             buffer,
             excerpt_ranges,
@@ -133,10 +128,6 @@ impl MultiBuffer {
             new,
             counts,
             cx,
-        );
-        (
-            excerpt_insertion_result.inserted_ranges,
-            excerpt_insertion_result.added_new_excerpt,
         )
     }
 
@@ -165,7 +156,7 @@ impl MultiBuffer {
 
             multi_buffer
                 .update(&mut app, move |multi_buffer, cx| {
-                    let excerpt_insertion_result = multi_buffer.set_merged_excerpt_ranges_for_path(
+                    let (ranges, _) = multi_buffer.set_merged_excerpt_ranges_for_path(
                         path_key,
                         buffer,
                         excerpt_ranges,
@@ -174,7 +165,7 @@ impl MultiBuffer {
                         counts,
                         cx,
                     );
-                    excerpt_insertion_result.inserted_ranges
+                    ranges
                 })
                 .ok()
                 .unwrap_or_default()
@@ -273,7 +264,7 @@ impl MultiBuffer {
     }
 
     /// Sets excerpts, returns `true` if at least one new excerpt was added.
-    pub fn set_merged_excerpt_ranges_for_path(
+    fn set_merged_excerpt_ranges_for_path(
         &mut self,
         path: PathKey,
         buffer: Entity<Buffer>,
@@ -282,110 +273,36 @@ impl MultiBuffer {
         new: Vec<ExcerptRange<Point>>,
         counts: Vec<usize>,
         cx: &mut Context<Self>,
-    ) -> PathExcerptInsertResult {
-        let (new, counts) =
-            self.expand_new_ranges_to_existing(&path, buffer_snapshot, new, counts, cx);
-        let (excerpt_ids, added_new_excerpt) =
-            self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
+    ) -> (Vec<Range<Anchor>>, bool) {
+        let insert_result = self.update_path_excerpts(path, buffer, buffer_snapshot, new, cx);
 
-        let mut inserted_ranges = Vec::new();
+        let mut result = Vec::new();
         let mut ranges = ranges.into_iter();
-        for (&excerpt_id, range_count) in excerpt_ids.iter().zip(counts.into_iter()) {
+        for (excerpt_id, range_count) in insert_result
+            .excerpt_ids
+            .into_iter()
+            .zip(counts.into_iter())
+        {
             for range in ranges.by_ref().take(range_count) {
                 let range = Anchor::range_in_buffer(
                     excerpt_id,
                     buffer_snapshot.anchor_before(&range.primary.start)
                         ..buffer_snapshot.anchor_after(&range.primary.end),
                 );
-                inserted_ranges.push(range)
-            }
-        }
-
-        PathExcerptInsertResult {
-            inserted_ranges,
-            excerpt_ids,
-            added_new_excerpt,
-        }
-    }
-
-    /// Expand each new merged range to encompass any overlapping existing
-    /// excerpt, then re-merge. This turns "partial overlap where the union
-    /// equals the existing range" into an exact match, avoiding unnecessary
-    /// remove+insert churn that floods the wrap map with edits.
-    fn expand_new_ranges_to_existing(
-        &self,
-        path: &PathKey,
-        buffer_snapshot: &BufferSnapshot,
-        mut new: Vec<ExcerptRange<Point>>,
-        counts: Vec<usize>,
-        cx: &App,
-    ) -> (Vec<ExcerptRange<Point>>, Vec<usize>) {
-        let existing = self.excerpts_by_path.get(path).cloned().unwrap_or_default();
-        if existing.is_empty() || new.is_empty() {
-            return (new, counts);
-        }
-
-        let snapshot = self.snapshot(cx);
-        let buffer_id = buffer_snapshot.remote_id();
-        let existing_ranges: Vec<Range<Point>> = existing
-            .iter()
-            .filter_map(|&id| {
-                let excerpt = snapshot.excerpt(id)?;
-                (excerpt.buffer_id == buffer_id)
-                    .then(|| excerpt.range.context.to_point(buffer_snapshot))
-            })
-            .collect();
-
-        let mut changed = false;
-        for new_range in &mut new {
-            for existing_range in &existing_ranges {
-                if new_range.context.start <= existing_range.end
-                    && new_range.context.end >= existing_range.start
-                {
-                    let expanded_start = new_range.context.start.min(existing_range.start);
-                    let expanded_end = new_range.context.end.max(existing_range.end);
-                    if expanded_start != new_range.context.start
-                        || expanded_end != new_range.context.end
-                    {
-                        new_range.context.start = expanded_start;
-                        new_range.context.end = expanded_end;
-                        changed = true;
-                    }
-                }
-            }
-        }
-
-        if !changed {
-            return (new, counts);
-        }
-
-        let mut result_ranges: Vec<ExcerptRange<Point>> = Vec::new();
-        let mut result_counts: Vec<usize> = Vec::new();
-        for (range, count) in new.into_iter().zip(counts) {
-            if let Some(last) = result_ranges.last_mut() {
-                if last.context.end >= range.context.start
-                    || last.context.end.row + 1 == range.context.start.row
-                {
-                    last.context.end = last.context.end.max(range.context.end);
-                    *result_counts.last_mut().unwrap() += count;
-                    continue;
-                }
+                result.push(range)
             }
-            result_ranges.push(range);
-            result_counts.push(count);
         }
-
-        (result_ranges, result_counts)
+        (result, insert_result.added_new_excerpt)
     }
 
-    fn update_path_excerpts(
+    pub fn update_path_excerpts(
         &mut self,
         path: PathKey,
         buffer: Entity<Buffer>,
         buffer_snapshot: &BufferSnapshot,
         new: Vec<ExcerptRange<Point>>,
         cx: &mut Context<Self>,
-    ) -> (Vec<ExcerptId>, bool) {
+    ) -> PathExcerptInsertResult {
         let mut insert_after = self
             .excerpts_by_path
             .range(..path.clone())
@@ -575,6 +492,9 @@ impl MultiBuffer {
             self.excerpts_by_path.insert(path, excerpt_ids);
         }
 
-        (excerpt_ids, added_a_new_excerpt)
+        PathExcerptInsertResult {
+            excerpt_ids,
+            added_new_excerpt: added_a_new_excerpt,
+        }
     }
 }

crates/search/Cargo.toml 🔗

@@ -52,7 +52,6 @@ editor = { workspace = true, features = ["test-support"] }
 gpui = { workspace = true, features = ["test-support"] }
 language = { workspace = true, features = ["test-support"] }
 lsp.workspace = true
-multi_buffer.workspace = true
 pretty_assertions.workspace = true
 unindent.workspace = true
 workspace = { workspace = true, features = ["test-support"] }

crates/search/src/buffer_search.rs 🔗

@@ -3465,7 +3465,6 @@ mod tests {
                 include_ignored: false,
                 regex: false,
                 center_on_match: false,
-                search_on_input: false,
             },
             cx,
         );
@@ -3529,7 +3528,6 @@ mod tests {
                 include_ignored: false,
                 regex: false,
                 center_on_match: false,
-                search_on_input: false,
             },
             cx,
         );
@@ -3568,7 +3566,6 @@ mod tests {
                 include_ignored: false,
                 regex: false,
                 center_on_match: false,
-                search_on_input: false,
             },
             cx,
         );
@@ -3651,7 +3648,6 @@ mod tests {
                         include_ignored: Some(search_settings.include_ignored),
                         regex: Some(search_settings.regex),
                         center_on_match: Some(search_settings.center_on_match),
-                        search_on_input: Some(search_settings.search_on_input),
                     });
                 });
             });

crates/search/src/project_search.rs 🔗

@@ -9,7 +9,7 @@ use crate::{
     },
 };
 use anyhow::Context as _;
-use collections::{HashMap, HashSet};
+use collections::HashMap;
 use editor::{
     Anchor, Editor, EditorEvent, EditorSettings, MAX_TAB_TITLE_LEN, MultiBuffer, PathKey,
     SelectionEffects,
@@ -333,7 +333,7 @@ impl ProjectSearch {
         }
     }
 
-    fn search(&mut self, query: SearchQuery, incremental: bool, cx: &mut Context<Self>) {
+    fn search(&mut self, query: SearchQuery, cx: &mut Context<Self>) {
         let search = self.project.update(cx, |project, cx| {
             project
                 .search_history_mut(SearchInputKind::Query)
@@ -360,22 +360,18 @@ impl ProjectSearch {
             let SearchResults { rx, _task_handle } = search;
 
             let mut matches = pin!(rx.ready_chunks(1024));
-
-            if !incremental {
-                project_search
-                    .update(cx, |project_search, cx| {
-                        project_search.match_ranges.clear();
-                        project_search
-                            .excerpts
-                            .update(cx, |excerpts, cx| excerpts.clear(cx));
-                        project_search.no_results = Some(true);
-                        project_search.limit_reached = false;
-                    })
-                    .ok()?;
-            }
+            project_search
+                .update(cx, |project_search, cx| {
+                    project_search.match_ranges.clear();
+                    project_search
+                        .excerpts
+                        .update(cx, |excerpts, cx| excerpts.clear(cx));
+                    project_search.no_results = Some(true);
+                    project_search.limit_reached = false;
+                })
+                .ok()?;
 
             let mut limit_reached = false;
-            let mut seen_paths = HashSet::default();
             while let Some(results) = matches.next().await {
                 let (buffers_with_ranges, has_reached_limit) = cx
                     .background_executor()
@@ -396,50 +392,6 @@ impl ProjectSearch {
                     })
                     .await;
                 limit_reached |= has_reached_limit;
-
-                if incremental {
-                    let buffers_with_ranges: Vec<_> = buffers_with_ranges
-                        .into_iter()
-                        .filter(|(_, ranges)| !ranges.is_empty())
-                        .collect();
-                    if buffers_with_ranges.is_empty() {
-                        continue;
-                    }
-                    let (mut chunk_ranges, chunk_paths) = project_search
-                        .update(cx, |project_search, cx| {
-                            let mut paths = Vec::new();
-                            let futures = project_search.excerpts.update(cx, |excerpts, cx| {
-                                buffers_with_ranges
-                                    .into_iter()
-                                    .map(|(buffer, ranges)| {
-                                        let path_key = PathKey::for_buffer(&buffer, cx);
-                                        paths.push(path_key.clone());
-                                        excerpts.set_anchored_excerpts_for_path(
-                                            path_key,
-                                            buffer,
-                                            ranges,
-                                            multibuffer_context_lines(cx),
-                                            cx,
-                                        )
-                                    })
-                                    .collect::<FuturesOrdered<_>>()
-                            });
-                            (futures, paths)
-                        })
-                        .ok()?;
-                    seen_paths.extend(chunk_paths);
-                    while let Some(ranges) = chunk_ranges.next().await {
-                        smol::future::yield_now().await;
-                        project_search
-                            .update(cx, |project_search, cx| {
-                                project_search.match_ranges.extend(ranges);
-                                cx.notify();
-                            })
-                            .ok()?;
-                    }
-                    continue;
-                }
-
                 let mut new_ranges = project_search
                     .update(cx, |project_search, cx| {
                         project_search.excerpts.update(cx, |excerpts, cx| {
@@ -471,43 +423,16 @@ impl ProjectSearch {
                 }
             }
 
-            if incremental {
-                project_search
-                    .update(cx, |project_search, cx| {
-                        if seen_paths.is_empty() {
-                            project_search
-                                .excerpts
-                                .update(cx, |excerpts, cx| excerpts.clear(cx));
-                        } else {
-                            project_search.excerpts.update(cx, |excerpts, cx| {
-                                let stale = excerpts
-                                    .paths()
-                                    .filter(|path| !seen_paths.contains(*path))
-                                    .cloned()
-                                    .collect::<Vec<_>>();
-                                for path in stale {
-                                    excerpts.remove_excerpts_for_path(path, cx);
-                                }
-                            });
-                        }
-                        project_search.no_results = Some(project_search.match_ranges.is_empty());
-                        project_search.limit_reached = limit_reached;
-                        project_search.pending_search.take();
-                        cx.notify();
-                    })
-                    .ok()?;
-            } else {
-                project_search
-                    .update(cx, |project_search, cx| {
-                        if !project_search.match_ranges.is_empty() {
-                            project_search.no_results = Some(false);
-                        }
-                        project_search.limit_reached = limit_reached;
-                        project_search.pending_search.take();
-                        cx.notify();
-                    })
-                    .ok()?;
-            }
+            project_search
+                .update(cx, |project_search, cx| {
+                    if !project_search.match_ranges.is_empty() {
+                        project_search.no_results = Some(false);
+                    }
+                    project_search.limit_reached = limit_reached;
+                    project_search.pending_search.take();
+                    cx.notify();
+                })
+                .ok()?;
 
             None
         }));
@@ -814,7 +739,7 @@ impl ProjectSearchView {
             && self.query_editor.read(cx).text(cx) != *last_search_query_text
         {
             // search query has changed, restart search and bail
-            self.search(false, cx);
+            self.search(cx);
             return;
         }
         if self.entity.read(cx).match_ranges.is_empty() {
@@ -842,7 +767,7 @@ impl ProjectSearchView {
             && self.query_editor.read(cx).text(cx) != *last_search_query_text
         {
             // search query has changed, restart search and bail
-            self.search(false, cx);
+            self.search(cx);
             return;
         }
         if self.active_match_index.is_none() {
@@ -941,32 +866,15 @@ impl ProjectSearchView {
         // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes
         subscriptions.push(
             cx.subscribe(&query_editor, |this, _, event: &EditorEvent, cx| {
-                if let EditorEvent::Edited { .. } = event {
-                    if EditorSettings::get_global(cx).use_smartcase_search {
-                        let query = this.search_query_text(cx);
-                        if !query.is_empty()
-                            && this.search_options.contains(SearchOptions::CASE_SENSITIVE)
-                                != contains_uppercase(&query)
-                        {
-                            this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
-                        }
-                    }
-
-                    let search_settings = &EditorSettings::get_global(cx).search;
-                    if search_settings.search_on_input {
-                        if this.query_editor.read(cx).is_empty(cx) {
-                            this.entity.update(cx, |model, cx| {
-                                model.pending_search = None;
-                                model.match_ranges.clear();
-                                model.excerpts.update(cx, |excerpts, cx| excerpts.clear(cx));
-                                model.no_results = None;
-                                model.limit_reached = false;
-                                model.last_search_query_text = None;
-                                cx.notify();
-                            });
-                        } else {
-                            this.search(true, cx);
-                        }
+                if let EditorEvent::Edited { .. } = event
+                    && EditorSettings::get_global(cx).use_smartcase_search
+                {
+                    let query = this.search_query_text(cx);
+                    if !query.is_empty()
+                        && this.search_options.contains(SearchOptions::CASE_SENSITIVE)
+                            != contains_uppercase(&query)
+                    {
+                        this.toggle_search_option(SearchOptions::CASE_SENSITIVE, cx);
                     }
                 }
                 cx.emit(ViewEvent::EditorEvent(event.clone()))
@@ -1152,7 +1060,7 @@ impl ProjectSearchView {
             if let Some(new_query) = new_query {
                 let entity = cx.new(|cx| {
                     let mut entity = ProjectSearch::new(workspace.project().clone(), cx);
-                    entity.search(new_query, false, cx);
+                    entity.search(new_query, cx);
                     entity
                 });
                 let weak_workspace = cx.entity().downgrade();
@@ -1305,14 +1213,14 @@ impl ProjectSearchView {
             };
             if should_search {
                 this.update(cx, |this, cx| {
-                    this.search(false, cx);
+                    this.search(cx);
                 })?;
             }
             anyhow::Ok(())
         })
     }
 
-    fn search(&mut self, incremental: bool, cx: &mut Context<Self>) {
+    fn search(&mut self, cx: &mut Context<Self>) {
         let open_buffers = if self.included_opened_only {
             self.workspace
                 .update(cx, |workspace, cx| self.open_buffers(cx, workspace))
@@ -1321,8 +1229,7 @@ impl ProjectSearchView {
             None
         };
         if let Some(query) = self.build_search_query(cx, open_buffers) {
-            self.entity
-                .update(cx, |model, cx| model.search(query, incremental, cx));
+            self.entity.update(cx, |model, cx| model.search(query, cx));
         }
     }
 
@@ -1584,15 +1491,10 @@ impl ProjectSearchView {
     }
 
     fn entity_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let model = self.entity.read(cx);
-        let match_ranges = model.match_ranges.clone();
-        let is_incremental_pending =
-            model.pending_search.is_some() && EditorSettings::get_global(cx).search.search_on_input;
+        let match_ranges = self.entity.read(cx).match_ranges.clone();
 
         if match_ranges.is_empty() {
-            if !is_incremental_pending {
-                self.active_match_index = None;
-            }
+            self.active_match_index = None;
             self.results_editor.update(cx, |editor, cx| {
                 editor.clear_background_highlights(HighlightKey::ProjectSearchView, cx);
             });
@@ -1612,11 +1514,7 @@ impl ProjectSearchView {
                     editor.scroll(Point::default(), Some(Axis::Vertical), window, cx);
                 }
             });
-            let should_auto_focus = !EditorSettings::get_global(cx).search.search_on_input;
-            if is_new_search
-                && self.query_editor.focus_handle(cx).is_focused(window)
-                && should_auto_focus
-            {
+            if is_new_search && self.query_editor.focus_handle(cx).is_focused(window) {
                 self.focus_results_editor(window, cx);
             }
         }
@@ -1679,13 +1577,9 @@ impl ProjectSearchView {
         v_flex()
             .gap_1()
             .child(
-                Label::new(if EditorSettings::get_global(cx).search.search_on_input {
-                    "Start typing to search. For more options:"
-                } else {
-                    "Hit enter to search. For more options:"
-                })
-                .color(Color::Muted)
-                .mb_2(),
+                Label::new("Hit enter to search. For more options:")
+                    .color(Color::Muted)
+                    .mb_2(),
             )
             .child(
                 Button::new("filter-paths", "Include/exclude specific paths")
@@ -2604,7 +2498,7 @@ pub fn perform_project_search(
         search_view.query_editor.update(cx, |query_editor, cx| {
             query_editor.set_text(text, window, cx)
         });
-        search_view.search(false, cx);
+        search_view.search(cx);
     });
     cx.run_until_parked();
 }
@@ -2612,9 +2506,7 @@ pub fn perform_project_search(
 #[cfg(test)]
 pub mod tests {
     use std::{
-        cell::RefCell,
         path::PathBuf,
-        rc::Rc,
         sync::{
             Arc,
             atomic::{self, AtomicUsize},
@@ -2626,7 +2518,6 @@ pub mod tests {
     use editor::{DisplayPoint, display_map::DisplayRow};
     use gpui::{Action, TestAppContext, VisualTestContext, WindowHandle};
     use language::{FakeLspAdapter, rust_lang};
-    use multi_buffer::Event as MultiBufferEvent;
     use pretty_assertions::assert_eq;
     use project::FakeFs;
     use serde_json::json;
@@ -2637,155 +2528,6 @@ pub mod tests {
     use util_macros::perf;
     use workspace::{DeploySearch, MultiWorkspace};
 
-    #[derive(Debug, Clone, PartialEq, Eq)]
-    enum ExcerptEvent {
-        Added { excerpts: usize },
-        Removed { ids: usize },
-        Edited,
-    }
-
-    fn subscribe_to_excerpt_events(
-        search: &Entity<ProjectSearch>,
-        cx: &mut TestAppContext,
-    ) -> (Rc<RefCell<Vec<ExcerptEvent>>>, Subscription) {
-        let events: Rc<RefCell<Vec<ExcerptEvent>>> = Rc::default();
-        let excerpts = cx.update(|cx| search.read(cx).excerpts.clone());
-        let subscription = cx.update({
-            let events = events.clone();
-            |cx| {
-                cx.subscribe(
-                    &excerpts,
-                    move |_, event: &MultiBufferEvent, _| match event {
-                        MultiBufferEvent::ExcerptsAdded { excerpts, .. } => {
-                            events.borrow_mut().push(ExcerptEvent::Added {
-                                excerpts: excerpts.len(),
-                            });
-                        }
-                        MultiBufferEvent::ExcerptsRemoved { ids, .. } => {
-                            events
-                                .borrow_mut()
-                                .push(ExcerptEvent::Removed { ids: ids.len() });
-                        }
-                        MultiBufferEvent::Edited { .. } => {
-                            events.borrow_mut().push(ExcerptEvent::Edited);
-                        }
-                        _ => {}
-                    },
-                )
-            }
-        });
-        (events, subscription)
-    }
-
-    fn init_test(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let settings = SettingsStore::test(cx);
-            cx.set_global(settings);
-
-            theme::init(theme::LoadThemes::JustBase, cx);
-
-            editor::init(cx);
-            crate::init(cx);
-
-            SettingsStore::update_global(cx, |store, cx| {
-                store.update_user_settings(cx, |settings| {
-                    settings
-                        .editor
-                        .search
-                        .get_or_insert_default()
-                        .search_on_input = Some(false);
-                });
-            });
-        });
-    }
-
-    fn perform_search(
-        search_view: WindowHandle<ProjectSearchView>,
-        text: impl Into<Arc<str>>,
-        cx: &mut TestAppContext,
-    ) {
-        search_view
-            .update(cx, |search_view, window, cx| {
-                search_view.query_editor.update(cx, |query_editor, cx| {
-                    query_editor.set_text(text, window, cx)
-                });
-                search_view.search(false, cx);
-            })
-            .unwrap();
-        // Ensure editor highlights appear after the search is done
-        cx.executor().advance_clock(
-            editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
-        );
-        cx.background_executor.run_until_parked();
-    }
-
-    fn perform_incremental_search(
-        search_view: WindowHandle<ProjectSearchView>,
-        text: impl Into<Arc<str>>,
-        cx: &mut TestAppContext,
-    ) {
-        search_view
-            .update(cx, |search_view, window, cx| {
-                search_view.query_editor.update(cx, |query_editor, cx| {
-                    query_editor.set_text(text, window, cx)
-                });
-                search_view.search(true, cx);
-            })
-            .unwrap();
-        cx.executor().advance_clock(
-            editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
-        );
-        cx.background_executor.run_until_parked();
-    }
-
-    fn read_match_count(
-        search_view: WindowHandle<ProjectSearchView>,
-        cx: &mut TestAppContext,
-    ) -> usize {
-        search_view
-            .read_with(cx, |search_view, cx| {
-                search_view.entity.read(cx).match_ranges.len()
-            })
-            .unwrap()
-    }
-
-    fn read_match_texts(
-        search_view: WindowHandle<ProjectSearchView>,
-        cx: &mut TestAppContext,
-    ) -> Vec<String> {
-        search_view
-            .read_with(cx, |search_view, cx| {
-                let search = search_view.entity.read(cx);
-                let snapshot = search.excerpts.read(cx).snapshot(cx);
-                search
-                    .match_ranges
-                    .iter()
-                    .map(|range| snapshot.text_for_range(range.clone()).collect::<String>())
-                    .collect()
-            })
-            .unwrap()
-    }
-
-    fn assert_all_highlights_match_query(
-        search_view: WindowHandle<ProjectSearchView>,
-        query: &str,
-        cx: &mut TestAppContext,
-    ) {
-        let match_texts = read_match_texts(search_view, cx);
-        assert_eq!(
-            match_texts.len(),
-            read_match_count(search_view, cx),
-            "match texts count should equal match_ranges count for query {query:?}"
-        );
-        for text in &match_texts {
-            assert_eq!(
-                text.to_uppercase(),
-                query.to_uppercase(),
-                "every highlighted range should match the query {query:?}"
-            );
-        }
-    }
-
     #[test]
     fn test_split_glob_patterns() {
         assert_eq!(split_glob_patterns("a,b,c"), vec!["a", "b", "c"]);
@@ -3331,7 +3073,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -3375,7 +3117,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("TWO", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -3528,7 +3270,7 @@ pub mod tests {
                         .update(cx, |exclude_editor, cx| {
                             exclude_editor.set_text("four.rs", window, cx)
                         });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -3558,7 +3300,7 @@ pub mod tests {
             .update(cx, |_, _, cx| {
                 search_view.update(cx, |search_view, cx| {
                     search_view.toggle_filters(cx);
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -3685,7 +3427,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("sOMETHINGtHATsURELYdOESnOTeXIST", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -3730,7 +3472,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("TWO", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 })
             })
             .unwrap();
@@ -3830,7 +3572,7 @@ pub mod tests {
                     search_view_2.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("FOUR", window, cx)
                     });
-                    search_view_2.search(false, cx);
+                    search_view_2.search(cx);
                 });
             })
             .unwrap();
@@ -3976,7 +3718,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("const", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -4051,7 +3793,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("ONE", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -4063,7 +3805,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("TWO", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -4074,7 +3816,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("THREE", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 })
             })
             .unwrap();
@@ -4231,7 +3973,7 @@ pub mod tests {
                     search_view.query_editor.update(cx, |query_editor, cx| {
                         query_editor.set_text("TWO_NEW", window, cx)
                     });
-                    search_view.search(false, cx);
+                    search_view.search(cx);
                 });
             })
             .unwrap();
@@ -4445,7 +4187,7 @@ pub mod tests {
                             search_view.query_editor.update(cx, |query_editor, cx| {
                                 query_editor.set_text(query, window, cx)
                             });
-                            search_view.search(false, cx);
+                            search_view.search(cx);
                         });
                     })
                     .unwrap();
@@ -5145,166 +4887,35 @@ pub mod tests {
             .unwrap();
     }
 
-    #[gpui::test]
-    async fn test_incremental_search_narrows_and_widens(cx: &mut TestAppContext) {
-        init_test(cx);
+    fn init_test(cx: &mut TestAppContext) {
+        cx.update(|cx| {
+            let settings = SettingsStore::test(cx);
+            cx.set_global(settings);
 
-        // Two matches 5 lines apart: with context_line_count=2, contexts
-        // [3..7] and [8..12] are adjacent and merge into a single excerpt
-        // [3..12]. Narrowing to "targeted" produces context [3..7] ⊂ [3..12]
-        // — the expand_new_ranges_to_existing fix ensures zero excerpt events.
-        let mut lines: Vec<String> = (0..20).map(|i| format!("line {i}: filler")).collect();
-        lines[5] = "line 5: targeted item".into();
-        lines[10] = "line 10: target item".into();
-        let big_file = lines.join("\n");
+            theme::init(theme::LoadThemes::JustBase, cx);
 
-        let fs = FakeFs::new(cx.background_executor.clone());
-        fs.insert_tree(
-            path!("/dir"),
-            json!({
-                "one.rs": "const ONE: usize = 1;\nconst ONEROUS: usize = 2;",
-                "two.rs": "const TWO: usize = one::ONE + one::ONE;",
-                "three.rs": "const THREE: usize = one::ONE + two::TWO;",
-                "four.rs": "const FOUR: usize = one::ONE + three::THREE;",
-                "only_one.rs": "const ONLY_ONE: usize = 1;",
-                "big.txt": big_file,
-            }),
-        )
-        .await;
-        let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
-        let window =
-            cx.add_window(|window, cx| MultiWorkspace::test_new(project.clone(), window, cx));
-        let workspace = window
-            .read_with(cx, |mw, _| mw.workspace().clone())
-            .unwrap();
-        let search = cx.new(|cx| ProjectSearch::new(project.clone(), cx));
-        let search_view = cx.add_window(|window, cx| {
-            ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None)
+            editor::init(cx);
+            crate::init(cx);
         });
-        let (events, _subscription) = subscribe_to_excerpt_events(&search, cx);
-        let take_excerpt_changes = || -> Vec<ExcerptEvent> {
-            events
-                .borrow_mut()
-                .drain(..)
-                .filter(|e| !matches!(e, ExcerptEvent::Edited))
-                .collect()
-        };
-        let expected_one_matches = vec![
-            "one", "ONE", "ONE", "ONE", "ONE", "one", "ONE", "one", "ONE", "one", "ONE",
-        ];
-
-        // Initial non-incremental search for "ONE" — clears then inserts one excerpt per file.
-        perform_search(search_view, "ONE", cx);
-        assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
-        assert_all_highlights_match_query(search_view, "ONE", cx);
-        assert_eq!(
-            take_excerpt_changes(),
-            vec![
-                ExcerptEvent::Removed { ids: 0 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-            ]
-        );
-
-        // Natural narrowing: typing "R" after "ONE" -> "ONER".
-        // Only one.rs has ONEROUS, 4 other files removed.
-        perform_incremental_search(search_view, "ONER", cx);
-        assert_eq!(read_match_texts(search_view, cx), vec!["ONER"]);
-        assert_all_highlights_match_query(search_view, "ONER", cx);
-        assert_eq!(
-            take_excerpt_changes(),
-            vec![
-                ExcerptEvent::Removed { ids: 1 },
-                ExcerptEvent::Removed { ids: 1 },
-                ExcerptEvent::Removed { ids: 1 },
-                ExcerptEvent::Removed { ids: 1 },
-            ]
-        );
-
-        // Continue typing "OUS" -> "ONEROUS". Still one.rs only, zero excerpt churn.
-        perform_incremental_search(search_view, "ONEROUS", cx);
-        assert_eq!(read_match_texts(search_view, cx), vec!["ONEROUS"]);
-        assert_all_highlights_match_query(search_view, "ONEROUS", cx);
-        assert_eq!(take_excerpt_changes(), Vec::new());
-
-        // Backspace to "ONER" — still one.rs only, zero events.
-        perform_incremental_search(search_view, "ONER", cx);
-        assert_eq!(read_match_texts(search_view, cx), vec!["ONER"]);
-        assert_all_highlights_match_query(search_view, "ONER", cx);
-        assert_eq!(take_excerpt_changes(), Vec::new());
-
-        // Backspace to "ONE" — 4 files re-added.
-        perform_incremental_search(search_view, "ONE", cx);
-        assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
-        assert_all_highlights_match_query(search_view, "ONE", cx);
-        assert_eq!(
-            take_excerpt_changes(),
-            vec![
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-            ]
-        );
-
-        // Repeat the same "ONE" query — excerpts already match, zero events emitted.
-        perform_incremental_search(search_view, "ONE", cx);
-        assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
-        assert_all_highlights_match_query(search_view, "ONE", cx);
-        assert_eq!(events.borrow().len(), 0);
-
-        // Narrow to "ONLY_ONE" — single match in only_one.rs, 4 files removed.
-        perform_incremental_search(search_view, "ONLY_ONE", cx);
-        assert_eq!(read_match_texts(search_view, cx), vec!["ONLY_ONE"]);
-        assert_all_highlights_match_query(search_view, "ONLY_ONE", cx);
-        assert_eq!(
-            take_excerpt_changes(),
-            vec![
-                ExcerptEvent::Removed { ids: 1 },
-                ExcerptEvent::Removed { ids: 1 },
-                ExcerptEvent::Removed { ids: 1 },
-                ExcerptEvent::Removed { ids: 1 },
-            ]
-        );
+    }
 
-        // Widen back to "ONE" — 4 files re-added.
-        perform_incremental_search(search_view, "ONE", cx);
-        assert_eq!(read_match_texts(search_view, cx), expected_one_matches);
-        assert_all_highlights_match_query(search_view, "ONE", cx);
-        assert_eq!(
-            take_excerpt_changes(),
-            vec![
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-                ExcerptEvent::Added { excerpts: 1 },
-            ]
+    fn perform_search(
+        search_view: WindowHandle<ProjectSearchView>,
+        text: impl Into<Arc<str>>,
+        cx: &mut TestAppContext,
+    ) {
+        search_view
+            .update(cx, |search_view, window, cx| {
+                search_view.query_editor.update(cx, |query_editor, cx| {
+                    query_editor.set_text(text, window, cx)
+                });
+                search_view.search(cx);
+            })
+            .unwrap();
+        // Ensure editor highlights appear after the search is done
+        cx.executor().advance_clock(
+            editor::SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT + Duration::from_millis(100),
         );
-
-        // Narrowing when all files still match — zero excerpt events.
-        // "usize" matches all 5 .rs files; "usize =" is narrower but still in every file.
-        perform_search(search_view, "usize", cx);
-        assert_eq!(read_match_count(search_view, cx), 6);
-        events.borrow_mut().clear();
-        perform_incremental_search(search_view, "usize =", cx);
-        assert_eq!(read_match_count(search_view, cx), 6);
-        assert_eq!(events.borrow().len(), 0);
-
-        // Merged-excerpt narrowing: "target" matches lines 5 and 10 in big.txt,
-        // whose context lines merge into one excerpt [3..12]. Narrowing to
-        // "targeted" shrinks context to [3..7] ⊂ [3..12] — the existing excerpt
-        // must be kept with zero events.
-        perform_search(search_view, "target", cx);
-        assert_all_highlights_match_query(search_view, "target", cx);
-        assert_eq!(read_match_count(search_view, cx), 2);
-        take_excerpt_changes();
-
-        perform_incremental_search(search_view, "targeted", cx);
-        assert_all_highlights_match_query(search_view, "targeted", cx);
-        assert_eq!(read_match_count(search_view, cx), 1);
-        assert_eq!(take_excerpt_changes(), Vec::new());
+        cx.background_executor.run_until_parked();
     }
 }

crates/settings/src/vscode_import.rs 🔗

@@ -357,8 +357,7 @@ impl VsCodeSettings {
     fn search_content(&self) -> Option<SearchSettingsContent> {
         skip_default(SearchSettingsContent {
             include_ignored: self.read_bool("search.useIgnoreFiles"),
-            search_on_input: self.read_bool("search.searchOnType"),
-            ..SearchSettingsContent::default()
+            ..Default::default()
         })
     }
 

crates/settings_content/src/editor.rs 🔗

@@ -828,8 +828,6 @@ 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 in project search.
-    pub search_on_input: Option<bool>,
 }
 
 #[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; 9] {
         [
             SettingsPageItem::SectionHeader("Search"),
             SettingsPageItem::SettingItem(SettingItem {
@@ -3133,29 +3133,6 @@ fn search_and_files_page() -> SettingsPage {
                 metadata: None,
                 files: USER,
             }),
-            SettingsPageItem::SettingItem(SettingItem {
-                title: "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| {
-                        settings_content
-                            .editor
-                            .search
-                            .as_ref()
-                            .and_then(|search| search.search_on_input.as_ref())
-                    },
-                    write: |settings_content, value| {
-                        settings_content
-                            .editor
-                            .search
-                            .get_or_insert_default()
-                            .search_on_input = 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 🔗

@@ -3279,7 +3279,13 @@ Non-negative `integer` values
 
 - Description: Whether to search on input in project search.
 - Setting: `search_on_input`
-- Default: `true`
+- 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