Import VS Code files.exclude into file_scan_exclusions (#64492)
## Objective Import VS Code’s `files.exclude` into `file_scan_exclusions`. The previous `files.watcherExclude` mapping was dead code: it expected an array, while VS Code stores an object of `pattern: bool`. `files.watcherExclude` only stops watching, and the files stay visible in VS Code, so it is not a match for `file_scan_exclusions`. `files.exclude` hides files from the explorer and search, which matches `file_scan_exclusions`, and its default set is nearly identical to Zed's. Related to #62769, reported during the review of #64484. A meta follow-up to #64399. ## Solution Import nonempty `files.exclude` patterns enabled with `true`, ignoring disabled and malformed entries, including VS Code's `{"when": ...}` form. Append `"..."` so the imported list extends inherited exclusions instead of replacing them. Inputs with no usable patterns leave the inherited list unchanged. `files.watcherExclude` is no longer imported. ## Testing The new regression tests reproduce the bug and verify the fix. To check manually, import `{"files.exclude":{"**/build/**":true,"**/cache/**":false}}` and verify that `file_scan_exclusions` becomes `["**/build/**", "..."]`, and that `.git` stays hidden in the project panel. ## 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: - Fixed importing VS Code `files.exclude` into `file_scan_exclusions`. --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
1 parent
313f63eb71
commit
24f3009720
2 files changed
+103
-31
No files matched your search
@@ -1126,25 +1126,15 @@ impl VsCodeSettings {
|
||||
WorktreeSettingsContent {
|
||||
prevent_sharing_in_public_channels: false,
|
||||
file_scan_depth: None,
|
||||
file_scan_exclusions: self
|
||||
.read_value("files.watcherExclude")
|
||||
.and_then(|v| v.as_array())
|
||||
.map(|v| {
|
||||
v.iter()
|
||||
.filter_map(|n| n.as_str().map(str::to_owned))
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.filter(|r| !r.is_empty())
|
||||
.map(SplicingVec::from),
|
||||
file_scan_exclusions: Self::enabled_patterns(self.read_value("files.exclude")),
|
||||
// `files.watcherInclude` adds watch roots, not Git-ignore overrides
|
||||
file_scan_inclusions: None,
|
||||
scan_symlinks: None,
|
||||
private_files: None,
|
||||
hidden_files: None,
|
||||
// Zed cannot represent the writable exceptions in `files.readonlyExclude`
|
||||
read_only_files: self
|
||||
.read_value("files.readonlyInclude")
|
||||
.filter(|_| {
|
||||
read_only_files: Self::enabled_patterns(
|
||||
self.read_value("files.readonlyInclude").filter(|_| {
|
||||
!self
|
||||
.read_value("files.readonlyExclude")
|
||||
.and_then(Value::as_object)
|
||||
@@ -1153,26 +1143,32 @@ impl VsCodeSettings {
|
||||
.values()
|
||||
.any(|enabled| enabled.as_bool() == Some(true))
|
||||
})
|
||||
})
|
||||
.and_then(Value::as_object)
|
||||
.map(|patterns| {
|
||||
patterns
|
||||
.iter()
|
||||
.filter(|(pattern, enabled)| {
|
||||
!pattern.is_empty()
|
||||
&& pattern.as_str() != SplicingVec::REST
|
||||
&& enabled.as_bool() == Some(true)
|
||||
})
|
||||
.map(|(pattern, _)| pattern.to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.filter(|patterns| !patterns.is_empty())
|
||||
.map(|mut patterns| {
|
||||
patterns.push(SplicingVec::REST.to_owned());
|
||||
SplicingVec::from(patterns)
|
||||
}),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn enabled_patterns(value: Option<&Value>) -> Option<SplicingVec> {
|
||||
value
|
||||
.and_then(Value::as_object)
|
||||
.map(|patterns| {
|
||||
patterns
|
||||
.iter()
|
||||
.filter(|(pattern, enabled)| {
|
||||
// Zed reserves `...` for inheritance, not a literal path
|
||||
!pattern.is_empty()
|
||||
&& pattern.as_str() != SplicingVec::REST
|
||||
&& enabled.as_bool() == Some(true)
|
||||
})
|
||||
.map(|(pattern, _)| pattern.to_owned())
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.filter(|patterns| !patterns.is_empty())
|
||||
.map(|mut patterns| {
|
||||
patterns.push(SplicingVec::REST.to_owned());
|
||||
SplicingVec::from(patterns)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_vscode_window_title_format(template: &str) -> String {
|
||||
@@ -1239,6 +1235,82 @@ mod tests {
|
||||
.reduce_motion
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_file_exclusions() -> Result<()> {
|
||||
let imported = VsCodeSettings::from_str(
|
||||
r#"{
|
||||
"files.exclude": {
|
||||
"": true,
|
||||
"**/array/**": [],
|
||||
"**/build/**": true,
|
||||
"**/cache/**": false,
|
||||
"**/null/**": null,
|
||||
"**/number/**": 1,
|
||||
"**/object/**": {"enabled": true},
|
||||
"**/string/**": "true",
|
||||
"**/target/**": true,
|
||||
"**/*.js": {"when": "$(basename).ts"},
|
||||
"...": true
|
||||
}
|
||||
}"#,
|
||||
VsCodeSettingsSource::VsCode,
|
||||
)?
|
||||
.settings_content();
|
||||
assert_eq!(
|
||||
serde_json::to_value(&imported.project.worktree.file_scan_exclusions)?,
|
||||
serde_json::json!(["**/build/**", "**/target/**", "..."])
|
||||
);
|
||||
|
||||
let mut inherited = WorktreeSettingsContent {
|
||||
file_scan_exclusions: Some(SplicingVec::from(vec!["**/inherited/**".to_string()])),
|
||||
..Default::default()
|
||||
};
|
||||
inherited.merge_from(&imported.project.worktree);
|
||||
assert_eq!(
|
||||
serde_json::to_value(&inherited.file_scan_exclusions)?,
|
||||
serde_json::json!(["**/build/**", "**/target/**", "**/inherited/**"])
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_file_exclusions_without_usable_patterns() -> Result<()> {
|
||||
let inherited = WorktreeSettingsContent {
|
||||
file_scan_exclusions: Some(SplicingVec::from(vec!["**/inherited/**".to_string()])),
|
||||
..Default::default()
|
||||
};
|
||||
for content in [
|
||||
r#"{"files.exclude": "**/cache/**"}"#,
|
||||
r#"{"files.exclude": 1}"#,
|
||||
r#"{"files.exclude": ["**/cache/**"]}"#,
|
||||
r#"{"files.exclude": []}"#,
|
||||
r#"{"files.exclude": null}"#,
|
||||
r#"{"files.exclude": true}"#,
|
||||
r#"{"files.exclude": {"": true}}"#,
|
||||
r#"{"files.exclude": {"**/cache/**": "true"}}"#,
|
||||
r#"{"files.exclude": {"**/cache/**": false}}"#,
|
||||
r#"{"files.exclude": {"**/*.js": {"when": "$(basename).ts"}}}"#,
|
||||
r#"{"files.exclude": {"...": true}}"#,
|
||||
r#"{"files.exclude": {}}"#,
|
||||
r#"{"files.watcherExclude": {"**/cache/**": true}}"#,
|
||||
r#"{}"#,
|
||||
] {
|
||||
let imported =
|
||||
VsCodeSettings::from_str(content, VsCodeSettingsSource::VsCode)?.settings_content();
|
||||
assert_eq!(
|
||||
imported.project.worktree.file_scan_exclusions, None,
|
||||
"{content}"
|
||||
);
|
||||
let mut unchanged = inherited.clone();
|
||||
unchanged.merge_from(&imported.project.worktree);
|
||||
assert_eq!(
|
||||
unchanged.file_scan_exclusions, inherited.file_scan_exclusions,
|
||||
"{content}"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_import_watcher_include_preserves_file_scan_inclusions() -> Result<()> {
|
||||
let inherited = WorktreeSettingsContent {
|
||||
|
||||
@@ -87,10 +87,10 @@ The following VS Code settings are automatically imported when you use **Import
|
||||
| `files.associations` | `file_types` |
|
||||
| `files.autoSave` | `autosave` |
|
||||
| `files.autoSaveDelay` | `autosave.milliseconds` |
|
||||
| `files.exclude` | `file_scan_exclusions` |
|
||||
| `files.insertFinalNewline` | `ensure_final_newline_on_save` |
|
||||
| `files.readonlyInclude` | `read_only_files` |
|
||||
| `files.simpleDialog.enable` | `use_system_path_prompts` |
|
||||
| `files.watcherExclude` | `file_scan_exclusions` |
|
||||
| `search.smartCase` | `use_smartcase_search` |
|
||||
| `search.useIgnoreFiles` | `search.include_ignored` |
|
||||
|
||||
|
||||
Reference in new issue
Block a user