TEMP

Conrad Irwin created

Change summary

crates/eval/src/eval.rs                    |   5 
crates/settings/src/settings.rs            |   7 
crates/settings/src/settings_content.rs    | 367 +++++++++++++++++++++++
crates/settings/src/settings_store.rs      | 350 +++++++++++++---------
crates/theme/src/settings.rs               |   2 
crates/title_bar/src/title_bar_settings.rs |  76 +---
crates/util/src/util.rs                    |  18 +
crates/zeta_cli/src/headless.rs            |   5 
8 files changed, 618 insertions(+), 212 deletions(-)

Detailed changes

crates/eval/src/eval.rs 🔗

@@ -340,10 +340,7 @@ pub fn init(cx: &mut App) -> Arc<AgentAppState> {
     release_channel::init(app_version, cx);
     gpui_tokio::init(cx);
 
-    let mut settings_store = SettingsStore::new(cx);
-    settings_store
-        .set_default_settings(settings::default_settings().as_ref(), cx)
-        .unwrap();
+    let mut settings_store = SettingsStore::new(cx, &settings::default_settings());
     cx.set_global(settings_store);
     client::init_settings(cx);
 

crates/settings/src/settings.rs 🔗

@@ -8,6 +8,8 @@ mod settings_store;
 mod settings_ui_core;
 mod vscode_import;
 
+pub use settings_content::*;
+
 use gpui::{App, Global};
 use rust_embed::RustEmbed;
 use std::{borrow::Cow, fmt, str};
@@ -76,10 +78,7 @@ impl fmt::Display for WorktreeId {
 pub struct SettingsAssets;
 
 pub fn init(cx: &mut App) {
-    let mut settings = SettingsStore::new(cx);
-    settings
-        .set_default_settings(&default_settings(), cx)
-        .unwrap();
+    let settings = SettingsStore::new(cx, &default_settings());
     cx.set_global(settings);
     BaseKeymap::register(cx);
     SettingsStore::observe_active_settings_profile_name(cx).detach();

crates/settings/src/settings_content.rs 🔗

@@ -3,8 +3,9 @@ use std::env;
 use std::num::NonZeroU32;
 use std::sync::Arc;
 
+use anyhow::Result;
 use collections::{HashMap, HashSet};
-use gpui::{App, Modifiers, SharedString};
+use gpui::{App, FontFallbacks, FontFeatures, HighlightStyle, Hsla, Modifiers, SharedString};
 use release_channel::ReleaseChannel;
 use schemars::{JsonSchema, json_schema};
 use serde::de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor};
@@ -20,6 +21,10 @@ pub struct SettingsContent {
     pub project: ProjectSettingsContent,
 
     pub base_keymap: Option<BaseKeymapContent>,
+
+    pub auto_update: Option<bool>,
+
+    pub title_bar: Option<TitleBarSettingsContent>,
 }
 
 impl SettingsContent {
@@ -32,7 +37,7 @@ impl SettingsContent {
 #[derive(Debug, Default, Serialize, Deserialize, JsonSchema)]
 pub struct ServerSettingsContent {
     #[serde(flatten)]
-    project: ProjectSettingsContent,
+    pub project: ProjectSettingsContent,
 }
 
 #[derive(Debug, Default, Clone, Serialize, Deserialize, JsonSchema)]
@@ -970,3 +975,361 @@ pub enum IndentGuideBackgroundColoring {
     /// Use a different color for each indentation level.
     IndentAware,
 }
+
+#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)]
+pub struct TitleBarSettingsContent {
+    /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen".
+    ///
+    /// Default: "always"
+    pub show: Option<TitleBarVisibilityContent>,
+    /// Whether to show the branch icon beside branch switcher in the title bar.
+    ///
+    /// Default: false
+    pub show_branch_icon: Option<bool>,
+    /// Whether to show onboarding banners in the title bar.
+    ///
+    /// Default: true
+    pub show_onboarding_banner: Option<bool>,
+    /// Whether to show user avatar in the title bar.
+    ///
+    /// Default: true
+    pub show_user_picture: Option<bool>,
+    /// Whether to show the branch name button in the titlebar.
+    ///
+    /// Default: true
+    pub show_branch_name: Option<bool>,
+    /// Whether to show the project host and name in the titlebar.
+    ///
+    /// Default: true
+    pub show_project_items: Option<bool>,
+    /// Whether to show the sign in button in the title bar.
+    ///
+    /// Default: true
+    pub show_sign_in: Option<bool>,
+    /// Whether to show the menus in the title bar.
+    ///
+    /// Default: false
+    pub show_menus: Option<bool>,
+}
+
+#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug)]
+#[serde(rename_all = "snake_case")]
+pub enum TitleBarVisibilityContent {
+    Always,
+    Never,
+    HideInFullScreen,
+}
+
+/// Settings for rendering text in UI and text buffers.
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+pub struct ThemeSettingsContent {
+    /// The default font size for text in the UI.
+    #[serde(default)]
+    pub ui_font_size: Option<f32>,
+    /// The name of a font to use for rendering in the UI.
+    #[serde(default)]
+    pub ui_font_family: Option<FontFamilyName>,
+    /// The font fallbacks to use for rendering in the UI.
+    #[serde(default)]
+    #[schemars(default = "default_font_fallbacks")]
+    #[schemars(extend("uniqueItems" = true))]
+    pub ui_font_fallbacks: Option<Vec<FontFamilyName>>,
+    /// The OpenType features to enable for text in the UI.
+    #[serde(default)]
+    #[schemars(default = "default_font_features")]
+    pub ui_font_features: Option<FontFeatures>,
+    /// The weight of the UI font in CSS units from 100 to 900.
+    #[serde(default)]
+    pub ui_font_weight: Option<f32>,
+    /// The name of a font to use for rendering in text buffers.
+    #[serde(default)]
+    pub buffer_font_family: Option<FontFamilyName>,
+    /// The font fallbacks to use for rendering in text buffers.
+    #[serde(default)]
+    #[schemars(extend("uniqueItems" = true))]
+    pub buffer_font_fallbacks: Option<Vec<FontFamilyName>>,
+    /// The default font size for rendering in text buffers.
+    #[serde(default)]
+    pub buffer_font_size: Option<f32>,
+    /// The weight of the editor font in CSS units from 100 to 900.
+    #[serde(default)]
+    pub buffer_font_weight: Option<f32>,
+    /// The buffer's line height.
+    #[serde(default)]
+    pub buffer_line_height: Option<BufferLineHeight>,
+    /// The OpenType features to enable for rendering in text buffers.
+    #[serde(default)]
+    #[schemars(default = "default_font_features")]
+    pub buffer_font_features: Option<FontFeatures>,
+    /// The font size for the agent panel. Falls back to the UI font size if unset.
+    #[serde(default)]
+    pub agent_font_size: Option<Option<f32>>,
+    /// The name of the Zed theme to use.
+    #[serde(default)]
+    pub theme: Option<ThemeSelection>,
+    /// The name of the icon theme to use.
+    #[serde(default)]
+    pub icon_theme: Option<IconThemeSelection>,
+
+    /// UNSTABLE: Expect many elements to be broken.
+    ///
+    // Controls the density of the UI.
+    #[serde(rename = "unstable.ui_density", default)]
+    pub ui_density: Option<UiDensity>,
+
+    /// How much to fade out unused code.
+    #[serde(default)]
+    pub unnecessary_code_fade: Option<f32>,
+
+    /// EXPERIMENTAL: Overrides for the current theme.
+    ///
+    /// These values will override the ones on the current theme specified in `theme`.
+    #[serde(rename = "experimental.theme_overrides", default)]
+    pub experimental_theme_overrides: Option<ThemeStyleContent>,
+
+    /// Overrides per theme
+    ///
+    /// These values will override the ones on the specified theme
+    #[serde(default)]
+    pub theme_overrides: HashMap<String, ThemeStyleContent>,
+}
+
+fn default_font_features() -> Option<FontFeatures> {
+    Some(FontFeatures::default())
+}
+
+fn default_font_fallbacks() -> Option<FontFallbacks> {
+    Some(FontFallbacks::default())
+}
+
+/// Represents the selection of a theme, which can be either static or dynamic.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(untagged)]
+pub enum ThemeSelection {
+    /// A static theme selection, represented by a single theme name.
+    Static(ThemeName),
+    /// A dynamic theme selection, which can change based the [ThemeMode].
+    Dynamic {
+        /// The mode used to determine which theme to use.
+        #[serde(default)]
+        mode: ThemeMode,
+        /// The theme to use for light mode.
+        light: ThemeName,
+        /// The theme to use for dark mode.
+        dark: ThemeName,
+    },
+}
+
+/// Represents the selection of an icon theme, which can be either static or dynamic.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(untagged)]
+pub enum IconThemeSelection {
+    /// A static icon theme selection, represented by a single icon theme name.
+    Static(IconThemeName),
+    /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
+    Dynamic {
+        /// The mode used to determine which theme to use.
+        #[serde(default)]
+        mode: ThemeMode,
+        /// The icon theme to use for light mode.
+        light: IconThemeName,
+        /// The icon theme to use for dark mode.
+        dark: IconThemeName,
+    },
+}
+
+// TODO: Rename ThemeMode -> ThemeAppearanceMode
+/// The mode use to select a theme.
+///
+/// `Light` and `Dark` will select their respective themes.
+///
+/// `System` will select the theme based on the system's appearance.
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(rename_all = "snake_case")]
+pub enum ThemeMode {
+    /// Use the specified `light` theme.
+    Light,
+
+    /// Use the specified `dark` theme.
+    Dark,
+
+    /// Use the theme based on the system's appearance.
+    #[default]
+    System,
+}
+
+/// Specifies the density of the UI.
+/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
+#[derive(
+    Debug,
+    Default,
+    PartialEq,
+    Eq,
+    PartialOrd,
+    Ord,
+    Hash,
+    Clone,
+    Copy,
+    Serialize,
+    Deserialize,
+    JsonSchema,
+)]
+#[serde(rename_all = "snake_case")]
+pub enum UiDensity {
+    /// A denser UI with tighter spacing and smaller elements.
+    #[serde(alias = "compact")]
+    Compact,
+    #[default]
+    #[serde(alias = "default")]
+    /// The default UI density.
+    Default,
+    #[serde(alias = "comfortable")]
+    /// A looser UI with more spacing and larger elements.
+    Comfortable,
+}
+
+impl UiDensity {
+    /// The spacing ratio of a given density.
+    /// TODO: Standardize usage throughout the app or remove
+    pub fn spacing_ratio(self) -> f32 {
+        match self {
+            UiDensity::Compact => 0.75,
+            UiDensity::Default => 1.0,
+            UiDensity::Comfortable => 1.25,
+        }
+    }
+}
+
+/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at
+/// runtime.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(transparent)]
+pub struct FontFamilyName(pub Arc<str>);
+
+inventory::submit! {
+    ParameterizedJsonSchema {
+        add_and_get_ref: |generator, params, _cx| {
+            replace_subschema::<FontFamilyName>(generator, || {
+                json_schema!({
+                    "type": "string",
+                    "enum": params.font_names,
+                })
+            })
+        }
+    }
+}
+
+/// The buffer's line height.
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
+#[serde(rename_all = "snake_case")]
+pub enum BufferLineHeight {
+    /// A less dense line height.
+    #[default]
+    Comfortable,
+    /// The default line height.
+    Standard,
+    /// A custom line height, where 1.0 is the font's height. Must be at least 1.0.
+    Custom(#[serde(deserialize_with = "deserialize_line_height")] f32),
+}
+
+fn deserialize_line_height<'de, D>(deserializer: D) -> Result<f32, D::Error>
+where
+    D: serde::Deserializer<'de>,
+{
+    let value = f32::deserialize(deserializer)?;
+    if value < 1.0 {
+        return Err(serde::de::Error::custom(
+            "buffer_line_height.custom must be at least 1.0",
+        ));
+    }
+
+    Ok(value)
+}
+
+/// The content of a serialized theme.
+#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)]
+#[serde(default)]
+pub struct ThemeStyleContent {
+    #[serde(default, rename = "background.appearance")]
+    pub window_background_appearance: Option<WindowBackgroundContent>,
+
+    #[serde(default)]
+    pub accents: Vec<AccentContent>,
+
+    #[serde(flatten, default)]
+    pub colors: ThemeColorsContent,
+
+    #[serde(flatten, default)]
+    pub status: StatusColorsContent,
+
+    #[serde(default)]
+    pub players: Vec<PlayerColorContent>,
+
+    /// The styles for syntax nodes.
+    #[serde(default)]
+    pub syntax: IndexMap<String, HighlightStyleContent>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct AccentContent(pub Option<String>);
+
+#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)]
+pub struct PlayerColorContent {
+    pub cursor: Option<String>,
+    pub background: Option<String>,
+    pub selection: Option<String>,
+}
+
+pub(crate) fn try_parse_color(color: &str) -> Result<Hsla> {
+    let rgba = gpui::Rgba::try_from(color)?;
+    let rgba = palette::rgb::Srgba::from_components((rgba.r, rgba.g, rgba.b, rgba.a));
+    let hsla = palette::Hsla::from_color(rgba);
+
+    let hsla = gpui::hsla(
+        hsla.hue.into_positive_degrees() / 360.,
+        hsla.saturation,
+        hsla.lightness,
+        hsla.alpha,
+    );
+
+    Ok(hsla)
+}
+
+/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(transparent)]
+pub struct ThemeName(pub Arc<str>);
+
+inventory::submit! {
+    ParameterizedJsonSchema {
+        add_and_get_ref: |generator, _params, cx| {
+            todo!()
+            // replace_subschema::<ThemeName>(generator, || json_schema!({
+            //     "type": "string",
+            //     "enum": ThemeRegistry::global(cx).list_names(),
+            // }))
+        }
+    }
+}
+
+/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at
+/// runtime.
+#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(transparent)]
+pub struct IconThemeName(pub Arc<str>);
+
+inventory::submit! {
+    ParameterizedJsonSchema {
+        add_and_get_ref: |generator, _params, cx| {
+            todo!()
+            // replace_subschema::<IconThemeName>(generator, || json_schema!({
+            //     "type": "string",
+            //     "enum": ThemeRegistry::global(cx)
+            //         .list_icon_themes()
+            //         .into_iter()
+            //         .map(|icon_theme| icon_theme.name)
+            //         .collect::<Vec<SharedString>>(),
+            // }))
+        }
+    }
+}

crates/settings/src/settings_store.rs 🔗

@@ -10,9 +10,8 @@ use futures::{
 use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal};
 
 use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
-use schemars::JsonSchema;
 use serde::{Serialize, de::DeserializeOwned};
-use serde_json::{Value, json};
+use serde_json::Value;
 use smallvec::SmallVec;
 use std::{
     any::{Any, TypeId, type_name},
@@ -32,7 +31,8 @@ pub type EditorconfigProperties = ec4rs::Properties;
 
 use crate::{
     ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry,
-    VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text,
+    VsCodeSettings, WorktreeId, default_settings, parse_json_with_comments,
+    replace_value_in_json_text,
     settings_content::{
         ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent,
         UserSettingsContent,
@@ -201,7 +201,7 @@ pub struct SettingsLocation<'a> {
 /// A set of strongly-typed setting values defined via multiple config files.
 pub struct SettingsStore {
     setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>,
-    default_settings: Option<SettingsContent>,
+    default_settings: SettingsContent,
     user_settings: Option<UserSettingsContent>,
     global_settings: Option<SettingsContent>,
 
@@ -276,11 +276,12 @@ trait AnySettingValue: 'static + Send + Sync {
 struct DeserializedSetting(Box<dyn Any>);
 
 impl SettingsStore {
-    pub fn new(cx: &App) -> Self {
+    pub fn new(cx: &App, default_settings: &str) -> Self {
         let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
+        let default_settings = parse_json_with_comments(default_settings).unwrap();
         Self {
             setting_values: Default::default(),
-            default_settings: Some(Default::default()), // todo!()
+            default_settings,
             global_settings: None,
             server_settings: None,
             user_settings: Some(Default::default()), // todo!()
@@ -347,9 +348,8 @@ impl SettingsStore {
         if let Some(server_settings) = self.server_settings.as_ref() {
             refinements.push(server_settings)
         }
-        let default = self.default_settings.as_ref().unwrap();
         // todo!() unwrap...
-        let mut value = T::from_file(default).unwrap();
+        let mut value = T::from_file(&self.default_settings).unwrap();
         for refinement in refinements {
             value.refine(refinement)
         }
@@ -438,17 +438,13 @@ impl SettingsStore {
     }
 
     /// Access the raw JSON value of the default settings.
-    pub fn raw_default_settings(&self) -> Option<&SettingsContent> {
-        self.default_settings.as_ref()
+    pub fn raw_default_settings(&self) -> &SettingsContent {
+        &self.default_settings
     }
 
     #[cfg(any(test, feature = "test-support"))]
     pub fn test(cx: &mut App) -> Self {
-        let mut this = Self::new(cx);
-        this.set_default_settings(&crate::test_settings(), cx)
-            .unwrap();
-        this.set_user_settings("{}", cx).unwrap();
-        this
+        Self::new(cx, &crate::test_settings())
     }
 
     /// Updates the value of a setting in the user's global configuration.
@@ -715,6 +711,12 @@ impl SettingsStore {
             parse_json_with_comments(server_settings_content)?
         };
 
+        // Rewrite the server settings into a content type
+        self.server_settings = settings.map(|settings| SettingsContent {
+            project: settings.project,
+            ..Default::default()
+        });
+
         todo!();
         // self.server_settings = Some(settings);
         self.recompute_values(None, cx)?;
@@ -1110,13 +1112,12 @@ impl SettingsStore {
         if let Some(server_settings) = self.server_settings.as_ref() {
             refinements.push(server_settings)
         }
-        let default = self.default_settings.as_ref().unwrap();
 
         for setting_value in self.setting_values.values_mut() {
             // If the global settings file changed, reload the global value for the field.
             if changed_local_path.is_none() {
-                let mut value = setting_value.from_file(&default).unwrap();
-                setting_value.refine(&mut value, &refinements);
+                let mut value = setting_value.from_file(&self.default_settings).unwrap();
+                setting_value.refine(value.as_mut(), &refinements);
                 setting_value.set_global_value(value);
             }
 
@@ -1151,9 +1152,9 @@ impl SettingsStore {
                         continue;
                     }
 
-                    let mut value = setting_value.from_file(&default).unwrap();
-                    setting_value.refine(&mut value, &refinements);
-                    setting_value.refine(&mut value, &project_settings_stack);
+                    let mut value = setting_value.from_file(&self.default_settings).unwrap();
+                    setting_value.refine(value.as_mut(), &refinements);
+                    setting_value.refine(value.as_mut(), &project_settings_stack);
                     setting_value.set_local_value(*root_id, directory_path.clone(), value);
                 }
             }
@@ -1237,10 +1238,12 @@ impl Debug for SettingsStore {
 
 impl<T: Settings> AnySettingValue for SettingValue<T> {
     fn from_file(&self, s: &SettingsContent) -> Option<Box<dyn Any>> {
+        dbg!(type_name::<T>(), TypeId::of::<T>());
         T::from_file(s).map(|result| Box::new(result) as _)
     }
 
     fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent]) {
+        dbg!(type_name::<T>(), TypeId::of::<T>());
         let value = value.downcast_mut::<T>().unwrap();
         for refinement in refinements {
             value.refine(refinement)
@@ -1336,7 +1339,10 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
 
 #[cfg(test)]
 mod tests {
-    use crate::{VsCodeSettingsSource, settings_content::LanguageSettingsContent};
+    use crate::{
+        TitleBarSettingsContent, TitleBarVisibilityContent, VsCodeSettingsSource,
+        settings_content::LanguageSettingsContent, test_settings,
+    };
 
     use super::*;
     // This is so the SettingsUi macro can still work properly
@@ -1344,138 +1350,186 @@ mod tests {
     use serde::Deserialize;
     use settings_ui_macros::{SettingsKey, SettingsUi};
     use unindent::Unindent;
+    use util::Refine;
 
-    // #[gpui::test]
-    // fn test_settings_store_basic(cx: &mut App) {
-    //     let mut store = SettingsStore::new(cx);
-    //     store.register_setting::<UserSettings>(cx);
-    //     store.register_setting::<TurboSetting>(cx);
-    //     store.register_setting::<MultiKeySettings>(cx);
-    //     store
-    //         .set_default_settings(
-    //             r#"{
-    //                 "turbo": false,
-    //                 "user": {
-    //                     "name": "John Doe",
-    //                     "age": 30,
-    //                     "staff": false
-    //                 }
-    //             }"#,
-    //             cx,
-    //         )
-    //         .unwrap();
+    #[derive(Debug, PartialEq)]
+    struct AutoUpdateSetting {
+        auto_update: bool,
+    }
 
-    //     assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
-    //     assert_eq!(
-    //         store.get::<UserSettings>(None),
-    //         &UserSettings {
-    //             name: "John Doe".to_string(),
-    //             age: 30,
-    //             staff: false,
-    //         }
-    //     );
-    //     assert_eq!(
-    //         store.get::<MultiKeySettings>(None),
-    //         &MultiKeySettings {
-    //             key1: String::new(),
-    //             key2: String::new(),
-    //         }
-    //     );
+    impl Settings for AutoUpdateSetting {
+        fn from_file(content: &SettingsContent) -> Option<Self> {
+            content
+                .auto_update
+                .map(|auto_update| AutoUpdateSetting { auto_update })
+        }
 
-    //     store
-    //         .set_user_settings(
-    //             r#"{
-    //                 "turbo": true,
-    //                 "user": { "age": 31 },
-    //                 "key1": "a"
-    //             }"#,
-    //             cx,
-    //         )
-    //         .unwrap();
+        fn refine(&mut self, content: &SettingsContent) {
+            if let Some(auto_update) = content.auto_update {
+                self.auto_update = auto_update;
+            }
+        }
 
-    //     assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
-    //     assert_eq!(
-    //         store.get::<UserSettings>(None),
-    //         &UserSettings {
-    //             name: "John Doe".to_string(),
-    //             age: 31,
-    //             staff: false
-    //         }
-    //     );
+        fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
+    }
 
-    //     store
-    //         .set_local_settings(
-    //             WorktreeId::from_usize(1),
-    //             Path::new("/root1").into(),
-    //             LocalSettingsKind::Settings,
-    //             Some(r#"{ "user": { "staff": true } }"#),
-    //             cx,
-    //         )
-    //         .unwrap();
-    //     store
-    //         .set_local_settings(
-    //             WorktreeId::from_usize(1),
-    //             Path::new("/root1/subdir").into(),
-    //             LocalSettingsKind::Settings,
-    //             Some(r#"{ "user": { "name": "Jane Doe" } }"#),
-    //             cx,
-    //         )
-    //         .unwrap();
+    #[derive(Debug, PartialEq)]
+    struct TitleBarSettings {
+        show: TitleBarVisibilityContent,
+    }
 
-    //     store
-    //         .set_local_settings(
-    //             WorktreeId::from_usize(1),
-    //             Path::new("/root2").into(),
-    //             LocalSettingsKind::Settings,
-    //             Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
-    //             cx,
-    //         )
-    //         .unwrap();
+    impl Settings for TitleBarSettings {
+        fn from_file(content: &SettingsContent) -> Option<Self> {
+            let content = content.title_bar?;
+            Some(TitleBarSettings {
+                show: content.show?,
+            })
+        }
 
-    //     assert_eq!(
-    //         store.get::<UserSettings>(Some(SettingsLocation {
-    //             worktree_id: WorktreeId::from_usize(1),
-    //             path: Path::new("/root1/something"),
-    //         })),
-    //         &UserSettings {
-    //             name: "John Doe".to_string(),
-    //             age: 31,
-    //             staff: true
-    //         }
-    //     );
-    //     assert_eq!(
-    //         store.get::<UserSettings>(Some(SettingsLocation {
-    //             worktree_id: WorktreeId::from_usize(1),
-    //             path: Path::new("/root1/subdir/something")
-    //         })),
-    //         &UserSettings {
-    //             name: "Jane Doe".to_string(),
-    //             age: 31,
-    //             staff: true
-    //         }
-    //     );
-    //     assert_eq!(
-    //         store.get::<UserSettings>(Some(SettingsLocation {
-    //             worktree_id: WorktreeId::from_usize(1),
-    //             path: Path::new("/root2/something")
-    //         })),
-    //         &UserSettings {
-    //             name: "John Doe".to_string(),
-    //             age: 42,
-    //             staff: false
-    //         }
-    //     );
-    //     assert_eq!(
-    //         store.get::<MultiKeySettings>(Some(SettingsLocation {
-    //             worktree_id: WorktreeId::from_usize(1),
-    //             path: Path::new("/root2/something")
-    //         })),
-    //         &MultiKeySettings {
-    //             key1: "a".to_string(),
-    //             key2: "b".to_string(),
-    //         }
-    //     );
-    // }
+        fn refine(&mut self, content: &SettingsContent) {
+            let Some(content) = content.title_bar else {
+                return;
+            };
+            self.show.refine(&content.show)
+        }
+
+        fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {}
+    }
+
+    #[gpui::test]
+    fn test_settings_store_basic(cx: &mut App) {
+        let mut store = SettingsStore::new(
+            cx,
+            r#"{
+                "auto_update": false,
+                "user": {
+                    "name": "John Doe",
+                    "age": 30,
+                    "staff": false
+                }
+            }"#,
+        );
+        store.register_setting::<AutoUpdateSetting>(cx);
+        store.register_setting::<TitleBarSettings>(cx);
+        // store.register_setting::<MultiKeySettings>(cx);
+
+        assert_eq!(
+            store.get::<AutoUpdateSetting>(None),
+            &AutoUpdateSetting { auto_update: false }
+        );
+        // assert_eq!(
+        //     store.get::<UserSettings>(None),
+        //     &UserSettings {
+        //         name: "John Doe".to_string(),
+        //         age: 30,
+        //         staff: false,
+        //     }
+        // );
+        // assert_eq!(
+        //     store.get::<MultiKeySettings>(None),
+        //     &MultiKeySettings {
+        //         key1: String::new(),
+        //         key2: String::new(),
+        //     }
+        // );
+
+        store
+            .set_user_settings(
+                r#"{
+                    "auto_update": true,
+                    "user": { "age": 31 },
+                    "key1": "a"
+                }"#,
+                cx,
+            )
+            .unwrap();
+
+        assert_eq!(
+            store.get::<AutoUpdateSetting>(None),
+            &AutoUpdateSetting { auto_update: true }
+        );
+        // assert_eq!(
+        //     store.get::<UserSettings>(None),
+        //     &UserSettings {
+        //         name: "John Doe".to_string(),
+        //         age: 31,
+        //         staff: false
+        //     }
+        // );
+
+        store
+            .set_local_settings(
+                WorktreeId::from_usize(1),
+                Path::new("/root1").into(),
+                LocalSettingsKind::Settings,
+                Some(r#"{ "user": { "staff": true } }"#),
+                cx,
+            )
+            .unwrap();
+        store
+            .set_local_settings(
+                WorktreeId::from_usize(1),
+                Path::new("/root1/subdir").into(),
+                LocalSettingsKind::Settings,
+                Some(r#"{ "user": { "name": "Jane Doe" } }"#),
+                cx,
+            )
+            .unwrap();
+
+        store
+            .set_local_settings(
+                WorktreeId::from_usize(1),
+                Path::new("/root2").into(),
+                LocalSettingsKind::Settings,
+                Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
+                cx,
+            )
+            .unwrap();
+
+        // assert_eq!(
+        //     store.get::<UserSettings>(Some(SettingsLocation {
+        //         worktree_id: WorktreeId::from_usize(1),
+        //         path: Path::new("/root1/something"),
+        //     })),
+        //     &UserSettings {
+        //         name: "John Doe".to_string(),
+        //         age: 31,
+        //         staff: true
+        //     }
+        // );
+        // assert_eq!(
+        //     store.get::<UserSettings>(Some(SettingsLocation {
+        //         worktree_id: WorktreeId::from_usize(1),
+        //         path: Path::new("/root1/subdir/something")
+        //     })),
+        //     &UserSettings {
+        //         name: "Jane Doe".to_string(),
+        //         age: 31,
+        //         staff: true
+        //     }
+        // );
+        // assert_eq!(
+        //     store.get::<UserSettings>(Some(SettingsLocation {
+        //         worktree_id: WorktreeId::from_usize(1),
+        //         path: Path::new("/root2/something")
+        //     })),
+        //     &UserSettings {
+        //         name: "John Doe".to_string(),
+        //         age: 42,
+        //         staff: false
+        //     }
+        // );
+        // assert_eq!(
+        //     store.get::<MultiKeySettings>(Some(SettingsLocation {
+        //         worktree_id: WorktreeId::from_usize(1),
+        //         path: Path::new("/root2/something")
+        //     })),
+        //     &MultiKeySettings {
+        //         key1: "a".to_string(),
+        //         key2: "b".to_string(),
+        //     }
+        // );
+    }
 
     // #[gpui::test]
     // fn test_setting_store_assign_json_before_register(cx: &mut App) {
@@ -1538,7 +1592,7 @@ mod tests {
 
     #[gpui::test]
     fn test_setting_store_update(cx: &mut App) {
-        let mut store = SettingsStore::new(cx);
+        let mut store = SettingsStore::new(cx, &test_settings());
         // store.register_setting::<MultiKeySettings>(cx);
         // store.register_setting::<UserSettings>(cx);
         // store.register_setting::<LanguageSettings>(cx);

crates/theme/src/settings.rs 🔗

@@ -819,8 +819,6 @@ fn clamp_font_weight(weight: f32) -> FontWeight {
 }
 
 impl settings::Settings for ThemeSettings {
-    type FileContent = ThemeSettingsContent;
-
     fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
         let themes = ThemeRegistry::default_global(cx);
         let system_appearance = SystemAppearance::default_global(cx);

crates/title_bar/src/title_bar_settings.rs 🔗

@@ -1,7 +1,7 @@
 use db::anyhow;
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
+use settings::{Settings, SettingsContent, SettingsKey, SettingsSources, SettingsUi};
 
 #[derive(Copy, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
 #[serde(rename_all = "snake_case")]
@@ -11,7 +11,7 @@ pub enum TitleBarVisibility {
     HideInFullScreen,
 }
 
-#[derive(Copy, Clone, Deserialize, Debug)]
+#[derive(Copy, Clone, Debug)]
 pub struct TitleBarSettings {
     pub show: TitleBarVisibility,
     pub show_branch_icon: bool,
@@ -23,54 +23,34 @@ pub struct TitleBarSettings {
     pub show_menus: bool,
 }
 
-#[derive(
-    Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey,
-)]
-#[settings_ui(group = "Title Bar")]
-#[settings_key(key = "title_bar")]
-pub struct TitleBarSettingsContent {
-    /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen".
-    ///
-    /// Default: "always"
-    pub show: Option<TitleBarVisibility>,
-    /// Whether to show the branch icon beside branch switcher in the title bar.
-    ///
-    /// Default: false
-    pub show_branch_icon: Option<bool>,
-    /// Whether to show onboarding banners in the title bar.
-    ///
-    /// Default: true
-    pub show_onboarding_banner: Option<bool>,
-    /// Whether to show user avatar in the title bar.
-    ///
-    /// Default: true
-    pub show_user_picture: Option<bool>,
-    /// Whether to show the branch name button in the titlebar.
-    ///
-    /// Default: true
-    pub show_branch_name: Option<bool>,
-    /// Whether to show the project host and name in the titlebar.
-    ///
-    /// Default: true
-    pub show_project_items: Option<bool>,
-    /// Whether to show the sign in button in the title bar.
-    ///
-    /// Default: true
-    pub show_sign_in: Option<bool>,
-    /// Whether to show the menus in the title bar.
-    ///
-    /// Default: false
-    pub show_menus: Option<bool>,
-}
-
 impl Settings for TitleBarSettings {
-    type FileContent = TitleBarSettingsContent;
+    fn from_file(s: &SettingsContent) -> Option<Self> {
+        let content = s.title_bar?;
+        TitleBarSettings {
+            show: content.show?,
+            show_branch_icon: content.show_branch_icon?,
+            show_onboarding_banner: content.show_onboarding_banner?,
+            show_user_picture: content.show_user_picture?,
+            show_branch_name: content.show_branch_name?,
+            show_project_items: content.show_project_items?,
+            show_sign_in: content.show_sign_in?,
+            show_menus: content.show_menus?,
+        }
+    }
+
+    fn refine(&mut self, s: &SettingsContent) {
+        let Some(content) = s.title_bar else {
+            return
+        }
 
-    fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::App) -> anyhow::Result<Self>
-    where
-        Self: Sized,
-    {
-        sources.json_merge()
+        self.show.refine(&content.show);
+        self.show_branch_icon.refine(content.show_branch_icon);
+        self.show_onboarding_banner.refine(content.show_onboarding_banner);
+        self.show_user_picture.refine(content.show_user_picture);
+        self.show_branch_name.refine(content.show_branch_name);
+        self.show_project_items.refine(content.show_project_items);
+        self.show_sign_in.refine(content.show_sign_in);
+        self.show_menus.refine(content.show_menus);
     }
 
     fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut Self::FileContent) {}

crates/util/src/util.rs 🔗

@@ -1381,3 +1381,21 @@ Line 3"#
         assert_eq!(result[1], (10..15, "world")); // '🦀' is 4 bytes
     }
 }
+
+pub fn refine<T: Clone>(dest: &mut T, src: &Option<T>) {
+    if let Some(src) = src {
+        *dest = src.clone()
+    }
+}
+
+pub trait Refine: Sized + Clone {
+    fn refine(&mut self, src: &Option<Self>);
+}
+
+impl<T: Clone> Refine for T {
+    fn refine(&mut self, src: &Option<Self>) {
+        if let Some(src) = src {
+            *self = src.clone();
+        }
+    }
+}

crates/zeta_cli/src/headless.rs 🔗

@@ -31,10 +31,7 @@ pub fn init(cx: &mut App) -> ZetaCliAppState {
     release_channel::init(app_version, cx);
     gpui_tokio::init(cx);
 
-    let mut settings_store = SettingsStore::new(cx);
-    settings_store
-        .set_default_settings(settings::default_settings().as_ref(), cx)
-        .unwrap();
+    let mut settings_store = SettingsStore::new(cx, settings::default_settings());
     cx.set_global(settings_store);
     client::init_settings(cx);