From fee42e1d89cc5e50df3579e873ce5280650285e8 Mon Sep 17 00:00:00 2001
From: holoflash <114405740+holoflash@users.noreply.github.com>
Date: Fri, 13 Feb 2026 16:54:17 +0100
Subject: [PATCH] Add search_on_input setting to Project Search (#42889)
I was really missing the ability to instantly see search results while
typing in the Project Search and decided to try and implement it.
As this may not be a feature that _everyone_ wants, I made it
toggle-able via the settings (or the settings.json)
### Settings
Set to false by default
### settings.json
Set to false by default
### Video demo:
https://github.com/user-attachments/assets/715d6b77-3a61-45f8-8e1a-9bd880c697c3
- Search input is debounced with 250ms in this mode (cool?)
The desire for this feature has been expressed here too:
https://github.com/zed-industries/zed/discussions/30843
Release Notes:
- Enabled project search on input. Can be turned off with
`search.search_on_input` settings toggle.
---
assets/settings/default.json | 2 +
crates/editor/src/editor_settings.rs | 3 +
crates/search/src/buffer_search.rs | 85 +++++++++++++++++++++++++++
crates/search/src/project_search.rs | 63 +++++++++++++++-----
crates/settings_content/src/editor.rs | 2 +
crates/settings_ui/src/page_data.rs | 25 +++++++-
docs/src/reference/all-settings.md | 12 ++--
7 files changed, 171 insertions(+), 21 deletions(-)
diff --git a/assets/settings/default.json b/assets/settings/default.json
index 3e6282e14eaebfb5a1d091a90c1883ed84da3d92..8e76ada7e9a193f666451b63743e0ad89b29e5c0 100644
--- a/assets/settings/default.json
+++ b/assets/settings/default.json
@@ -667,6 +667,8 @@
"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,
},
// When to populate a new search's query based on the text under the cursor.
// This setting can take the following three values:
diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs
index 47210a7561f4a3ebbddb994f00afcaff9158254d..654a541a699b62fc7fb9d88859ac8845a1dbff61 100644
--- a/crates/editor/src/editor_settings.rs
+++ b/crates/editor/src/editor_settings.rs
@@ -175,6 +175,8 @@ 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.
+ pub search_on_input: bool,
}
impl EditorSettings {
@@ -271,6 +273,7 @@ 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(),
diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs
index 25cbb4ef4cdb65128adee25b93d33409b9df57f9..a8639b70cd75ca38bcafd81e6d4aee9acf6dbc98 100644
--- a/crates/search/src/buffer_search.rs
+++ b/crates/search/src/buffer_search.rs
@@ -3421,6 +3421,7 @@ mod tests {
include_ignored: false,
regex: false,
center_on_match: false,
+ search_on_input: false,
},
cx,
);
@@ -3484,6 +3485,7 @@ mod tests {
include_ignored: false,
regex: false,
center_on_match: false,
+ search_on_input: false,
},
cx,
);
@@ -3522,6 +3524,7 @@ mod tests {
include_ignored: false,
regex: false,
center_on_match: false,
+ search_on_input: false,
},
cx,
);
@@ -3604,9 +3607,91 @@ 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),
});
});
});
});
}
+ #[gpui::test]
+ async fn test_search_on_input_setting(cx: &mut TestAppContext) {
+ let (editor, search_bar, cx) = init_test(cx);
+
+ update_search_settings(
+ SearchSettings {
+ button: true,
+ whole_word: false,
+ case_sensitive: false,
+ include_ignored: false,
+ regex: false,
+ center_on_match: false,
+ search_on_input: false,
+ },
+ cx,
+ );
+
+ search_bar.update_in(cx, |search_bar, window, cx| {
+ search_bar.show(window, cx);
+ search_bar.query_editor.update(cx, |query_editor, cx| {
+ query_editor.buffer().update(cx, |buffer, cx| {
+ buffer.edit(
+ [(MultiBufferOffset(0)..MultiBufferOffset(0), "expression")],
+ None,
+ cx,
+ );
+ });
+ });
+ });
+
+ cx.background_executor.run_until_parked();
+
+ editor.update_in(cx, |editor, window, cx| {
+ let highlights = editor.all_text_background_highlights(window, cx);
+ assert!(
+ highlights.is_empty(),
+ "No highlights should appear when search_on_input is false"
+ );
+ });
+
+ update_search_settings(
+ SearchSettings {
+ button: true,
+ whole_word: false,
+ case_sensitive: false,
+ include_ignored: false,
+ regex: false,
+ center_on_match: false,
+ search_on_input: true,
+ },
+ cx,
+ );
+
+ search_bar.update_in(cx, |search_bar, window, cx| {
+ search_bar.dismiss(&Dismiss, window, cx);
+ search_bar.show(window, cx);
+ });
+
+ search_bar
+ .update_in(cx, |search_bar, window, cx| {
+ search_bar.search("expression", None, true, window, cx)
+ })
+ .await
+ .unwrap();
+
+ editor.update_in(cx, |editor, window, cx| {
+ let highlights = display_points_of(editor.all_text_background_highlights(window, cx));
+ assert_eq!(
+ highlights.len(),
+ 2,
+ "Should find 2 matches for 'expression' when search_on_input is true"
+ );
+ assert_eq!(
+ highlights,
+ &[
+ DisplayPoint::new(DisplayRow(0), 10)..DisplayPoint::new(DisplayRow(0), 20),
+ DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 19),
+ ]
+ );
+ });
+ }
}
diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs
index 7c85077c488371e611fdadf0baf7ee94f49fe511..acd6576ce298ef86282cd0e6a6f98cde1d62bfba 100644
--- a/crates/search/src/project_search.rs
+++ b/crates/search/src/project_search.rs
@@ -869,15 +869,32 @@ 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
- && 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);
+ 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);
+ }
+ }
+ // 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
+ 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;
+ cx.notify();
+ });
+ } else {
+ this.search(cx);
+ }
}
}
cx.emit(ViewEvent::EditorEvent(event.clone()))
@@ -1529,7 +1546,11 @@ impl ProjectSearchView {
editor.scroll(Point::default(), Some(Axis::Vertical), window, cx);
}
});
- if is_new_search && self.query_editor.focus_handle(cx).is_focused(window) {
+ 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
+ {
self.focus_results_editor(window, cx);
}
}
@@ -1592,9 +1613,13 @@ impl ProjectSearchView {
v_flex()
.gap_1()
.child(
- Label::new("Hit enter to search. For more options:")
- .color(Color::Muted)
- .mb_2(),
+ 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(),
)
.child(
Button::new("filter-paths", "Include/exclude specific paths")
@@ -2537,7 +2562,8 @@ pub mod tests {
use project::FakeFs;
use serde_json::json;
use settings::{
- InlayHintSettingsContent, SettingsStore, ThemeColorsContent, ThemeStyleContent,
+ InlayHintSettingsContent, SearchSettingsContent, SettingsStore, ThemeColorsContent,
+ ThemeStyleContent,
};
use util::{path, paths::PathStyle, rel_path::rel_path};
use util_macros::perf;
@@ -4756,6 +4782,15 @@ pub mod tests {
let settings = SettingsStore::test(cx);
cx.set_global(settings);
+ SettingsStore::update_global(cx, |store, cx| {
+ store.update_user_settings(cx, |settings| {
+ settings.editor.search = Some(SearchSettingsContent {
+ search_on_input: Some(false),
+ ..Default::default()
+ });
+ });
+ });
+
theme::init(theme::LoadThemes::JustBase, cx);
editor::init(cx);
diff --git a/crates/settings_content/src/editor.rs b/crates/settings_content/src/editor.rs
index 4d824e85e0e2ee020f48cdddb530bf494b2ce800..a2f53e1b9e309e5b07eaef7f7730773abfac70a9 100644
--- a/crates/settings_content/src/editor.rs
+++ b/crates/settings_content/src/editor.rs
@@ -828,6 +828,8 @@ pub struct SearchSettingsContent {
pub regex: Option,
/// Whether to center the cursor on each search match when navigating.
pub center_on_match: Option,
+ /// Whether to search on input.
+ pub search_on_input: Option,
}
#[with_fallible_options]
diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs
index 81500ce1730868e8090df6d97b5c84a25dd965fb..32fe90a9a39700d993d61051497a7c2ef1edddc9 100644
--- a/crates/settings_ui/src/page_data.rs
+++ b/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; 9] {
+ fn search_section() -> [SettingsPageItem; 10] {
[
SettingsPageItem::SectionHeader("Search"),
SettingsPageItem::SettingItem(SettingItem {
@@ -3133,6 +3133,29 @@ fn search_and_files_page() -> SettingsPage {
metadata: None,
files: USER,
}),
+ SettingsPageItem::SettingItem(SettingItem {
+ title: "Search on Input",
+ description: "Whether to search on input.",
+ 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.",
diff --git a/docs/src/reference/all-settings.md b/docs/src/reference/all-settings.md
index 4aaf979993802da20d0b1a0f43d1a1323008611c..56f5b234aef463304874efc16177727196a320fb 100644
--- a/docs/src/reference/all-settings.md
+++ b/docs/src/reference/all-settings.md
@@ -3270,6 +3270,12 @@ Non-negative `integer` values
- Setting: `regex`
- Default: `false`
+### Search On Input
+
+- Description: Whether to search on input.
+- Setting: `search_on_input
+- Default: `true`
+
### Center On Match
- Description: Whether to center the cursor on each search match when navigating.
@@ -3282,12 +3288,6 @@ Non-negative `integer` values
- Setting: `search_wrap`
- Default: `true`
-## Center on Match
-
-- Description: If `center_on_match` is enabled, the editor will center the cursor on the current match when searching.
-- Setting: `center_on_match`
-- Default: `false`
-
## Seed Search Query From Cursor
- Description: When to populate a new search's query based on the text under the cursor.