From dfdf7e4866989bd71ce2ad0fd810f61cd9bfda3e Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Tue, 9 May 2023 23:33:55 +0300 Subject: [PATCH] Test the search inclusions/exclusions --- crates/project/src/project.rs | 6 +- crates/project/src/project_tests.rs | 368 ++++++++++++++++++++++++++-- crates/search/src/project_search.rs | 6 +- crates/workspace/src/toolbar.rs | 3 + 4 files changed, 353 insertions(+), 30 deletions(-) diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index a48111fdd2..94872708c4 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -4208,11 +4208,9 @@ impl Project { if matching_paths_tx.is_closed() { break; } - let matches = if !query + let matches = if query .file_matches(Some(&entry.path)) { - false - } else { abs_path.clear(); abs_path.push(&snapshot.abs_path()); abs_path.push(&entry.path); @@ -4223,6 +4221,8 @@ impl Project { } else { false } + } else { + false }; if matches { diff --git a/crates/project/src/project_tests.rs b/crates/project/src/project_tests.rs index 24c4ff3821..894b27f2ee 100644 --- a/crates/project/src/project_tests.rs +++ b/crates/project/src/project_tests.rs @@ -3335,28 +3335,348 @@ async fn test_search(cx: &mut gpui::TestAppContext) { ("four.rs".to_string(), vec![25..28, 36..39]) ]) ); - - async fn search( - project: &ModelHandle, - query: SearchQuery, - cx: &mut gpui::TestAppContext, - ) -> Result>>> { - let results = project - .update(cx, |project, cx| project.search(query, cx)) - .await?; - - Ok(results - .into_iter() - .map(|(buffer, ranges)| { - buffer.read_with(cx, |buffer, _| { - let path = buffer.file().unwrap().path().to_string_lossy().to_string(); - let ranges = ranges - .into_iter() - .map(|range| range.to_offset(buffer)) - .collect::>(); - (path, ranges) - }) - }) - .collect()) - } +} + +#[gpui::test] +async fn test_search_with_inclusions(cx: &mut gpui::TestAppContext) { + let search_query = "file"; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": r#"// Rust file one"#, + "one.ts": r#"// TypeScript file one"#, + "two.rs": r#"// Rust file two"#, + "two.ts": r#"// TypeScript file two"#, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![glob::Pattern::new("*.odd").unwrap()], + Vec::new() + ), + cx + ) + .await + .unwrap() + .is_empty(), + "If no inclusions match, no files should be returned" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![glob::Pattern::new("*.rs").unwrap()], + Vec::new() + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("two.rs".to_string(), vec![8..12]), + ]), + "Rust only search should give only Rust files" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap(), + ], + Vec::new() + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.ts".to_string(), vec![14..18]), + ("two.ts".to_string(), vec![14..18]), + ]), + "TypeScript only search should give only TypeScript files, even if other inclusions don't match anything" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + glob::Pattern::new("*.rs").unwrap(), + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap(), + ], + Vec::new() + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("one.ts".to_string(), vec![14..18]), + ("two.rs".to_string(), vec![8..12]), + ("two.ts".to_string(), vec![14..18]), + ]), + "Rust and typescript search should give both Rust and TypeScript files, even if other inclusions don't match anything" + ); +} + +#[gpui::test] +async fn test_search_with_exclusions(cx: &mut gpui::TestAppContext) { + let search_query = "file"; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": r#"// Rust file one"#, + "one.ts": r#"// TypeScript file one"#, + "two.rs": r#"// Rust file two"#, + "two.ts": r#"// TypeScript file two"#, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![glob::Pattern::new("*.odd").unwrap()], + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("one.ts".to_string(), vec![14..18]), + ("two.rs".to_string(), vec![8..12]), + ("two.ts".to_string(), vec![14..18]), + ]), + "If no exclusions match, all files should be returned" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![glob::Pattern::new("*.rs").unwrap()], + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.ts".to_string(), vec![14..18]), + ("two.ts".to_string(), vec![14..18]), + ]), + "Rust exclusion search should give only TypeScript files" + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![ + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap(), + ], + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.rs".to_string(), vec![8..12]), + ("two.rs".to_string(), vec![8..12]), + ]), + "TypeScript exclusion search should give only Rust files, even if other exclusions don't match anything" + ); + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + Vec::new(), + vec![ + glob::Pattern::new("*.rs").unwrap(), + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap(), + ], + ), + cx + ) + .await + .unwrap().is_empty(), + "Rust and typescript exclusion should give no files, even if other exclusions don't match anything" + ); +} + +#[gpui::test] +async fn test_search_with_exclusions_and_inclusions(cx: &mut gpui::TestAppContext) { + let search_query = "file"; + + let fs = FakeFs::new(cx.background()); + fs.insert_tree( + "/dir", + json!({ + "one.rs": r#"// Rust file one"#, + "one.ts": r#"// TypeScript file one"#, + "two.rs": r#"// Rust file two"#, + "two.ts": r#"// TypeScript file two"#, + }), + ) + .await; + let project = Project::test(fs.clone(), ["/dir".as_ref()], cx).await; + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![glob::Pattern::new("*.odd").unwrap()], + vec![glob::Pattern::new("*.odd").unwrap()], + ), + cx + ) + .await + .unwrap() + .is_empty(), + "If both no exclusions and inclusions match, exclusions should win and return nothing" + ); + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![glob::Pattern::new("*.ts").unwrap()], + vec![glob::Pattern::new("*.ts").unwrap()], + ), + cx + ) + .await + .unwrap() + .is_empty(), + "If both TypeScript exclusions and inclusions match, exclusions should win and return nothing files." + ); + + assert!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap() + ], + vec![ + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap() + ], + ), + cx + ) + .await + .unwrap() + .is_empty(), + "Non-matching inclusions and exclusions should not change that." + ); + + assert_eq!( + search( + &project, + SearchQuery::text( + search_query, + false, + true, + vec![ + glob::Pattern::new("*.ts").unwrap(), + glob::Pattern::new("*.odd").unwrap() + ], + vec![ + glob::Pattern::new("*.rs").unwrap(), + glob::Pattern::new("*.odd").unwrap() + ], + ), + cx + ) + .await + .unwrap(), + HashMap::from_iter([ + ("one.ts".to_string(), vec![14..18]), + ("two.ts".to_string(), vec![14..18]), + ]), + "Non-intersecting TypeScript inclusions and Rust exclusions should return TypeScript files" + ); +} + +async fn search( + project: &ModelHandle, + query: SearchQuery, + cx: &mut gpui::TestAppContext, +) -> Result>>> { + let results = project + .update(cx, |project, cx| project.search(query, cx)) + .await?; + + Ok(results + .into_iter() + .map(|(buffer, ranges)| { + buffer.read_with(cx, |buffer, _| { + let path = buffer.file().unwrap().path().to_string_lossy().to_string(); + let ranges = ranges + .into_iter() + .map(|range| range.to_offset(buffer)) + .collect::>(); + (path, ranges) + }) + }) + .collect()) } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 61de5890dd..a660a6dce8 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -428,7 +428,7 @@ impl ProjectSearchView { editor.set_text(query_text, cx); editor }); - // Subcribe to query_editor in order to reraise editor events for workspace item activation purposes + // Subscribe to query_editor in order to reraise editor events for workspace item activation purposes cx.subscribe(&query_editor, |_, _, event, cx| { cx.emit(ViewEvent::EditorEvent(event.clone())) }) @@ -462,7 +462,7 @@ impl ProjectSearchView { editor }); - // Subcribe to include_files_editor in order to reraise editor events for workspace item activation purposes + // Subscribe to include_files_editor in order to reraise editor events for workspace item activation purposes cx.subscribe(&included_files_editor, |_, _, event, cx| { cx.emit(ViewEvent::EditorEvent(event.clone())) }) @@ -479,7 +479,7 @@ impl ProjectSearchView { editor }); - // Subcribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes + // Subscribe to excluded_files_editor in order to reraise editor events for workspace item activation purposes cx.subscribe(&excluded_files_editor, |_, _, event, cx| { cx.emit(ViewEvent::EditorEvent(event.clone())) }) diff --git a/crates/workspace/src/toolbar.rs b/crates/workspace/src/toolbar.rs index 9a00462901..b2832aa1e8 100644 --- a/crates/workspace/src/toolbar.rs +++ b/crates/workspace/src/toolbar.rs @@ -23,6 +23,9 @@ pub trait ToolbarItemView: View { fn pane_focus_update(&mut self, _pane_focused: bool, _cx: &mut ViewContext) {} + /// Number of times toolbar's height will be repeated to get the effective height. + /// Useful when multiple rows one under each other are needed. + /// The rows have the same width and act as a whole when reacting to resizes and similar events. fn row_count(&self) -> usize { 1 }