From e8d453721c9869c634aadbade96c9440e45dfddc Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 19 Feb 2026 18:24:54 +0200 Subject: [PATCH] Revert "Enable type on search by default for the project search (#49374)" (#49619) 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 --- 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(-) diff --git a/Cargo.lock b/Cargo.lock index e6b8178878125457b679c5e86f5a8f4693492bdb..dfcb8cc63b1e51995e3c3eca2a3d8c41fb4e1e98 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14770,7 +14770,6 @@ dependencies = [ "language", "lsp", "menu", - "multi_buffer", "pretty_assertions", "project", "serde", diff --git a/assets/settings/default.json b/assets/settings/default.json index 58a3f97cd9b033a67c2102130ca6890fabbd9300..409a7a1ddd7eb9421c740715bf17616a2840e101 100644 --- a/assets/settings/default.json +++ b/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: diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 336d2492c9a393e8747325cb4ab0161098fb5be0..98283f045853e1df0dab7ffd0955aa52249377e6 100644 --- a/crates/editor/src/editor_settings.rs +++ b/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(), diff --git a/crates/editor/src/split.rs b/crates/editor/src/split.rs index 7a1db34e24c06d0fc53bbc7011edd2dc4e3bf30b..cfa40235e851f74ac3c5c0a5d9a59a45b737ff7b 100644 --- a/crates/editor/src/split.rs +++ b/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::>(); + .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::>(); - 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> = { + 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 = + lhs_result.excerpt_ids.iter().dedup().copied().collect(); + + Some((deduplicated_lhs_ids, rhs_merge_groups)) } fn sync_path_excerpts( diff --git a/crates/multi_buffer/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs index e2335497939b706e4d531fd33b3cf52f39d87154..15d7b9f3610eaf9e9063c7da95e915c73f95a341 100644 --- a/crates/multi_buffer/src/multi_buffer.rs +++ b/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> + 'a, ) -> (Vec>, Vec) { let mut merged_ranges: Vec> = Vec::new(); diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 464c30aee1d40b380cea27d960bfc3c4ea106617..be2b7bfa33954b6d8a19c42b544822cb1608aaef 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -15,7 +15,6 @@ use crate::{ #[derive(Debug, Clone)] pub struct PathExcerptInsertResult { - pub inserted_ranges: Vec>, pub excerpt_ids: Vec, 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, ) -> (Vec>, 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, @@ -282,110 +273,36 @@ impl MultiBuffer { new: Vec>, counts: Vec, cx: &mut Context, - ) -> 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>, 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>, - counts: Vec, - cx: &App, - ) -> (Vec>, Vec) { - 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> = 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> = Vec::new(); - let mut result_counts: Vec = 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_snapshot: &BufferSnapshot, new: Vec>, cx: &mut Context, - ) -> (Vec, 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, + } } } diff --git a/crates/search/Cargo.toml b/crates/search/Cargo.toml index d4a962523b9b7b43873d8c347ba8db42f0f15ac1..9613bd720919d77f2e7c9421ed51a0b18edf7355 100644 --- a/crates/search/Cargo.toml +++ b/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"] } diff --git a/crates/search/src/buffer_search.rs b/crates/search/src/buffer_search.rs index 7fe9af4ff0f7adfa42796b02b3955aae7a71b2c6..eac830c140107ce822ca6149da296b9e7a7308f6 100644 --- a/crates/search/src/buffer_search.rs +++ b/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), }); }); }); diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index c11b793e2e86c0f08ed0641c7600e6205a90ad09..9b23c96259e4933bc1660af960b508c0678fe767 100644 --- a/crates/search/src/project_search.rs +++ b/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) { + fn search(&mut self, query: SearchQuery, cx: &mut Context) { 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::>() - }); - (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::>(); - 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) { + fn search(&mut self, cx: &mut Context) { 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) { - 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, - cx: &mut TestAppContext, - ) -> (Rc>>, Subscription) { - let events: Rc>> = 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, - text: impl Into>, - 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, - text: impl Into>, - 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, - 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, - cx: &mut TestAppContext, - ) -> Vec { - 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::()) - .collect() - }) - .unwrap() - } - - fn assert_all_highlights_match_query( - search_view: WindowHandle, - 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 = (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 { - 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, + text: impl Into>, + 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(); } } diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 13252b1366a91d65eae318d48c1657f1feb79dbe..d0643be3bbee82be02c9c461a5f18ba62893a3cd 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -357,8 +357,7 @@ impl VsCodeSettings { fn search_content(&self) -> Option { skip_default(SearchSettingsContent { include_ignored: self.read_bool("search.useIgnoreFiles"), - search_on_input: self.read_bool("search.searchOnType"), - ..SearchSettingsContent::default() + ..Default::default() }) } diff --git a/crates/settings_content/src/editor.rs b/crates/settings_content/src/editor.rs index aee50c9df8ba55e49d55c4148e94c57de629fd0b..4d824e85e0e2ee020f48cdddb530bf494b2ce800 100644 --- a/crates/settings_content/src/editor.rs +++ b/crates/settings_content/src/editor.rs @@ -828,8 +828,6 @@ 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 in project search. - 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 d3ccc428408a7b59e4f833f36bc7f45d1164b101..738eff917bc57a7a2543f9c31494af02883299d1 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; 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.", diff --git a/docs/src/reference/all-settings.md b/docs/src/reference/all-settings.md index 45bd5a23ac2be11f020d6ead9e0cdd04a049ad8c..b32d75b9ab5cdc654f13f01f4b08f36b6e03f6b5 100644 --- a/docs/src/reference/all-settings.md +++ b/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