Add support for the "..." entry in file_scan_exclusions (#62769)

## Objective

`file_scan_exclusions` replaces the defaults instead of adding to them,
so excluding one extra directory means restating all eleven default
globs and never picking up defaults added in later Zed releases.

## Solution

`file_scan_exclusions` now accepts the `"..."` entry, which expands to
the value it overrides, so `["**/node_modules", "..."]` adds to the
inherited globs instead of replacing them. Entries listed by name keep
their position, and leaving `"..."` out still replaces the list
outright, so existing settings behave exactly as they do today.

## Testing

- Four unit tests in `crates/settings_content/src/project.rs` cover
splicing versus replacing, accumulation across successive layers, and
edge cases: a repeated `"..."`, an empty list clearing the value, and a
bare `["..."]` leaving it unchanged.
- To check by hand: set `"file_scan_exclusions": ["**/node_modules",
"..."]` in user settings and confirm `node_modules` disappears from the
project panel and file finder while `.git` and `.DS_Store` stay
excluded. Remove `"..."` and confirm only `node_modules` is excluded.
Repeat in a project's `.zed/settings.json` to confirm it splices the
resolved user settings rather than the defaults.
- Tested on macOS. This is platform-independent settings-merge logic
with no OS-specific code paths, so I did not test Linux or Windows.

## Self-Review Checklist:

- [x] I’ve reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed’s UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

---

Release Notes:

- Added support for the `"..."` entry in `file_scan_exclusions`. Custom
exclusions can now extend the defaults instead of replacing them.
This commit is contained in:
Dom Porada authored and GitHub committed 2026-08-18 09:57:38 +00:00
1 parent 6706fa4651
commit ef973c67c0
14 files changed
+228 -52

No files matched your search

+7 -5
View File
@@ -398,7 +398,7 @@ mod tests {
use gpui::{TestAppContext, UpdateGlobal};
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use settings::{SettingsStore, SplicingVec};
use std::path::PathBuf;
use unindent::Unindent;
use util::path;
@@ -1068,10 +1068,10 @@ mod tests {
use settings::SettingsStore;
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.secretdir".to_string(),
"**/.mymetadata".to_string(),
]);
]));
settings.project.worktree.private_files = Some(
vec![
"**/.mysecrets".to_string(),
@@ -1303,8 +1303,10 @@ mod tests {
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.git".to_string(),
"**/node_modules".to_string(),
]));
settings.project.worktree.private_files =
Some(vec!["**/.env".to_string()].into());
});
@@ -342,7 +342,7 @@ mod tests {
use indoc::indoc;
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use settings::{SettingsStore, SplicingVec};
use std::path::PathBuf;
use util::path;
@@ -578,11 +578,11 @@ mod tests {
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.secretdir".to_string(),
"**/.mymetadata".to_string(),
"**/.hidden_subdir".to_string(),
]);
]));
settings.project.worktree.private_files = Some(
vec![
"**/.mysecrets".to_string(),
@@ -740,8 +740,10 @@ mod tests {
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.git".to_string(),
"**/node_modules".to_string(),
]));
settings.project.worktree.private_files =
Some(vec!["**/.env".to_string()].into());
});
+7 -5
View File
@@ -542,7 +542,7 @@ mod test {
use gpui::{AppContext, TestAppContext, UpdateGlobal as _};
use project::{FakeFs, Project};
use serde_json::json;
use settings::SettingsStore;
use settings::{SettingsStore, SplicingVec};
use std::path::PathBuf;
use std::sync::Arc;
use util::path;
@@ -1051,10 +1051,10 @@ mod test {
use settings::SettingsStore;
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.secretdir".to_string(),
"**/.mymetadata".to_string(),
]);
]));
settings.project.worktree.private_files = Some(
vec![
"**/.mysecrets".to_string(),
@@ -1357,8 +1357,10 @@ mod test {
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.git".to_string(),
"**/node_modules".to_string(),
]));
settings.project.worktree.private_files =
Some(vec!["**/.env".to_string()].into());
});
@@ -15,7 +15,7 @@ use gpui::{
use language::Capability;
use rpc::proto::PeerId;
use serde_json::json;
use settings::SettingsStore;
use settings::{SettingsStore, SplicingVec};
use text::{Point, ToPoint};
use util::{path, rel_path::rel_path, test::sample_text};
use workspace::{
@@ -1762,7 +1762,7 @@ async fn test_following_into_excluded_file(
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string()]);
Some(SplicingVec::from(vec!["**/.git".to_string()]));
});
});
});
@@ -67,7 +67,7 @@ use project::{
};
use rand::{Rng as _, rngs::StdRng};
use serde_json::json;
use settings::{GlobalLspSettingsContent, SettingsStore};
use settings::{GlobalLspSettingsContent, SettingsStore, SplicingVec};
#[cfg(target_os = "linux")]
use settings::{LocalSettingsKind, LocalSettingsPath};
#[cfg(not(windows))]
@@ -13420,7 +13420,8 @@ async fn test_git_events_after_project_excludes_dot_git(cx: &mut gpui::TestAppCo
cx.update(|cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec!["foo".to_string()]);
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(vec!["foo".to_string()]));
});
});
});
@@ -15268,7 +15269,8 @@ async fn test_rescan_with_gitignore(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
});
});
});
+21 -11
View File
@@ -11,7 +11,7 @@ use menu::Cancel;
use pretty_assertions::assert_eq;
use project::{FakeFs, ProjectPath};
use serde_json::json;
use settings::{ProjectPanelAutoOpenSettings, SettingsStore};
use settings::{ProjectPanelAutoOpenSettings, SettingsStore, SplicingVec};
use smallvec::smallvec;
use std::path::{Path, PathBuf};
use util::{path, paths::PathStyle, rel_path::rel_path};
@@ -426,8 +426,10 @@ async fn test_exclusions_in_visible_list(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/4/**".to_string()]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.git".to_string(),
"**/4/**".to_string(),
]));
});
});
});
@@ -5562,7 +5564,8 @@ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
settings
.project_panel
.get_or_insert_default()
@@ -5864,7 +5867,8 @@ async fn test_gitignored_and_always_included(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
settings.project.worktree.file_scan_inclusions =
Some(vec!["always_included_but_ignored_dir/*".to_string()]);
settings
@@ -5988,7 +5992,8 @@ async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
settings
.project_panel
.get_or_insert_default()
@@ -6585,8 +6590,10 @@ async fn test_creating_excluded_entries(cx: &mut gpui::TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"excluded_dir".to_string(),
"**/.git".to_string(),
]));
});
});
});
@@ -11325,7 +11332,8 @@ pub(crate) fn init_test(cx: &mut TestAppContext) {
.project_panel
.get_or_insert_default()
.auto_fold_dirs = Some(false);
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
settings.project.worktree.file_scan_depth = Some(0);
});
});
@@ -11346,7 +11354,8 @@ fn init_test_with_editor(cx: &mut TestAppContext) {
.project_panel
.get_or_insert_default()
.auto_fold_dirs = Some(false);
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
settings.project.worktree.file_scan_depth = Some(0);
});
});
@@ -11368,7 +11377,8 @@ fn init_test_with_git_ui(cx: &mut TestAppContext) {
.project_panel
.get_or_insert_default()
.auto_fold_dirs = Some(false);
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_exclusions =
Some(SplicingVec::from(Vec::new()));
settings.project.worktree.file_scan_depth = Some(0);
});
});
+3 -3
View File
@@ -7,7 +7,7 @@ use fs::{FakeFs, Fs};
use gpui::{App, BorrowAppContext, Context, Entity, VisualTestContext, Window};
use project::Project;
use serde_json::{Value, json};
use settings::SettingsStore;
use settings::{SettingsStore, SplicingVec};
use std::path::Path;
use std::sync::Arc;
use workspace::{Item, MultiWorkspace, register_project_item};
@@ -617,10 +617,10 @@ async fn excluded_create_is_not_recorded(cx: &mut gpui::TestAppContext) {
cx.update_app(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/token.secret".to_string(),
"**/banana.secret".to_string(),
]);
]));
});
});
+2 -1
View File
@@ -1108,7 +1108,8 @@ impl VsCodeSettings {
.filter_map(|n| n.as_str().map(str::to_owned))
.collect::<Vec<_>>()
})
.filter(|r| !r.is_empty()),
.filter(|r| !r.is_empty())
.map(SplicingVec::from),
file_scan_inclusions: self
.read_value("files.watcherInclude")
.and_then(|v| v.as_array())
+96 -2
View File
@@ -14,7 +14,7 @@ use util::serde::default_true;
use crate::{
AllLanguageSettingsContent, DelayMs, ExtendingVec, ParseStatus, ProjectTerminalSettingsContent,
RootUserSettings, SaturatingBool, fallible_options,
RootUserSettings, SaturatingBool, SplicingVec, fallible_options,
};
#[with_fallible_options]
@@ -123,6 +123,10 @@ pub struct WorktreeSettingsContent {
/// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
/// `file_scan_inclusions`.
///
/// A `"..."` entry expands to the value being overridden, so
/// `["**/node_modules", "..."]` adds to the inherited globs instead of
/// replacing them. Leave `"..."` out to replace them.
///
/// Default: [
/// "**/.git",
/// "**/.svn",
@@ -134,7 +138,7 @@ pub struct WorktreeSettingsContent {
/// "**/.classpath",
/// "**/.settings"
/// ]
pub file_scan_exclusions: Option<Vec<String>>,
pub file_scan_exclusions: Option<SplicingVec>,
/// Always include files that match these globs when scanning for files, even if they're
/// ignored by git. This setting is overridden by `file_scan_exclusions`.
@@ -936,6 +940,96 @@ pub enum GitHostingProviderKind {
#[cfg(test)]
mod tests {
use super::*;
use crate::{REST_OF_FILE_SCAN_EXCLUSIONS, merge_from::MergeFrom};
fn exclusions(globs: &[&str]) -> WorktreeSettingsContent {
WorktreeSettingsContent {
file_scan_exclusions: Some(SplicingVec::from(
globs
.iter()
.map(|glob| glob.to_string())
.collect::<Vec<_>>(),
)),
..Default::default()
}
}
#[test]
fn test_file_scan_exclusions_splice_rest_of_list() {
let defaults = exclusions(&["**/.git", "**/.DS_Store"]);
let mut extended = defaults.clone();
extended.merge_from(&exclusions(&[
"**/node_modules",
REST_OF_FILE_SCAN_EXCLUSIONS,
]));
assert_eq!(
extended.file_scan_exclusions.unwrap().0,
vec!["**/node_modules", "**/.git", "**/.DS_Store"]
);
let mut replaced = defaults;
replaced.merge_from(&exclusions(&["**/node_modules"]));
assert_eq!(
replaced.file_scan_exclusions.unwrap().0,
vec!["**/node_modules"]
);
}
#[test]
fn test_file_scan_exclusions_splice_each_layer() {
let mut settings = exclusions(&["**/.git"]);
settings.merge_from(&exclusions(&[REST_OF_FILE_SCAN_EXCLUSIONS, "**/target"]));
settings.merge_from(&exclusions(&[REST_OF_FILE_SCAN_EXCLUSIONS, "**/dist"]));
assert_eq!(
settings.file_scan_exclusions.unwrap().0,
vec!["**/.git", "**/target", "**/dist"]
);
}
#[test]
fn test_file_scan_exclusions_splice_edge_cases() {
let mut repeated = exclusions(&["**/.git"]);
repeated.merge_from(&exclusions(&[
REST_OF_FILE_SCAN_EXCLUSIONS,
REST_OF_FILE_SCAN_EXCLUSIONS,
]));
assert_eq!(repeated.file_scan_exclusions.unwrap().0, vec!["**/.git"]);
let mut relisted = exclusions(&["**/.git", "**/.DS_Store"]);
relisted.merge_from(&exclusions(&["**/.git", REST_OF_FILE_SCAN_EXCLUSIONS]));
assert_eq!(
relisted.file_scan_exclusions.unwrap().0,
vec!["**/.git", "**/.DS_Store"]
);
let mut cleared = exclusions(&["**/.git"]);
cleared.merge_from(&exclusions(&[]));
assert!(cleared.file_scan_exclusions.unwrap().0.is_empty());
let mut unchanged = exclusions(&["**/.git", "**/.DS_Store"]);
unchanged.merge_from(&exclusions(&[REST_OF_FILE_SCAN_EXCLUSIONS]));
assert_eq!(
unchanged.file_scan_exclusions.unwrap().0,
vec!["**/.git", "**/.DS_Store"]
);
}
#[test]
fn test_file_scan_exclusions_splice_without_a_base_layer() {
let mut settings = WorktreeSettingsContent::default();
settings.merge_from(&exclusions(&[REST_OF_FILE_SCAN_EXCLUSIONS, "**/target"]));
// `Option::merge_from` replaces a `None` base outright rather than
// splicing, so the sentinel survives here. `assets/settings/default.json`
// always populates this field, and `WorktreeSettings::from_settings`
// unwraps it, so no glob is ever compiled from this state.
assert_eq!(
settings.file_scan_exclusions.unwrap().0,
vec![REST_OF_FILE_SCAN_EXCLUSIONS, "**/target"]
);
}
#[test]
fn test_stdio_context_server_without_args() {
@@ -1399,6 +1399,46 @@ impl<T: Clone> merge_from::MergeFrom for ExtendingVec<T> {
}
}
pub const REST_OF_FILE_SCAN_EXCLUSIONS: &str = "...";
// A SplicingVec in the settings replaces the value it merges over, except that
// a `...` entry expands to that previous value.
//
// This lets a settings file add to a list without restating what it inherits,
// while omitting `...` still replaces the list outright. Unlike ExtendingVec,
// entries can be dropped by leaving `...` out and listing what to keep.
//
// Entries collapse to their first occurrence, so naming a value that `...`
// already covers keeps it at the position it was written in rather than
// repeating it.
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct SplicingVec(pub Vec<String>);
impl From<Vec<String>> for SplicingVec {
fn from(vec: Vec<String>) -> Self {
SplicingVec(vec)
}
}
impl merge_from::MergeFrom for SplicingVec {
fn merge_from(&mut self, other: &Self) {
let inherited = std::mem::take(&mut self.0);
self.0 = other
.0
.iter()
.flat_map(|entry| {
if entry == REST_OF_FILE_SCAN_EXCLUSIONS {
inherited.clone()
} else {
vec![entry.clone()]
}
})
.collect::<IndexSet<_>>()
.into_iter()
.collect();
}
}
// An ExtendingSet in the settings can only accumulate new values, and ignores
// values that are already present, so merging the same source more than once
// (e.g. re-importing VS Code settings) is idempotent.
+1 -1
View File
@@ -60,7 +60,7 @@ impl WorktreeSettings {
impl Settings for WorktreeSettings {
fn from_settings(content: &settings::SettingsContent) -> Self {
let worktree = content.project.worktree.clone();
let file_scan_exclusions = worktree.file_scan_exclusions.unwrap();
let file_scan_exclusions = worktree.file_scan_exclusions.unwrap().0;
let file_scan_inclusions = worktree.file_scan_inclusions.unwrap();
let private_files = worktree.private_files.unwrap().0;
let hidden_files = worktree.hidden_files.unwrap();
@@ -15,7 +15,7 @@ use rpc::{AnyProtoClient, NoopProtoClient, proto};
use worktree::{Entry, EntryKind, Event, PathChange, Worktree, WorktreeModelHandle};
use serde_json::json;
use settings::{LocalSettingsKind, LocalSettingsPath, SettingsStore, WorktreeId};
use settings::{LocalSettingsKind, LocalSettingsPath, SettingsStore, SplicingVec, WorktreeId};
use std::{
cell::Cell,
env,
@@ -2070,7 +2070,7 @@ async fn test_file_scan_inclusions(cx: &mut TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![]));
settings.project.worktree.file_scan_inclusions = Some(vec![
"node_modules/**/package.json".to_string(),
"**/.DS_Store".to_string(),
@@ -2141,7 +2141,7 @@ async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext)
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.DS_Store".to_string()]);
Some(SplicingVec::from(vec!["**/.DS_Store".to_string()]));
settings.project.worktree.file_scan_inclusions =
Some(vec!["**/.DS_Store".to_string()]);
});
@@ -2204,7 +2204,7 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![]));
settings.project.worktree.file_scan_inclusions =
Some(vec!["node_modules/**".to_string()]);
});
@@ -2239,7 +2239,7 @@ async fn test_file_scan_inclusions_reindexes_on_setting_change(cx: &mut TestAppC
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![]));
settings.project.worktree.file_scan_inclusions = Some(vec![]);
});
});
@@ -2291,8 +2291,10 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/foo/**".to_string(),
"**/.DS_Store".to_string(),
]));
});
});
});
@@ -2333,7 +2335,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/node_modules/**".to_string()]);
Some(SplicingVec::from(vec!["**/node_modules/**".to_string()]));
});
});
});
@@ -2487,11 +2489,11 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![
settings.project.worktree.file_scan_exclusions = Some(SplicingVec::from(vec![
"**/.git".to_string(),
"node_modules/".to_string(),
"build_output".to_string(),
]);
]));
});
});
});
+6 -3
View File
@@ -2938,7 +2938,7 @@ mod tests {
use remote_server::{HeadlessAppState, HeadlessProject};
use semver::Version;
use serde_json::json;
use settings::{SaturatingBool, SettingsStore, watch_config_file};
use settings::{SaturatingBool, SettingsStore, SplicingVec, watch_config_file};
use std::{
path::{Path, PathBuf},
sync::Arc,
@@ -4283,7 +4283,10 @@ mod tests {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |project_settings| {
project_settings.project.worktree.file_scan_exclusions =
Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
Some(SplicingVec::from(vec![
"excluded_dir".to_string(),
"**/.git".to_string(),
]));
});
});
});
@@ -6319,7 +6322,7 @@ mod tests {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings(cx, |worktree_settings| {
worktree_settings.project.worktree.file_scan_exclusions =
Some(vec![".zed".to_string()]);
Some(SplicingVec::from(vec![".zed".to_string()]));
});
});
+19 -1
View File
@@ -2152,6 +2152,8 @@ The result is still `)))` and not `))))))`, which is what it would be by default
"**/.svn",
"**/.hg",
"**/.jj",
"**/.sl",
"**/.repo",
"**/CVS",
"**/.DS_Store",
"**/Thumbs.db",
@@ -2161,7 +2163,23 @@ The result is still `)))` and not `))))))`, which is what it would be by default
}
```
Note, specifying `file_scan_exclusions` in settings.json will override the defaults (shown above). If you are looking to exclude additional items you will need to include all the default values in your settings.
Note that specifying `file_scan_exclusions` in `settings.json` will override the defaults (listed above). Use `"..."` to keep them:
```json [settings]
{
"file_scan_exclusions": ["**/node_modules", "..."]
}
```
The `"..."` entry expands to the list you are overriding, so the example above excludes `node_modules` in addition to every default. Entries you list by name keep their position, and `"..."` fills in the inherited ones at that point in the list. If you want full control over what is excluded, omit `"..."` — only the entries you list by name will be used.
| Configuration | Result |
| ---------------------------- | ------------------------------------ |
| `["..."]` | The defaults, unchanged |
| `["**/node_modules", "..."]` | `**/node_modules`, then the defaults |
| `["**/node_modules"]` | `**/node_modules` only |
> Note: `"..."` resolves one settings layer at a time. In a project’s `.zed/settings.json` it expands to whatever your user settings resolved to, rather than to Zed’s defaults.
## File Scan Inclusions