Settings refactor (#38367)

Co-Authored-By: Ben K <ben@zed.dev>
Co-Authored-By: Anthony <anthony@zed.dev>
Co-Authored-By: Mikayla <mikayla@zed.dev>

Release Notes:

- settings: Major internal changes to settings. The primary user-facing
effect is that some settings which did not make sense in project
settings files are no-longer read from there. (For example the inline
blame settings)

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>
Co-authored-by: Anthony <anthony@zed.dev>
This commit is contained in:
Conrad Irwin
2025-09-18 16:47:23 +00:00
committed by GitHub
co-authored by Ben Kunkle Mikayla Maki Anthony
parent 0a9023bce0
commit fcdab160f9
219 changed files with 11697 additions and 11894 deletions
+66 -70
View File
@@ -2,10 +2,8 @@ use std::path::Path;
use anyhow::Context as _;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use util::paths::PathMatcher;
use settings::{Settings, SettingsContent};
use util::{ResultExt, paths::PathMatcher};
#[derive(Clone, PartialEq, Eq)]
pub struct WorktreeSettings {
@@ -32,57 +30,13 @@ impl WorktreeSettings {
}
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct WorktreeSettingsContent {
/// The displayed name of this project. If not set, the root directory name
/// will be displayed.
///
/// Default: none
#[serde(default)]
pub project_name: Option<String>,
/// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
/// `file_scan_inclusions`.
///
/// Default: [
/// "**/.git",
/// "**/.svn",
/// "**/.hg",
/// "**/.jj",
/// "**/CVS",
/// "**/.DS_Store",
/// "**/Thumbs.db",
/// "**/.classpath",
/// "**/.settings"
/// ]
#[serde(default)]
pub file_scan_exclusions: Option<Vec<String>>,
/// 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`.
/// Default: [
/// ".env*",
/// "docker-compose.*.yml",
/// ]
#[serde(default)]
pub file_scan_inclusions: Option<Vec<String>>,
/// Treat the files matching these globs as `.env` files.
/// Default: [ "**/.env*" ]
pub private_files: Option<Vec<String>>,
}
impl Settings for WorktreeSettings {
type FileContent = WorktreeSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
let result: WorktreeSettingsContent = sources.json_merge()?;
let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
let mut private_files = result.private_files.unwrap_or_default();
let mut parsed_file_scan_inclusions: Vec<String> = result
.file_scan_inclusions
.unwrap_or_default()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self {
let worktree = content.project.worktree.clone();
let file_scan_exclusions = worktree.file_scan_exclusions.unwrap();
let file_scan_inclusions = worktree.file_scan_inclusions.unwrap();
let private_files = worktree.private_files.unwrap();
let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
.iter()
.flat_map(|glob| {
Path::new(glob)
@@ -91,30 +45,71 @@ impl Settings for WorktreeSettings {
})
.filter(|p: &String| !p.is_empty())
.collect();
file_scan_exclusions.sort();
private_files.sort();
parsed_file_scan_inclusions.sort();
Ok(Self {
file_scan_exclusions: path_matchers(&file_scan_exclusions, "file_scan_exclusions")?,
private_files: path_matchers(&private_files, "private_files")?,
Self {
project_name: None,
file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions")
.unwrap(),
file_scan_inclusions: path_matchers(
&parsed_file_scan_inclusions,
parsed_file_scan_inclusions,
"file_scan_inclusions",
)?,
project_name: result.project_name,
})
)
.unwrap(),
private_files: path_matchers(private_files, "private_files").unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
fn refine(&mut self, content: &SettingsContent, _cx: &mut App) {
let worktree = &content.project.worktree;
if let Some(project_name) = worktree.project_name.clone() {
self.project_name = Some(project_name);
}
if let Some(mut private_files) = worktree.private_files.clone() {
let sources = self.private_files.sources();
private_files.extend_from_slice(sources);
if let Some(matchers) = path_matchers(private_files, "private_files").log_err() {
self.private_files = matchers;
}
}
if let Some(file_scan_exclusions) = worktree.file_scan_exclusions.clone() {
if let Some(matchers) =
path_matchers(file_scan_exclusions, "file_scan_exclusions").log_err()
{
self.file_scan_exclusions = matchers
}
}
if let Some(file_scan_inclusions) = worktree.file_scan_inclusions.clone() {
let parsed_file_scan_inclusions: Vec<String> = file_scan_inclusions
.iter()
.flat_map(|glob| {
Path::new(glob)
.ancestors()
.map(|a| a.to_string_lossy().into())
})
.filter(|p: &String| !p.is_empty())
.collect();
if let Some(matchers) =
path_matchers(parsed_file_scan_inclusions, "file_scan_inclusions").log_err()
{
self.file_scan_inclusions = matchers
}
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(inclusions) = vscode
.read_value("files.watcherInclude")
.and_then(|v| v.as_array())
.and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
{
if let Some(old) = current.file_scan_inclusions.as_mut() {
if let Some(old) = current.project.worktree.file_scan_inclusions.as_mut() {
old.extend(inclusions)
} else {
current.file_scan_inclusions = Some(inclusions)
current.project.worktree.file_scan_inclusions = Some(inclusions)
}
}
if let Some(exclusions) = vscode
@@ -122,15 +117,16 @@ impl Settings for WorktreeSettings {
.and_then(|v| v.as_array())
.and_then(|v| v.iter().map(|n| n.as_str().map(str::to_owned)).collect())
{
if let Some(old) = current.file_scan_exclusions.as_mut() {
if let Some(old) = current.project.worktree.file_scan_exclusions.as_mut() {
old.extend(exclusions)
} else {
current.file_scan_exclusions = Some(exclusions)
current.project.worktree.file_scan_exclusions = Some(exclusions)
}
}
}
}
fn path_matchers(values: &[String], context: &'static str) -> anyhow::Result<PathMatcher> {
fn path_matchers(mut values: Vec<String>, context: &'static str) -> anyhow::Result<PathMatcher> {
values.sort();
PathMatcher::new(values).with_context(|| format!("Failed to parse globs from {}", context))
}
+21 -18
View File
@@ -770,9 +770,9 @@ async fn test_file_scan_inclusions(cx: &mut TestAppContext) {
}));
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![
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![]);
settings.project.worktree.file_scan_inclusions = Some(vec![
"node_modules/**/package.json".to_string(),
"**/.DS_Store".to_string(),
]);
@@ -836,9 +836,11 @@ async fn test_file_scan_exclusions_overrules_inclusions(cx: &mut TestAppContext)
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()]);
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.DS_Store".to_string()]);
settings.project.worktree.file_scan_inclusions =
Some(vec!["**/.DS_Store".to_string()]);
});
});
});
@@ -894,9 +896,10 @@ 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::<WorktreeSettings>(cx, |project_settings| {
project_settings.file_scan_exclusions = Some(vec![]);
project_settings.file_scan_inclusions = Some(vec!["node_modules/**".to_string()]);
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![]);
settings.project.worktree.file_scan_inclusions =
Some(vec!["node_modules/**".to_string()]);
});
});
});
@@ -926,9 +929,9 @@ 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::<WorktreeSettings>(cx, |project_settings| {
project_settings.file_scan_exclusions = Some(vec![]);
project_settings.file_scan_inclusions = Some(vec![]);
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![]);
settings.project.worktree.file_scan_inclusions = Some(vec![]);
});
});
});
@@ -978,8 +981,8 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
}));
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
project_settings.file_scan_exclusions =
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
});
});
@@ -1015,8 +1018,8 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
project_settings.file_scan_exclusions =
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/node_modules/**".to_string()]);
});
});
@@ -1080,8 +1083,8 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
}));
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
project_settings.file_scan_exclusions = Some(vec![
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(vec![
"**/.git".to_string(),
"node_modules/".to_string(),
"build_output".to_string(),