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
+24 -21
View File
@@ -36,12 +36,13 @@ use project::{
project_settings::GoToDiagnosticSeverityFilter,
relativize_path,
};
use project_panel_settings::{
ProjectPanelDockPosition, ProjectPanelSettings, ShowDiagnostics, ShowIndentGuides,
};
use project_panel_settings::ProjectPanelSettings;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore, update_settings_file};
use settings::{
DockSide, ProjectPanelEntrySpacing, Settings, SettingsStore, ShowDiagnostics, ShowIndentGuides,
update_settings_file,
};
use smallvec::SmallVec;
use std::{any::TypeId, time::Instant};
use std::{
@@ -343,8 +344,14 @@ pub fn init(cx: &mut App) {
workspace.register_action(|workspace, _: &ToggleHideGitIgnore, _, cx| {
let fs = workspace.app_state().fs.clone();
update_settings_file::<ProjectPanelSettings>(fs, cx, move |setting, _| {
setting.hide_gitignore = Some(!setting.hide_gitignore.unwrap_or(false));
update_settings_file(fs, cx, move |setting, _| {
setting.project_panel.get_or_insert_default().hide_gitignore = Some(
!setting
.project_panel
.get_or_insert_default()
.hide_gitignore
.unwrap_or(false),
);
})
});
@@ -4481,8 +4488,8 @@ impl ProjectPanel {
.indent_level(depth)
.indent_step_size(px(settings.indent_size))
.spacing(match settings.entry_spacing {
project_panel_settings::EntrySpacing::Comfortable => ListItemSpacing::Dense,
project_panel_settings::EntrySpacing::Standard => {
ProjectPanelEntrySpacing::Comfortable => ListItemSpacing::Dense,
ProjectPanelEntrySpacing::Standard => {
ListItemSpacing::ExtraDense
}
})
@@ -5760,8 +5767,8 @@ impl EventEmitter<PanelEvent> for ProjectPanel {}
impl Panel for ProjectPanel {
fn position(&self, _: &Window, cx: &App) -> DockPosition {
match ProjectPanelSettings::get_global(cx).dock {
ProjectPanelDockPosition::Left => DockPosition::Left,
ProjectPanelDockPosition::Right => DockPosition::Right,
DockSide::Left => DockPosition::Left,
DockSide::Right => DockPosition::Right,
}
}
@@ -5770,17 +5777,13 @@ impl Panel for ProjectPanel {
}
fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
settings::update_settings_file::<ProjectPanelSettings>(
self.fs.clone(),
cx,
move |settings, _| {
let dock = match position {
DockPosition::Left | DockPosition::Bottom => ProjectPanelDockPosition::Left,
DockPosition::Right => ProjectPanelDockPosition::Right,
};
settings.dock = Some(dock);
},
);
settings::update_settings_file(self.fs.clone(), cx, move |settings, _| {
let dock = match position {
DockPosition::Left | DockPosition::Bottom => DockSide::Left,
DockPosition::Right => DockSide::Right,
};
settings.project_panel.get_or_insert_default().dock = Some(dock);
});
}
fn size(&self, _: &Window, cx: &App) -> Pixels {
+106 -148
View File
@@ -2,40 +2,23 @@ use editor::EditorSettings;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum ProjectPanelDockPosition {
Left,
Right,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ShowIndentGuides {
Always,
Never,
}
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum EntrySpacing {
/// Comfortable spacing of entries.
#[default]
Comfortable,
/// The standard spacing of entries.
Standard,
}
use settings::{
DockSide, ProjectPanelEntrySpacing, Settings, SettingsContent, ShowDiagnostics,
ShowIndentGuides,
};
use ui::{
px,
scrollbars::{ScrollbarVisibility, ShowScrollbar},
};
use util::MergeFrom;
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
pub struct ProjectPanelSettings {
pub button: bool,
pub hide_gitignore: bool,
pub default_width: Pixels,
pub dock: ProjectPanelDockPosition,
pub entry_spacing: EntrySpacing,
pub dock: DockSide,
pub entry_spacing: ProjectPanelEntrySpacing,
pub file_icons: bool,
pub folder_icons: bool,
pub git_status: bool,
@@ -56,12 +39,6 @@ pub struct IndentGuidesSettings {
pub show: ShowIndentGuides,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct IndentGuidesSettingsContent {
/// When to show the scrollbar in the project panel.
pub show: Option<ShowIndentGuides>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarSettings {
/// When to show the scrollbar in the project panel.
@@ -70,105 +47,6 @@ pub struct ScrollbarSettings {
pub show: Option<ShowScrollbar>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
pub struct ScrollbarSettingsContent {
/// When to show the scrollbar in the project panel.
///
/// Default: inherits editor scrollbar settings
pub show: Option<Option<ShowScrollbar>>,
}
/// Whether to indicate diagnostic errors and/or warnings in project panel items.
///
/// Default: all
#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum ShowDiagnostics {
/// Never mark the diagnostic errors/warnings in the project panel.
Off,
/// Mark files containing only diagnostic errors in the project panel.
Errors,
#[default]
/// Mark files containing diagnostic errors or warnings in the project panel.
All,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "project_panel")]
pub struct ProjectPanelSettingsContent {
/// Whether to show the project panel button in the status bar.
///
/// Default: true
pub button: Option<bool>,
/// Whether to hide gitignore files in the project panel.
///
/// Default: false
pub hide_gitignore: Option<bool>,
/// Customize default width (in pixels) taken by project panel
///
/// Default: 240
pub default_width: Option<f32>,
/// The position of project panel
///
/// Default: left
pub dock: Option<ProjectPanelDockPosition>,
/// Spacing between worktree entries in the project panel.
///
/// Default: comfortable
pub entry_spacing: Option<EntrySpacing>,
/// Whether to show file icons in the project panel.
///
/// Default: true
pub file_icons: Option<bool>,
/// Whether to show folder icons or chevrons for directories in the project panel.
///
/// Default: true
pub folder_icons: Option<bool>,
/// Whether to show the git status in the project panel.
///
/// Default: true
pub git_status: Option<bool>,
/// Amount of indentation (in pixels) for nested items.
///
/// Default: 20
pub indent_size: Option<f32>,
/// Whether to reveal it in the project panel automatically,
/// when a corresponding project entry becomes active.
/// Gitignored entries are never auto revealed.
///
/// Default: true
pub auto_reveal_entries: Option<bool>,
/// Whether to fold directories automatically
/// when directory has only one directory inside.
///
/// Default: true
pub auto_fold_dirs: Option<bool>,
/// Whether the project panel should open on startup.
///
/// Default: true
pub starts_open: Option<bool>,
/// Scrollbar-related settings
pub scrollbar: Option<ScrollbarSettingsContent>,
/// Which files containing diagnostic errors/warnings to mark in the project panel.
///
/// Default: all
pub show_diagnostics: Option<ShowDiagnostics>,
/// Settings related to indent guides in the project panel.
pub indent_guides: Option<IndentGuidesSettingsContent>,
/// Whether to hide the root entry when only one folder is open in the window.
///
/// Default: false
pub hide_root: Option<bool>,
/// Whether to stick parent directories at top of the project panel.
///
/// Default: true
pub sticky_scroll: Option<bool>,
/// Whether to enable drag-and-drop operations in the project panel.
///
/// Default: true
pub drag_and_drop: Option<bool>,
}
impl ScrollbarVisibility for ProjectPanelSettings {
fn visibility(&self, cx: &ui::App) -> ShowScrollbar {
self.scrollbar
@@ -178,32 +56,112 @@ impl ScrollbarVisibility for ProjectPanelSettings {
}
impl Settings for ProjectPanelSettings {
type FileContent = ProjectPanelSettingsContent;
fn load(
sources: SettingsSources<Self::FileContent>,
_: &mut gpui::App,
) -> anyhow::Result<Self> {
sources.json_merge()
fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self {
let project_panel = content.project_panel.clone().unwrap();
Self {
button: project_panel.button.unwrap(),
hide_gitignore: project_panel.hide_gitignore.unwrap(),
default_width: px(project_panel.default_width.unwrap()),
dock: project_panel.dock.unwrap(),
entry_spacing: project_panel.entry_spacing.unwrap(),
file_icons: project_panel.file_icons.unwrap(),
folder_icons: project_panel.folder_icons.unwrap(),
git_status: project_panel.git_status.unwrap(),
indent_size: project_panel.indent_size.unwrap(),
indent_guides: IndentGuidesSettings {
show: project_panel.indent_guides.unwrap().show.unwrap(),
},
sticky_scroll: project_panel.sticky_scroll.unwrap(),
auto_reveal_entries: project_panel.auto_reveal_entries.unwrap(),
auto_fold_dirs: project_panel.auto_fold_dirs.unwrap(),
starts_open: project_panel.starts_open.unwrap(),
scrollbar: ScrollbarSettings {
show: project_panel
.scrollbar
.unwrap()
.show
.flatten()
.map(Into::into),
},
show_diagnostics: project_panel.show_diagnostics.unwrap(),
hide_root: project_panel.hide_root.unwrap(),
drag_and_drop: project_panel.drag_and_drop.unwrap(),
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
vscode.bool_setting("explorer.excludeGitIgnore", &mut current.hide_gitignore);
vscode.bool_setting("explorer.autoReveal", &mut current.auto_reveal_entries);
vscode.bool_setting("explorer.compactFolders", &mut current.auto_fold_dirs);
fn refine(&mut self, content: &SettingsContent, _cx: &mut ui::App) {
let Some(project_panel) = content.project_panel.as_ref() else {
return;
};
self.button.merge_from(&project_panel.button);
self.hide_gitignore
.merge_from(&project_panel.hide_gitignore);
self.default_width
.merge_from(&project_panel.default_width.map(px));
self.dock.merge_from(&project_panel.dock);
self.entry_spacing.merge_from(&project_panel.entry_spacing);
self.file_icons.merge_from(&project_panel.file_icons);
self.folder_icons.merge_from(&project_panel.folder_icons);
self.git_status.merge_from(&project_panel.git_status);
self.indent_size.merge_from(&project_panel.indent_size);
self.sticky_scroll.merge_from(&project_panel.sticky_scroll);
self.auto_reveal_entries
.merge_from(&project_panel.auto_reveal_entries);
self.auto_fold_dirs
.merge_from(&project_panel.auto_fold_dirs);
self.starts_open.merge_from(&project_panel.starts_open);
self.show_diagnostics
.merge_from(&project_panel.show_diagnostics);
self.hide_root.merge_from(&project_panel.hide_root);
self.drag_and_drop.merge_from(&project_panel.drag_and_drop);
if let Some(show) = project_panel
.indent_guides
.as_ref()
.and_then(|indent| indent.show)
{
self.indent_guides.show = show;
}
if let Some(show) = project_panel
.scrollbar
.as_ref()
.and_then(|scrollbar| scrollbar.show)
{
self.scrollbar.show = show.map(Into::into)
}
}
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
if let Some(hide_gitignore) = vscode.read_bool("explorer.excludeGitIgnore") {
current.project_panel.get_or_insert_default().hide_gitignore = Some(hide_gitignore);
}
if let Some(auto_reveal) = vscode.read_bool("explorer.autoReveal") {
current
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(auto_reveal);
}
if let Some(compact_folders) = vscode.read_bool("explorer.compactFolders") {
current.project_panel.get_or_insert_default().auto_fold_dirs = Some(compact_folders);
}
if Some(false) == vscode.read_bool("git.decorations.enabled") {
current.git_status = Some(false);
current.project_panel.get_or_insert_default().git_status = Some(false);
}
if Some(false) == vscode.read_bool("problems.decorations.enabled") {
current.show_diagnostics = Some(ShowDiagnostics::Off);
current
.project_panel
.get_or_insert_default()
.show_diagnostics = Some(ShowDiagnostics::Off);
}
if let (Some(false), Some(false)) = (
vscode.read_bool("explorer.decorations.badges"),
vscode.read_bool("explorer.decorations.colors"),
) {
current.git_status = Some(false);
current.show_diagnostics = Some(ShowDiagnostics::Off);
current.project_panel.get_or_insert_default().git_status = Some(false);
current
.project_panel
.get_or_insert_default()
.show_diagnostics = Some(ShowDiagnostics::Off);
}
}
}
+46 -35
View File
@@ -2,7 +2,7 @@ use super::*;
use collections::HashSet;
use gpui::{Empty, Entity, TestAppContext, VisualTestContext, WindowHandle};
use pretty_assertions::assert_eq;
use project::{FakeFs, WorktreeSettings};
use project::FakeFs;
use serde_json::json;
use settings::SettingsStore;
use std::path::{Path, PathBuf};
@@ -161,8 +161,8 @@ async fn test_exclusions_in_visible_list(cx: &mut gpui::TestAppContext) {
init_test(cx);
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
worktree_settings.file_scan_exclusions =
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions =
Some(vec!["**/.git".to_string(), "**/4/**".to_string()]);
});
});
@@ -3448,11 +3448,12 @@ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) {
init_test_with_editor(cx);
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
worktree_settings.file_scan_exclusions = Some(Vec::new());
});
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_reveal_entries = Some(false)
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(false);
});
})
});
@@ -3570,8 +3571,11 @@ async fn test_autoreveal_and_gitignored_files(cx: &mut gpui::TestAppContext) {
cx.update(|_, cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_reveal_entries = Some(true)
store.update_user_settings(cx, |settings| {
settings
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(true)
});
})
});
@@ -3684,13 +3688,14 @@ async fn test_gitignored_and_always_included(cx: &mut gpui::TestAppContext) {
init_test_with_editor(cx);
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
worktree_settings.file_scan_exclusions = Some(Vec::new());
worktree_settings.file_scan_inclusions =
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings.project.worktree.file_scan_inclusions =
Some(vec!["always_included_but_ignored_dir/*".to_string()]);
});
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_reveal_entries = Some(false)
settings
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(false)
});
})
});
@@ -3759,8 +3764,11 @@ async fn test_gitignored_and_always_included(cx: &mut gpui::TestAppContext) {
cx.run_until_parked();
cx.update(|_, cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_reveal_entries = Some(true)
store.update_user_settings(cx, |settings| {
settings
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(true)
});
})
});
@@ -3800,11 +3808,12 @@ async fn test_explicit_reveal(cx: &mut gpui::TestAppContext) {
init_test_with_editor(cx);
cx.update(|cx| {
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
worktree_settings.file_scan_exclusions = Some(Vec::new());
});
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_reveal_entries = Some(false)
store.update_user_settings(cx, |settings| {
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
settings
.project_panel
.get_or_insert_default()
.auto_reveal_entries = Some(false)
});
})
});
@@ -4001,8 +4010,8 @@ async fn test_creating_excluded_entries(cx: &mut gpui::TestAppContext) {
init_test(cx);
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!["excluded_dir".to_string(), "**/.git".to_string()]);
});
});
@@ -6650,11 +6659,12 @@ fn init_test(cx: &mut TestAppContext) {
Project::init_settings(cx);
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_fold_dirs = Some(false);
});
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
worktree_settings.file_scan_exclusions = Some(Vec::new());
store.update_user_settings(cx, |settings| {
settings
.project_panel
.get_or_insert_default()
.auto_fold_dirs = Some(false);
settings.project.worktree.file_scan_exclusions = Some(Vec::new());
});
});
});
@@ -6672,11 +6682,12 @@ fn init_test_with_editor(cx: &mut TestAppContext) {
Project::init_settings(cx);
cx.update_global::<SettingsStore, _>(|store, cx| {
store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
project_panel_settings.auto_fold_dirs = Some(false);
});
store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
worktree_settings.file_scan_exclusions = Some(Vec::new());
store.update_user_settings(cx, |settings| {
settings
.project_panel
.get_or_insert_default()
.auto_fold_dirs = Some(false);
settings.project.worktree.file_scan_exclusions = Some(Vec::new())
});
});
});