Add file_scan_inclusions
setting to customize Zed file indexing (#16852)
Closes #4745 Release Notes: - Added a new `file_scan_inclusions` setting to force Zed to index files that match the provided globs, even if they're gitignored. --------- Co-authored-by: Mikayla Maki <mikayla@zed.dev>
This commit is contained in:
parent
95ace03706
commit
0e62b6dddd
6 changed files with 350 additions and 25 deletions
|
@ -878,6 +878,211 @@ async fn test_write_file(cx: &mut TestAppContext) {
|
|||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_file_scan_inclusions(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
let dir = temp_tree(json!({
|
||||
".gitignore": "**/target\n/node_modules\ntop_level.txt\n",
|
||||
"target": {
|
||||
"index": "blah2"
|
||||
},
|
||||
"node_modules": {
|
||||
".DS_Store": "",
|
||||
"prettier": {
|
||||
"package.json": "{}",
|
||||
},
|
||||
},
|
||||
"src": {
|
||||
".DS_Store": "",
|
||||
"foo": {
|
||||
"foo.rs": "mod another;\n",
|
||||
"another.rs": "// another",
|
||||
},
|
||||
"bar": {
|
||||
"bar.rs": "// bar",
|
||||
},
|
||||
"lib.rs": "mod foo;\nmod bar;\n",
|
||||
},
|
||||
"top_level.txt": "top level file",
|
||||
".DS_Store": "",
|
||||
}));
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
|
||||
project_settings.file_scan_exclusions = Some(vec![]);
|
||||
project_settings.file_scan_inclusions = Some(vec![
|
||||
"node_modules/**/package.json".to_string(),
|
||||
"**/.DS_Store".to_string(),
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let tree = Worktree::local(
|
||||
dir.path(),
|
||||
true,
|
||||
Arc::new(RealFs::default()),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
tree.read_with(cx, |tree, _| {
|
||||
// Assert that file_scan_inclusions overrides file_scan_exclusions.
|
||||
check_worktree_entries(
|
||||
tree,
|
||||
&[],
|
||||
&["target", "node_modules"],
|
||||
&["src/lib.rs", "src/bar/bar.rs", ".gitignore"],
|
||||
&[
|
||||
"node_modules/prettier/package.json",
|
||||
".DS_Store",
|
||||
"node_modules/.DS_Store",
|
||||
"src/.DS_Store",
|
||||
],
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
let dir = temp_tree(json!({
|
||||
".gitignore": "**/target\n/node_modules\n",
|
||||
"target": {
|
||||
"index": "blah2"
|
||||
},
|
||||
"node_modules": {
|
||||
".DS_Store": "",
|
||||
"prettier": {
|
||||
"package.json": "{}",
|
||||
},
|
||||
},
|
||||
"src": {
|
||||
".DS_Store": "",
|
||||
"foo": {
|
||||
"foo.rs": "mod another;\n",
|
||||
"another.rs": "// another",
|
||||
},
|
||||
},
|
||||
".DS_Store": "",
|
||||
}));
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
|
||||
project_settings.file_scan_exclusions = Some(vec!["**/.DS_Store".to_string()]);
|
||||
project_settings.file_scan_inclusions = Some(vec!["**/.DS_Store".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let tree = Worktree::local(
|
||||
dir.path(),
|
||||
true,
|
||||
Arc::new(RealFs::default()),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
tree.read_with(cx, |tree, _| {
|
||||
// Assert that file_scan_inclusions overrides file_scan_exclusions.
|
||||
check_worktree_entries(
|
||||
tree,
|
||||
&[".DS_Store, src/.DS_Store"],
|
||||
&["target", "node_modules"],
|
||||
&["src/foo/another.rs", "src/foo/foo.rs", ".gitignore"],
|
||||
&[],
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
cx.executor().allow_parking();
|
||||
let dir = temp_tree(json!({
|
||||
".gitignore": "**/target\n/node_modules/\n",
|
||||
"target": {
|
||||
"index": "blah2"
|
||||
},
|
||||
"node_modules": {
|
||||
".DS_Store": "",
|
||||
"prettier": {
|
||||
"package.json": "{}",
|
||||
},
|
||||
},
|
||||
"src": {
|
||||
".DS_Store": "",
|
||||
"foo": {
|
||||
"foo.rs": "mod another;\n",
|
||||
"another.rs": "// another",
|
||||
},
|
||||
},
|
||||
".DS_Store": "",
|
||||
}));
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
|
||||
project_settings.file_scan_exclusions = Some(vec![]);
|
||||
project_settings.file_scan_inclusions = Some(vec!["node_modules/**".to_string()]);
|
||||
});
|
||||
});
|
||||
});
|
||||
let tree = Worktree::local(
|
||||
dir.path(),
|
||||
true,
|
||||
Arc::new(RealFs::default()),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert!(tree
|
||||
.entry_for_path("node_modules")
|
||||
.is_some_and(|f| f.is_always_included));
|
||||
assert!(tree
|
||||
.entry_for_path("node_modules/prettier/package.json")
|
||||
.is_some_and(|f| f.is_always_included));
|
||||
});
|
||||
|
||||
cx.update(|cx| {
|
||||
cx.update_global::<SettingsStore, _>(|store, cx| {
|
||||
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
|
||||
project_settings.file_scan_exclusions = Some(vec![]);
|
||||
project_settings.file_scan_inclusions = Some(vec![]);
|
||||
});
|
||||
});
|
||||
});
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
|
||||
tree.read_with(cx, |tree, _| {
|
||||
assert!(tree
|
||||
.entry_for_path("node_modules")
|
||||
.is_some_and(|f| !f.is_always_included));
|
||||
assert!(tree
|
||||
.entry_for_path("node_modules/prettier/package.json")
|
||||
.is_some_and(|f| !f.is_always_included));
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
@ -939,6 +1144,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
|
|||
],
|
||||
&["target", "node_modules"],
|
||||
&["src/lib.rs", "src/bar/bar.rs", ".gitignore"],
|
||||
&[],
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -970,6 +1176,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
|
|||
"src/.DS_Store",
|
||||
".DS_Store",
|
||||
],
|
||||
&[],
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -1051,6 +1258,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||
"src/bar/bar.rs",
|
||||
".gitignore",
|
||||
],
|
||||
&[],
|
||||
)
|
||||
});
|
||||
|
||||
|
@ -1111,6 +1319,7 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
|
|||
"src/new_file",
|
||||
".gitignore",
|
||||
],
|
||||
&[],
|
||||
)
|
||||
});
|
||||
}
|
||||
|
@ -1140,14 +1349,14 @@ async fn test_fs_events_in_dot_git_worktree(cx: &mut TestAppContext) {
|
|||
.await;
|
||||
tree.flush_fs_events(cx).await;
|
||||
tree.read_with(cx, |tree, _| {
|
||||
check_worktree_entries(tree, &[], &["HEAD", "foo"], &[])
|
||||
check_worktree_entries(tree, &[], &["HEAD", "foo"], &[], &[])
|
||||
});
|
||||
|
||||
std::fs::write(dot_git_worktree_dir.join("new_file"), "new file contents")
|
||||
.unwrap_or_else(|e| panic!("Failed to create in {dot_git_worktree_dir:?} a new file: {e}"));
|
||||
tree.flush_fs_events(cx).await;
|
||||
tree.read_with(cx, |tree, _| {
|
||||
check_worktree_entries(tree, &[], &["HEAD", "foo", "new_file"], &[])
|
||||
check_worktree_entries(tree, &[], &["HEAD", "foo", "new_file"], &[], &[])
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1180,8 +1389,12 @@ async fn test_create_directory_during_initial_scan(cx: &mut TestAppContext) {
|
|||
let snapshot = Arc::new(Mutex::new(tree.snapshot()));
|
||||
tree.observe_updates(0, cx, {
|
||||
let snapshot = snapshot.clone();
|
||||
let settings = tree.settings().clone();
|
||||
move |update| {
|
||||
snapshot.lock().apply_remote_update(update).unwrap();
|
||||
snapshot
|
||||
.lock()
|
||||
.apply_remote_update(update, &settings.file_scan_inclusions)
|
||||
.unwrap();
|
||||
async { true }
|
||||
}
|
||||
});
|
||||
|
@ -1474,12 +1687,14 @@ async fn test_random_worktree_operations_during_initial_scan(
|
|||
snapshot
|
||||
});
|
||||
|
||||
let settings = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().settings());
|
||||
|
||||
for (i, snapshot) in snapshots.into_iter().enumerate().rev() {
|
||||
let mut updated_snapshot = snapshot.clone();
|
||||
for update in updates.lock().iter() {
|
||||
if update.scan_id >= updated_snapshot.scan_id() as u64 {
|
||||
updated_snapshot
|
||||
.apply_remote_update(update.clone())
|
||||
.apply_remote_update(update.clone(), &settings.file_scan_inclusions)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -1610,10 +1825,14 @@ async fn test_random_worktree_changes(cx: &mut TestAppContext, mut rng: StdRng)
|
|||
);
|
||||
}
|
||||
|
||||
let settings = worktree.read_with(cx, |tree, _| tree.as_local().unwrap().settings());
|
||||
|
||||
for (i, mut prev_snapshot) in snapshots.into_iter().enumerate().rev() {
|
||||
for update in updates.lock().iter() {
|
||||
if update.scan_id >= prev_snapshot.scan_id() as u64 {
|
||||
prev_snapshot.apply_remote_update(update.clone()).unwrap();
|
||||
prev_snapshot
|
||||
.apply_remote_update(update.clone(), &settings.file_scan_inclusions)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2588,6 +2807,7 @@ fn check_worktree_entries(
|
|||
expected_excluded_paths: &[&str],
|
||||
expected_ignored_paths: &[&str],
|
||||
expected_tracked_paths: &[&str],
|
||||
expected_included_paths: &[&str],
|
||||
) {
|
||||
for path in expected_excluded_paths {
|
||||
let entry = tree.entry_for_path(path);
|
||||
|
@ -2610,10 +2830,19 @@ fn check_worktree_entries(
|
|||
.entry_for_path(path)
|
||||
.unwrap_or_else(|| panic!("Missing entry for expected tracked path '{path}'"));
|
||||
assert!(
|
||||
!entry.is_ignored,
|
||||
!entry.is_ignored || entry.is_always_included,
|
||||
"expected path '{path}' to be tracked, but got entry: {entry:?}",
|
||||
);
|
||||
}
|
||||
for path in expected_included_paths {
|
||||
let entry = tree
|
||||
.entry_for_path(path)
|
||||
.unwrap_or_else(|| panic!("Missing entry for expected included path '{path}'"));
|
||||
assert!(
|
||||
entry.is_always_included,
|
||||
"expected path '{path}' to always be included, but got entry: {entry:?}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn init_test(cx: &mut gpui::TestAppContext) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue