From c061f1ce5c7e0684c0015291ad93b40f7ef04141 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Mon, 13 Apr 2026 14:18:40 +0300 Subject: [PATCH] Fix the tests --- crates/multi_buffer/src/path_key.rs | 74 +----------- crates/search/src/project_search.rs | 180 ++++++++++------------------ 2 files changed, 65 insertions(+), 189 deletions(-) diff --git a/crates/multi_buffer/src/path_key.rs b/crates/multi_buffer/src/path_key.rs index 3df0236dd54058a089c43d39579e8d8281a290ac..5c2123d0f9c1b09c16fd99531973df81c45140f7 100644 --- a/crates/multi_buffer/src/path_key.rs +++ b/crates/multi_buffer/src/path_key.rs @@ -100,7 +100,7 @@ impl MultiBuffer { let merged = Self::merge_excerpt_ranges(&excerpt_ranges); let (inserted, _path_key_index) = - let excerpt_insertion_result = self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, merged, cx); + self.set_merged_excerpt_ranges_for_path(path, buffer, &buffer_snapshot, merged, cx); inserted } @@ -367,77 +367,7 @@ impl MultiBuffer { index } - /// 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_ranges.push(range); - result_counts.push(count); - } - - (result_ranges, result_counts) - } - - fn update_path_excerpts( + pub fn update_path_excerpts( &mut self, path_key: PathKey, buffer: Entity, diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 2965e221372317ed121bf8ea4a4c0ee41cbfabca..f91765069680451d65985b63b53fdfe9271d68dc 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -326,9 +326,10 @@ impl ProjectSearch { no_results: self.no_results, limit_reached: self.limit_reached, search_input_confirmed: self.search_input_confirmed, - search_history_cursor: self.search_history_cursor.clone(), - search_included_history_cursor: self.search_included_history_cursor.clone(), - search_excluded_history_cursor: self.search_excluded_history_cursor.clone(),_excerpts_subscription: subscription, + search_history_cursor: self.search_history_cursor.clone(), + search_included_history_cursor: self.search_included_history_cursor.clone(), + search_excluded_history_cursor: self.search_excluded_history_cursor.clone(), + _excerpts_subscription: subscription, } }) } @@ -536,16 +537,17 @@ impl ProjectSearch { .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); - } - }); + // TODO kb + // 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; @@ -2800,41 +2802,32 @@ 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 take_updated_files(events: &RefCell>) -> Vec { + let mut files: Vec = Vec::new(); + for event in events.borrow_mut().drain(..) { + if let MultiBufferEvent::BufferRangesUpdated { path_key, .. } = event { + let name = path_key.path.as_unix_str().to_owned(); + if !files.contains(&name) { + files.push(name); + } + } + } + files.sort(); + files } fn subscribe_to_excerpt_events( search: &Entity, cx: &mut TestAppContext, - ) -> (Rc>>, Subscription) { - let events: Rc>> = Rc::default(); + ) -> (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); - } - _ => {} - }, - ) + cx.subscribe(&excerpts, move |_, event: &MultiBufferEvent, _| { + events.borrow_mut().push(event.clone()); + }) } }); (events, subscription) @@ -2846,6 +2839,7 @@ pub mod tests { cx.set_global(settings); theme::init(theme::LoadThemes::JustBase, cx); + theme_settings::init(theme::LoadThemes::JustBase, cx); editor::init(cx); crate::init(cx); @@ -5771,130 +5765,79 @@ pub mod tests { ProjectSearchView::new(workspace.downgrade(), search.clone(), window, cx, None) }); 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 all_rs_files = vec!["four.rs", "one.rs", "only_one.rs", "three.rs", "two.rs"]; 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. + // Initial non-incremental search for "ONE" — 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 }, - ] - ); + assert_eq!(take_updated_files(&events), all_rs_files); - // Natural narrowing: typing "R" after "ONE" -> "ONER". - // Only one.rs has ONEROUS, 4 other files removed. + // Natural narrowing: "ONE" -> "ONER". Only one.rs has ONEROUS. 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 }, - ] - ); + assert_eq!(take_updated_files(&events), vec!["one.rs"]); - // Continue typing "OUS" -> "ONEROUS". Still one.rs only, zero excerpt churn. + // Continue narrowing: "ONER" -> "ONEROUS". Still one.rs only. 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()); + assert_eq!(take_updated_files(&events), vec!["one.rs"]); - // Backspace to "ONER" — still one.rs only, zero events. + // Backspace to "ONER" — still one.rs only. 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()); + assert_eq!(take_updated_files(&events), vec!["one.rs"]); - // Backspace to "ONE" — 4 files re-added. + // Backspace to "ONE" — all 5 files re-appear. 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 }, - ] - ); + assert_eq!(take_updated_files(&events), all_rs_files); - // Repeat the same "ONE" query — excerpts already match, zero events emitted. + // Repeat the same "ONE" query — excerpts still get updated. 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); + assert_eq!(take_updated_files(&events), all_rs_files); - // Narrow to "ONLY_ONE" — single match in only_one.rs, 4 files removed. + // Narrow to "ONLY_ONE" — single match in only_one.rs. 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 }, - ] - ); + assert_eq!(take_updated_files(&events), vec!["only_one.rs"]); - // Widen back to "ONE" — 4 files re-added. + // Widen back to "ONE" — all 5 files re-appear. 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 }, - ] - ); + assert_eq!(take_updated_files(&events), all_rs_files); - // Narrowing when all files still match — zero excerpt events. - // "usize" matches all 5 .rs files; "usize =" is narrower but still in every file. + // Narrowing when all files still match: "usize" -> "usize =". + // Same file set, same match count. 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); + assert_eq!(take_updated_files(&events), all_rs_files); - // 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. + // Merged-excerpt narrowing: "target" matches lines 5 and 10 in big.txt. + // Narrowing to "targeted" drops to 1 match but big.txt stays. 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(); + events.borrow_mut().clear(); 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()); + assert_eq!(take_updated_files(&events), vec!["big.txt"]); } #[gpui::test] @@ -5930,9 +5873,12 @@ pub mod tests { let cx = &mut VisualTestContext::from_window(window.into(), cx); let search_bar = window.build_entity(cx, |_, _| ProjectSearchBar::new()); - theme_settings::init(theme::LoadThemes::JustBase, cx); - - ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx) + workspace.update_in(cx, |workspace, window, cx| { + workspace.panes()[0].update(cx, |pane, cx| { + pane.toolbar() + .update(cx, |toolbar, cx| toolbar.add_item(search_bar, window, cx)) + }); + ProjectSearchView::new_search(workspace, &workspace::NewSearch, window, cx); }); let search_view = cx