For file finder queries, match in all gitignored worktree entries (#3748)

Deals with https://github.com/zed-industries/community/issues/2347
Part of https://github.com/zed-industries/community/issues/1538

Now file finder will match all gitignored worktree entries.
Zed does not traverse gitignored dirs by default, which means that not
all gitignored files will be matches, but all that were toggled in
project panel and all root non-directory gitignored entries will be now
used, hopefully causing less questions.

Release Notes:

- Improved file finder to match all gitignored files that were added
into worktrees (e.g. due to opening gitignored directories in project
panel)
This commit is contained in:
Kirill Bulatov 2023-12-21 02:34:34 +02:00 committed by GitHub
commit 87a1387f9a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 229 additions and 22 deletions

View File

@ -324,15 +324,10 @@ impl FileFinderDelegate {
let include_root_name = worktrees.len() > 1;
let candidate_sets = worktrees
.into_iter()
.map(|worktree| {
let worktree = worktree.read(cx);
PathMatchCandidateSet {
snapshot: worktree.snapshot(),
include_ignored: worktree
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name,
}
.map(|worktree| PathMatchCandidateSet {
snapshot: worktree.read(cx).snapshot(),
include_ignored: true,
include_root_name,
})
.collect::<Vec<_>>();
@ -1058,7 +1053,7 @@ mod tests {
}
#[gpui::test]
async fn test_ignored_files(cx: &mut TestAppContext) {
async fn test_ignored_root(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
@ -1115,7 +1110,105 @@ mod tests {
f.delegate_mut().spawn_search(test_path_like("hi"), cx)
})
.await;
finder.read_with(cx, |f, _| assert_eq!(f.delegate().matches.len(), 7));
finder.update(cx, |f, _| {
assert_eq!(
collect_search_results(f),
vec![
PathBuf::from("ignored-root/happiness"),
PathBuf::from("ignored-root/height"),
PathBuf::from("ignored-root/hi"),
PathBuf::from("ignored-root/hiccup"),
PathBuf::from("tracked-root/happiness"),
PathBuf::from("tracked-root/height"),
PathBuf::from("tracked-root/hi"),
PathBuf::from("tracked-root/hiccup"),
],
"All files in all roots (including gitignored) should be searched"
)
});
}
#[gpui::test]
async fn test_ignored_files(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
".git": {},
".gitignore": "ignored_a\n.env\n",
"a": {
"banana_env": "11",
"bandana_env": "12",
},
"ignored_a": {
"ignored_banana_env": "21",
"ignored_bandana_env": "22",
"ignored_nested": {
"ignored_nested_banana_env": "31",
"ignored_nested_bandana_env": "32",
},
},
".env": "something",
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
let workspace = window.root(cx);
cx.dispatch_action(window.into(), Toggle);
let finder = cx.read(|cx| workspace.read(cx).modal::<FileFinder>().unwrap());
finder
.update(cx, |finder, cx| {
finder.delegate_mut().update_matches("env".to_string(), cx)
})
.await;
finder.update(cx, |f, _| {
assert_eq!(
collect_search_results(f),
vec![
PathBuf::from(".env"),
PathBuf::from("a/banana_env"),
PathBuf::from("a/bandana_env"),
],
"Root gitignored files and all non-gitignored files should be searched"
)
});
let _ = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(
PathBuf::from("/root/ignored_a/ignored_banana_env"),
true,
cx,
)
})
.await
.unwrap();
cx.foreground().run_until_parked();
finder
.update(cx, |finder, cx| {
finder.delegate_mut().update_matches("env".to_string(), cx)
})
.await;
finder.update(cx, |f, _| {
assert_eq!(
collect_search_results(f),
vec![
PathBuf::from(".env"),
PathBuf::from("a/banana_env"),
PathBuf::from("a/bandana_env"),
PathBuf::from("ignored_a/ignored_banana_env"),
PathBuf::from("ignored_a/ignored_bandana_env"),
],
"Root gitignored dir got listed and its entries got into worktree, but all gitignored dirs below it were not listed. Old entries + new listed gitignored entries should be searched"
)
});
}
#[gpui::test]
@ -2192,4 +2285,20 @@ mod tests {
absolute: None,
}
}
fn collect_search_results(picker: &Picker<FileFinderDelegate>) -> Vec<PathBuf> {
let matches = &picker.delegate().matches;
assert!(
matches.history.is_empty(),
"Should have no history matches, but got: {:?}",
matches.history
);
let mut results = matches
.search
.iter()
.map(|path_match| Path::new(path_match.path_prefix.as_ref()).join(&path_match.path))
.collect::<Vec<_>>();
results.sort();
results
}
}

View File

@ -354,15 +354,10 @@ impl FileFinderDelegate {
let include_root_name = worktrees.len() > 1;
let candidate_sets = worktrees
.into_iter()
.map(|worktree| {
let worktree = worktree.read(cx);
PathMatchCandidateSet {
snapshot: worktree.snapshot(),
include_ignored: worktree
.root_entry()
.map_or(false, |entry| entry.is_ignored),
include_root_name,
}
.map(|worktree| PathMatchCandidateSet {
snapshot: worktree.read(cx).snapshot(),
include_ignored: true,
include_root_name,
})
.collect::<Vec<_>>();
@ -1038,7 +1033,7 @@ mod tests {
}
#[gpui::test]
async fn test_ignored_files(cx: &mut TestAppContext) {
async fn test_ignored_root(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
@ -1081,7 +1076,94 @@ mod tests {
picker.delegate.spawn_search(test_path_like("hi"), cx)
})
.await;
picker.update(cx, |picker, _| assert_eq!(picker.delegate.matches.len(), 7));
picker.update(cx, |picker, _| {
assert_eq!(
collect_search_results(picker),
vec![
PathBuf::from("ignored-root/happiness"),
PathBuf::from("ignored-root/height"),
PathBuf::from("ignored-root/hi"),
PathBuf::from("ignored-root/hiccup"),
PathBuf::from("tracked-root/happiness"),
PathBuf::from("tracked-root/height"),
PathBuf::from("tracked-root/hi"),
PathBuf::from("tracked-root/hiccup"),
],
"All files in all roots (including gitignored) should be searched"
)
});
}
#[gpui::test]
async fn test_ignored_files(cx: &mut TestAppContext) {
let app_state = init_test(cx);
app_state
.fs
.as_fake()
.insert_tree(
"/root",
json!({
".git": {},
".gitignore": "ignored_a\n.env\n",
"a": {
"banana_env": "11",
"bandana_env": "12",
},
"ignored_a": {
"ignored_banana_env": "21",
"ignored_bandana_env": "22",
"ignored_nested": {
"ignored_nested_banana_env": "31",
"ignored_nested_bandana_env": "32",
},
},
".env": "something",
}),
)
.await;
let project = Project::test(app_state.fs.clone(), ["/root".as_ref()], cx).await;
let (picker, workspace, cx) = build_find_picker(project, cx);
cx.simulate_input("env");
picker.update(cx, |picker, _| {
assert_eq!(
collect_search_results(picker),
vec![
PathBuf::from(".env"),
PathBuf::from("a/banana_env"),
PathBuf::from("a/bandana_env"),
],
"Root gitignored files and all non-gitignored files should be searched"
)
});
let _ = workspace
.update(cx, |workspace, cx| {
workspace.open_abs_path(
PathBuf::from("/root/ignored_a/ignored_banana_env"),
true,
cx,
)
})
.await
.unwrap();
cx.run_until_parked();
cx.simulate_input("env");
picker.update(cx, |picker, _| {
assert_eq!(
collect_search_results(picker),
vec![
PathBuf::from(".env"),
PathBuf::from("a/banana_env"),
PathBuf::from("a/bandana_env"),
PathBuf::from("ignored_a/ignored_banana_env"),
PathBuf::from("ignored_a/ignored_bandana_env"),
],
"Root gitignored dir got listed and its entries got into worktree, but all gitignored dirs below it were not listed. Old entries + new listed gitignored entries should be searched"
)
});
}
#[gpui::test]
@ -1846,4 +1928,20 @@ mod tests {
.clone()
})
}
fn collect_search_results(picker: &Picker<FileFinderDelegate>) -> Vec<PathBuf> {
let matches = &picker.delegate.matches;
assert!(
matches.history.is_empty(),
"Should have no history matches, but got: {:?}",
matches.history
);
let mut results = matches
.search
.iter()
.map(|path_match| Path::new(path_match.path_prefix.as_ref()).join(&path_match.path))
.collect::<Vec<_>>();
results.sort();
results
}
}