diff --git a/crates/outline_panel/src/outline_panel.rs b/crates/outline_panel/src/outline_panel.rs index f9b1afe34e5ebf51576b07164f5ccfa23428ca56..69f0857df517724c70359b5043125765b83c29b1 100644 --- a/crates/outline_panel/src/outline_panel.rs +++ b/crates/outline_panel/src/outline_panel.rs @@ -465,14 +465,8 @@ impl SearchData { let match_offset_range = match_range.to_offset(multi_buffer_snapshot); let mut search_match_indices = vec![ - multi_buffer_snapshot.clip_offset( - match_offset_range.start - context_offset_range.start, - Bias::Left, - ) - ..multi_buffer_snapshot.clip_offset( - match_offset_range.end - context_offset_range.start, - Bias::Right, - ), + match_offset_range.start - context_offset_range.start + ..match_offset_range.end - context_offset_range.start, ]; let entire_context_text = multi_buffer_snapshot @@ -509,14 +503,8 @@ impl SearchData { .next() .is_some_and(|c| !c.is_whitespace()); search_match_indices.iter_mut().for_each(|range| { - range.start = multi_buffer_snapshot.clip_offset( - range.start.saturating_sub(left_whitespaces_offset), - Bias::Left, - ); - range.end = multi_buffer_snapshot.clip_offset( - range.end.saturating_sub(left_whitespaces_offset), - Bias::Right, - ); + range.start = range.start.saturating_sub(left_whitespaces_offset); + range.end = range.end.saturating_sub(left_whitespaces_offset); }); let trimmed_row_offset_range = @@ -5256,10 +5244,13 @@ mod tests { use language::{Language, LanguageConfig, LanguageMatcher, tree_sitter_rust}; use pretty_assertions::assert_eq; use project::FakeFs; - use search::project_search::{self, perform_project_search}; + use search::{ + buffer_search, + project_search::{self, perform_project_search}, + }; use serde_json::json; use util::path; - use workspace::{OpenOptions, OpenVisible}; + use workspace::{OpenOptions, OpenVisible, ToolbarItemView}; use super::*; @@ -5322,25 +5313,28 @@ mod tests { ide/src/ inlay_hints/ fn_lifetime_fn.rs - search: match config.param_names_for_lifetime_elision_hints { - search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { - search: Some(it) if config.param_names_for_lifetime_elision_hints => { - search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, + search: match config.«param_names_for_lifetime_elision_hints» { + search: allocated_lifetimes.push(if config.«param_names_for_lifetime_elision_hints» { + search: Some(it) if config.«param_names_for_lifetime_elision_hints» => { + search: InlayHintsConfig { «param_names_for_lifetime_elision_hints»: true, ..TEST_CONFIG }, inlay_hints.rs - search: pub param_names_for_lifetime_elision_hints: bool, - search: param_names_for_lifetime_elision_hints: self + search: pub «param_names_for_lifetime_elision_hints»: bool, + search: «param_names_for_lifetime_elision_hints»: self static_index.rs - search: param_names_for_lifetime_elision_hints: false, + search: «param_names_for_lifetime_elision_hints»: false, rust-analyzer/src/ cli/ analysis_stats.rs - search: param_names_for_lifetime_elision_hints: true, + search: «param_names_for_lifetime_elision_hints»: true, config.rs - search: param_names_for_lifetime_elision_hints: self"# + search: «param_names_for_lifetime_elision_hints»: self"# .to_string(); let select_first_in_all_matches = |line_to_select: &str| { - assert!(all_matches.contains(line_to_select)); + assert!( + all_matches.contains(line_to_select), + "`{line_to_select}` was not found in all matches `{all_matches}`" + ); all_matches.replacen( line_to_select, &format!("{line_to_select}{SELECTED_MARKER}"), @@ -5361,7 +5355,7 @@ mod tests { cx, ), select_first_in_all_matches( - "search: match config.param_names_for_lifetime_elision_hints {" + "search: match config.«param_names_for_lifetime_elision_hints» {" ) ); }); @@ -5401,16 +5395,16 @@ mod tests { inlay_hints/ fn_lifetime_fn.rs{SELECTED_MARKER} inlay_hints.rs - search: pub param_names_for_lifetime_elision_hints: bool, - search: param_names_for_lifetime_elision_hints: self + search: pub «param_names_for_lifetime_elision_hints»: bool, + search: «param_names_for_lifetime_elision_hints»: self static_index.rs - search: param_names_for_lifetime_elision_hints: false, + search: «param_names_for_lifetime_elision_hints»: false, rust-analyzer/src/ cli/ analysis_stats.rs - search: param_names_for_lifetime_elision_hints: true, + search: «param_names_for_lifetime_elision_hints»: true, config.rs - search: param_names_for_lifetime_elision_hints: self"#, + search: «param_names_for_lifetime_elision_hints»: self"#, ) ); }); @@ -5471,9 +5465,9 @@ mod tests { rust-analyzer/src/ cli/ analysis_stats.rs - search: param_names_for_lifetime_elision_hints: true, + search: «param_names_for_lifetime_elision_hints»: true, config.rs - search: param_names_for_lifetime_elision_hints: self"#, + search: «param_names_for_lifetime_elision_hints»: self"#, ) ); }); @@ -5553,21 +5547,21 @@ mod tests { ide/src/ inlay_hints/ fn_lifetime_fn.rs - search: match config.param_names_for_lifetime_elision_hints { - search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { - search: Some(it) if config.param_names_for_lifetime_elision_hints => { - search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, + search: match config.«param_names_for_lifetime_elision_hints» { + search: allocated_lifetimes.push(if config.«param_names_for_lifetime_elision_hints» { + search: Some(it) if config.«param_names_for_lifetime_elision_hints» => { + search: InlayHintsConfig { «param_names_for_lifetime_elision_hints»: true, ..TEST_CONFIG }, inlay_hints.rs - search: pub param_names_for_lifetime_elision_hints: bool, - search: param_names_for_lifetime_elision_hints: self + search: pub «param_names_for_lifetime_elision_hints»: bool, + search: «param_names_for_lifetime_elision_hints»: self static_index.rs - search: param_names_for_lifetime_elision_hints: false, + search: «param_names_for_lifetime_elision_hints»: false, rust-analyzer/src/ cli/ analysis_stats.rs - search: param_names_for_lifetime_elision_hints: true, + search: «param_names_for_lifetime_elision_hints»: true, config.rs - search: param_names_for_lifetime_elision_hints: self"# + search: «param_names_for_lifetime_elision_hints»: self"# .to_string(); cx.executor() @@ -5692,30 +5686,40 @@ mod tests { ide/src/ inlay_hints/ fn_lifetime_fn.rs - search: match config.param_names_for_lifetime_elision_hints { - search: allocated_lifetimes.push(if config.param_names_for_lifetime_elision_hints { - search: Some(it) if config.param_names_for_lifetime_elision_hints => { - search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG }, + search: match config.«param_names_for_lifetime_elision_hints» { + search: allocated_lifetimes.push(if config.«param_names_for_lifetime_elision_hints» { + search: Some(it) if config.«param_names_for_lifetime_elision_hints» => { + search: InlayHintsConfig { «param_names_for_lifetime_elision_hints»: true, ..TEST_CONFIG }, inlay_hints.rs - search: pub param_names_for_lifetime_elision_hints: bool, - search: param_names_for_lifetime_elision_hints: self + search: pub «param_names_for_lifetime_elision_hints»: bool, + search: «param_names_for_lifetime_elision_hints»: self static_index.rs - search: param_names_for_lifetime_elision_hints: false, + search: «param_names_for_lifetime_elision_hints»: false, rust-analyzer/src/ cli/ analysis_stats.rs - search: param_names_for_lifetime_elision_hints: true, + search: «param_names_for_lifetime_elision_hints»: true, config.rs - search: param_names_for_lifetime_elision_hints: self"# + search: «param_names_for_lifetime_elision_hints»: self"# .to_string(); let select_first_in_all_matches = |line_to_select: &str| { - assert!(all_matches.contains(line_to_select)); + assert!( + all_matches.contains(line_to_select), + "`{line_to_select}` was not found in all matches `{all_matches}`" + ); all_matches.replacen( line_to_select, &format!("{line_to_select}{SELECTED_MARKER}"), 1, ) }; + let clear_outline_metadata = |input: &str| { + input + .replace("search: ", "") + .replace("«", "") + .replace("»", "") + }; + cx.executor() .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(100)); cx.run_until_parked(); @@ -5726,7 +5730,7 @@ mod tests { .expect("should have an active editor open") }); let initial_outline_selection = - "search: match config.param_names_for_lifetime_elision_hints {"; + "search: match config.«param_names_for_lifetime_elision_hints» {"; outline_panel.update_in(cx, |outline_panel, window, cx| { assert_eq!( display_entries( @@ -5740,7 +5744,7 @@ mod tests { ); assert_eq!( selected_row_text(&active_editor, cx), - initial_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes + clear_outline_metadata(initial_outline_selection), "Should place the initial editor selection on the corresponding search result" ); @@ -5749,7 +5753,7 @@ mod tests { }); let navigated_outline_selection = - "search: Some(it) if config.param_names_for_lifetime_elision_hints => {"; + "search: Some(it) if config.«param_names_for_lifetime_elision_hints» => {"; outline_panel.update(cx, |outline_panel, cx| { assert_eq!( display_entries( @@ -5767,7 +5771,7 @@ mod tests { outline_panel.update(cx, |_, cx| { assert_eq!( selected_row_text(&active_editor, cx), - navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes + clear_outline_metadata(navigated_outline_selection), "Should still have the initial caret position after SelectNext calls" ); }); @@ -5778,7 +5782,7 @@ mod tests { outline_panel.update(cx, |_outline_panel, cx| { assert_eq!( selected_row_text(&active_editor, cx), - navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes + clear_outline_metadata(navigated_outline_selection), "After opening, should move the caret to the opened outline entry's position" ); }); @@ -5786,7 +5790,7 @@ mod tests { outline_panel.update_in(cx, |outline_panel, window, cx| { outline_panel.select_next(&SelectNext, window, cx); }); - let next_navigated_outline_selection = "search: InlayHintsConfig { param_names_for_lifetime_elision_hints: true, ..TEST_CONFIG },"; + let next_navigated_outline_selection = "search: InlayHintsConfig { «param_names_for_lifetime_elision_hints»: true, ..TEST_CONFIG },"; outline_panel.update(cx, |outline_panel, cx| { assert_eq!( display_entries( @@ -5804,7 +5808,7 @@ mod tests { outline_panel.update(cx, |_outline_panel, cx| { assert_eq!( selected_row_text(&active_editor, cx), - next_navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes + clear_outline_metadata(next_navigated_outline_selection), "Should again preserve the selection after another SelectNext call" ); }); @@ -5837,7 +5841,7 @@ mod tests { ); assert_eq!( selected_row_text(&new_active_editor, cx), - next_navigated_outline_selection.replace("search: ", ""), // Clear outline metadata prefixes + clear_outline_metadata(next_navigated_outline_selection), "When opening the excerpt, should navigate to the place corresponding the outline entry" ); }); @@ -5939,11 +5943,11 @@ mod tests { format!( r#"one/ a.txt - search: aaa aaa <==== selected - search: aaa aaa + search: «aaa» aaa <==== selected + search: aaa «aaa» two/ b.txt - search: a aaa"#, + search: a «aaa»"#, ), ); }); @@ -5969,7 +5973,7 @@ two/ a.txt <==== selected two/ b.txt - search: a aaa"#, + search: a «aaa»"#, ), ); }); @@ -6018,7 +6022,7 @@ two/ <==== selected"#, a.txt two/ <==== selected b.txt - search: a aaa"#, + search: a «aaa»"#, ) ); }); @@ -6483,18 +6487,18 @@ outline: struct OutlineEntryExcerpt r#"frontend-project/ public/lottie/ syntax-tree.json - search: {{ "something": "static" }} <==== selected + search: {{ "something": "«static»" }} <==== selected src/ app/(site)/ (about)/jobs/[slug]/ page.tsx - search: static + search: «static» (blog)/post/[slug]/ page.tsx - search: static + search: «static» components/ ErrorBoundary.tsx - search: static"# + search: «static»"# ) ); }); @@ -6522,12 +6526,12 @@ outline: struct OutlineEntryExcerpt r#"frontend-project/ public/lottie/ syntax-tree.json - search: {{ "something": "static" }} + search: {{ "something": "«static»" }} src/ app/(site)/ <==== selected components/ ErrorBoundary.tsx - search: static"# + search: «static»"# ) ); }); @@ -6552,12 +6556,12 @@ outline: struct OutlineEntryExcerpt r#"frontend-project/ public/lottie/ syntax-tree.json - search: {{ "something": "static" }} + search: {{ "something": "«static»" }} src/ app/(site)/ components/ ErrorBoundary.tsx - search: static <==== selected"# + search: «static» <==== selected"# ) ); }); @@ -6586,7 +6590,7 @@ outline: struct OutlineEntryExcerpt r#"frontend-project/ public/lottie/ syntax-tree.json - search: {{ "something": "static" }} + search: {{ "something": "«static»" }} src/ app/(site)/ components/ @@ -6619,12 +6623,12 @@ outline: struct OutlineEntryExcerpt r#"frontend-project/ public/lottie/ syntax-tree.json - search: {{ "something": "static" }} + search: {{ "something": "«static»" }} src/ app/(site)/ components/ ErrorBoundary.tsx <==== selected - search: static"# + search: «static»"# ) ); }); @@ -6667,18 +6671,18 @@ outline: struct OutlineEntryExcerpt r#"frontend-project/ public/lottie/ syntax-tree.json - search: {{ "something": "static" }} + search: {{ "something": "«static»" }} src/ app/(site)/ (about)/jobs/[slug]/ page.tsx - search: static + search: «static» (blog)/post/[slug]/ page.tsx - search: static + search: «static» components/ ErrorBoundary.tsx <==== selected - search: static"# + search: «static»"# ) ); }); @@ -6784,16 +6788,21 @@ outline: struct OutlineEntryExcerpt } }, PanelEntry::Search(search_entry) => { - format!( - "search: {}", - search_entry - .render_data - .get_or_init(|| SearchData::new( - &search_entry.match_range, - multi_buffer_snapshot - )) - .context_text - ) + let search_data = search_entry.render_data.get_or_init(|| { + SearchData::new(&search_entry.match_range, multi_buffer_snapshot) + }); + let mut search_result = String::new(); + let mut last_end = 0; + for range in &search_data.search_match_indices { + search_result.push_str(&search_data.context_text[last_end..range.start]); + search_result.push('«'); + search_result.push_str(&search_data.context_text[range.start..range.end]); + search_result.push('»'); + last_end = range.end; + } + search_result.push_str(&search_data.context_text[last_end..]); + + format!("search: {search_result}") } }; @@ -6816,6 +6825,7 @@ outline: struct OutlineEntryExcerpt workspace::init_settings(cx); Project::init_settings(cx); project_search::init(cx); + buffer_search::init(cx); super::init(cx); }); } @@ -7827,4 +7837,102 @@ outline: fn main()" }; }); } + + #[gpui::test] + async fn test_buffer_search(cx: &mut TestAppContext) { + init_test(cx); + + let fs = FakeFs::new(cx.background_executor.clone()); + fs.insert_tree( + "/test", + json!({ + "foo.txt": r#"<_constitution> + + + + + +## 📊 Output + +| Field | Meaning | +"# + }), + ) + .await; + + let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; + let workspace = add_outline_panel(&project, cx).await; + let cx = &mut VisualTestContext::from_window(*workspace, cx); + + let editor = workspace + .update(cx, |workspace, window, cx| { + workspace.open_abs_path( + PathBuf::from("/test/foo.txt"), + OpenOptions { + visible: Some(OpenVisible::All), + ..OpenOptions::default() + }, + window, + cx, + ) + }) + .unwrap() + .await + .unwrap() + .downcast::() + .unwrap(); + + let search_bar = workspace + .update(cx, |_, window, cx| { + cx.new(|cx| { + let mut search_bar = BufferSearchBar::new(None, window, cx); + search_bar.set_active_pane_item(Some(&editor), window, cx); + search_bar.show(window, cx); + search_bar + }) + }) + .unwrap(); + + let outline_panel = outline_panel(&workspace, cx); + + outline_panel.update_in(cx, |outline_panel, window, cx| { + outline_panel.set_active(true, window, cx) + }); + + search_bar + .update_in(cx, |search_bar, window, cx| { + search_bar.search(" ", None, true, window, cx) + }) + .await + .unwrap(); + + cx.executor() + .advance_clock(UPDATE_DEBOUNCE + Duration::from_millis(500)); + cx.run_until_parked(); + + outline_panel.update(cx, |outline_panel, cx| { + assert_eq!( + display_entries( + &project, + &snapshot(outline_panel, cx), + &outline_panel.cached_entries, + outline_panel.selected_entry(), + cx, + ), + "search: | Field« » | Meaning | <==== selected +search: | Field « » | Meaning | +search: | Field « » | Meaning | +search: | Field « » | Meaning | +search: | Field « »| Meaning | +search: | Field | Meaning« » | +search: | Field | Meaning « » | +search: | Field | Meaning « » | +search: | Field | Meaning « » | +search: | Field | Meaning « » | +search: | Field | Meaning « » | +search: | Field | Meaning « » | +search: | Field | Meaning « »|" + ); + }); + } }