@@ -89,7 +89,7 @@ impl SearchQuery {
/// Create a text query
///
/// If `match_full_paths` is true, include/exclude patterns will always be matched against fully qualified project paths beginning with a project root.
- /// If `match_full_paths` is false, patterns will be matched against full paths only when the project has multiple roots.
+ /// If `match_full_paths` is false, patterns will be matched against worktree-relative paths.
pub fn text(
query: impl ToString,
whole_word: bool,
@@ -290,7 +290,7 @@ impl SearchQuery {
message.include_ignored,
PathMatcher::new(files_to_include, path_style)?,
PathMatcher::new(files_to_exclude, path_style)?,
- false,
+ message.match_full_paths,
None, // search opened only don't need search remote
)
}
@@ -33,7 +33,7 @@ use std::{
sync::Arc,
};
use unindent::Unindent as _;
-use util::{path, rel_path::rel_path};
+use util::{path, paths::PathMatcher, rel_path::rel_path};
#[gpui::test]
async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
@@ -168,6 +168,51 @@ async fn test_basic_remote_editing(cx: &mut TestAppContext, server_cx: &mut Test
});
}
+async fn do_search_and_assert(
+ project: &Entity<Project>,
+ query: &str,
+ files_to_include: PathMatcher,
+ match_full_paths: bool,
+ expected_paths: &[&str],
+ mut cx: TestAppContext,
+) -> Vec<Entity<Buffer>> {
+ let query = query.to_string();
+ let receiver = project.update(&mut cx, |project, cx| {
+ project.search(
+ SearchQuery::text(
+ query,
+ false,
+ true,
+ false,
+ files_to_include,
+ Default::default(),
+ match_full_paths,
+ None,
+ )
+ .unwrap(),
+ cx,
+ )
+ });
+
+ let mut buffers = Vec::new();
+ for expected_path in expected_paths {
+ let response = receiver.rx.recv().await.unwrap();
+ let SearchResult::Buffer { buffer, .. } = response else {
+ panic!("incorrect result");
+ };
+ buffer.update(&mut cx, |buffer, cx| {
+ assert_eq!(
+ buffer.file().unwrap().full_path(cx).to_string_lossy(),
+ *expected_path
+ )
+ });
+ buffers.push(buffer);
+ }
+
+ assert!(receiver.rx.recv().await.is_err());
+ buffers
+}
+
#[gpui::test]
async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut TestAppContext) {
let fs = FakeFs::new(server_cx.executor());
@@ -196,47 +241,31 @@ async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut Tes
cx.run_until_parked();
- async fn do_search(project: &Entity<Project>, mut cx: TestAppContext) -> Entity<Buffer> {
- let receiver = project.update(&mut cx, |project, cx| {
- project.search(
- SearchQuery::text(
- "project",
- false,
- true,
- false,
- Default::default(),
- Default::default(),
- false,
- None,
- )
- .unwrap(),
- cx,
- )
- });
-
- let first_response = receiver.rx.recv().await.unwrap();
- let SearchResult::Buffer { buffer, .. } = first_response else {
- panic!("incorrect result");
- };
- buffer.update(&mut cx, |buffer, cx| {
- assert_eq!(
- buffer.file().unwrap().full_path(cx).to_string_lossy(),
- path!("project1/README.md")
- )
- });
-
- assert!(receiver.rx.recv().await.is_err());
- buffer
- }
-
- let buffer = do_search(&project, cx.clone()).await;
+ let buffers = do_search_and_assert(
+ &project,
+ "project",
+ Default::default(),
+ false,
+ &[path!("project1/README.md")],
+ cx.clone(),
+ )
+ .await;
+ let buffer = buffers.into_iter().next().unwrap();
// test that the headless server is tracking which buffers we have open correctly.
cx.run_until_parked();
headless.update(server_cx, |headless, cx| {
assert!(headless.buffer_store.read(cx).has_shared_buffers())
});
- do_search(&project, cx.clone()).await;
+ do_search_and_assert(
+ &project,
+ "project",
+ Default::default(),
+ false,
+ &[path!("project1/README.md")],
+ cx.clone(),
+ )
+ .await;
server_cx.run_until_parked();
cx.update(|_| {
drop(buffer);
@@ -247,7 +276,80 @@ async fn test_remote_project_search(cx: &mut TestAppContext, server_cx: &mut Tes
assert!(!headless.buffer_store.read(cx).has_shared_buffers())
});
- do_search(&project, cx.clone()).await;
+ do_search_and_assert(
+ &project,
+ "project",
+ Default::default(),
+ false,
+ &[path!("project1/README.md")],
+ cx.clone(),
+ )
+ .await;
+}
+
+#[gpui::test]
+async fn test_remote_project_search_inclusion(
+ cx: &mut TestAppContext,
+ server_cx: &mut TestAppContext,
+) {
+ let fs = FakeFs::new(server_cx.executor());
+ fs.insert_tree(
+ path!("/code"),
+ json!({
+ "project1": {
+ "README.md": "# project 1",
+ },
+ "project2": {
+ "README.md": "# project 2",
+ },
+ }),
+ )
+ .await;
+
+ let (project, _) = init_test(&fs, cx, server_cx).await;
+
+ project
+ .update(cx, |project, cx| {
+ project.find_or_create_worktree(path!("/code/project1"), true, cx)
+ })
+ .await
+ .unwrap();
+
+ project
+ .update(cx, |project, cx| {
+ project.find_or_create_worktree(path!("/code/project2"), true, cx)
+ })
+ .await
+ .unwrap();
+
+ cx.run_until_parked();
+
+ // Case 1: Test search with path matcher limiting to only one worktree
+ let path_matcher = PathMatcher::new(
+ &["project1/*.md".to_owned()],
+ util::paths::PathStyle::local(),
+ )
+ .unwrap();
+ do_search_and_assert(
+ &project,
+ "project",
+ path_matcher,
+ true, // should be true in case of multiple worktrees
+ &[path!("project1/README.md")],
+ cx.clone(),
+ )
+ .await;
+
+ // Case 2: Test search without path matcher, matching both worktrees
+ do_search_and_assert(
+ &project,
+ "project",
+ Default::default(),
+ true, // should be true in case of multiple worktrees
+ &[path!("project1/README.md"), path!("project2/README.md")],
+ cx.clone(),
+ )
+ .await;
}
#[gpui::test]