Fix settings2 crate tests

Conrad Irwin created

Change summary

Cargo.lock                             |   3 
crates/language2/Cargo.toml            |   2 
crates/settings2/Cargo.toml            |   2 
crates/settings2/src/settings_file.rs  |   2 
crates/settings2/src/settings_store.rs | 909 +++++++++++++--------------
crates/theme2/src/settings.rs          |   2 
crates/theme2/src/theme2.rs            |   1 
7 files changed, 446 insertions(+), 475 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -4248,7 +4248,7 @@ dependencies = [
  "collections",
  "ctor",
  "env_logger 0.9.3",
- "fs",
+ "fs2",
  "futures 0.3.28",
  "fuzzy2",
  "git",
@@ -7515,6 +7515,7 @@ dependencies = [
  "collections",
  "feature_flags2",
  "fs",
+ "fs2",
  "futures 0.3.28",
  "gpui2",
  "indoc",

crates/language2/Cargo.toml 🔗

@@ -25,7 +25,7 @@ test-support = [
 clock = { path = "../clock" }
 collections = { path = "../collections" }
 fuzzy2 = { path = "../fuzzy2" }
-fs = { path = "../fs" }
+fs2 = { path = "../fs2" }
 git = { path = "../git" }
 gpui2 = { path = "../gpui2" }
 lsp2 = { path = "../lsp2" }

crates/settings2/Cargo.toml 🔗

@@ -15,7 +15,7 @@ test-support = ["gpui2/test-support", "fs/test-support"]
 collections = { path = "../collections" }
 gpui2 = { path = "../gpui2" }
 sqlez = { path = "../sqlez" }
-fs = { path = "../fs" }
+fs2 = { path = "../fs2" }
 feature_flags2 = { path = "../feature_flags2" }
 util = { path = "../util" }
 

crates/settings2/src/settings_file.rs 🔗

@@ -1,6 +1,6 @@
 use crate::{settings_store::SettingsStore, Settings};
 use anyhow::Result;
-use fs::Fs;
+use fs2::Fs;
 use futures::{channel::mpsc, StreamExt};
 use gpui2::{AppContext, Executor};
 use std::{io::ErrorKind, path::PathBuf, str, sync::Arc, time::Duration};

crates/settings2/src/settings_store.rs 🔗

@@ -35,7 +35,7 @@ pub trait Settings: 'static + Send + Sync {
     fn load(
         default_value: &Self::FileContent,
         user_values: &[&Self::FileContent],
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<Self>
     where
         Self: Sized;
@@ -76,36 +76,6 @@ pub trait Settings: 'static + Send + Sync {
     fn missing_default() -> anyhow::Error {
         anyhow::anyhow!("missing default")
     }
-
-    fn register(cx: &mut AppContext)
-    where
-        Self: Sized,
-    {
-        cx.update_global(|store: &mut SettingsStore, cx| {
-            store.register_setting::<Self>(cx);
-        });
-    }
-
-    fn get<'a>(path: Option<(usize, &Path)>, cx: &'a AppContext) -> &'a Self
-    where
-        Self: Sized,
-    {
-        cx.global::<SettingsStore>().get(path)
-    }
-
-    fn get_global<'a>(cx: &'a AppContext) -> &'a Self
-    where
-        Self: Sized,
-    {
-        cx.global::<SettingsStore>().get(None)
-    }
-
-    fn override_global<'a>(settings: Self, cx: &'a mut AppContext)
-    where
-        Self: Sized,
-    {
-        cx.global_mut::<SettingsStore>().override_global(settings)
-    }
 }
 
 pub struct SettingsJsonSchemaParams<'a> {
@@ -151,7 +121,7 @@ trait AnySettingValue: 'static + Send + Sync {
         &self,
         default_value: &DeserializedSetting,
         custom: &[DeserializedSetting],
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<Box<dyn Any>>;
     fn value_for_path(&self, path: Option<(usize, &Path)>) -> &dyn Any;
     fn set_global_value(&mut self, value: Box<dyn Any>);
@@ -168,7 +138,7 @@ struct DeserializedSetting(Box<dyn Any>);
 
 impl SettingsStore {
     /// Add a new type of setting to the store.
-    pub fn register_setting<T: Settings>(&mut self, cx: &mut AppContext) {
+    pub fn register_setting<T: Settings>(&mut self, cx: &AppContext) {
         let setting_type_id = TypeId::of::<T>();
         let entry = self.setting_values.entry(setting_type_id);
         if matches!(entry, hash_map::Entry::Occupied(_)) {
@@ -235,7 +205,7 @@ impl SettingsStore {
     }
 
     #[cfg(any(test, feature = "test-support"))]
-    pub fn test(cx: &mut AppContext) -> Self {
+    pub fn test(cx: &AppContext) -> Self {
         let mut this = Self::default();
         this.set_default_settings(&crate::test_settings(), cx)
             .unwrap();
@@ -250,7 +220,7 @@ impl SettingsStore {
     #[cfg(any(test, feature = "test-support"))]
     pub fn update_user_settings<T: Settings>(
         &mut self,
-        cx: &mut AppContext,
+        cx: &AppContext,
         update: impl FnOnce(&mut T::FileContent),
     ) {
         let old_text = serde_json::to_string(&self.raw_user_settings).unwrap();
@@ -347,7 +317,7 @@ impl SettingsStore {
     pub fn set_default_settings(
         &mut self,
         default_settings_content: &str,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         let settings: serde_json::Value = parse_json_with_comments(default_settings_content)?;
         if settings.is_object() {
@@ -363,7 +333,7 @@ impl SettingsStore {
     pub fn set_user_settings(
         &mut self,
         user_settings_content: &str,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         let settings: serde_json::Value = parse_json_with_comments(user_settings_content)?;
         if settings.is_object() {
@@ -381,7 +351,7 @@ impl SettingsStore {
         root_id: usize,
         path: Arc<Path>,
         settings_content: Option<&str>,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         if let Some(content) = settings_content {
             self.raw_local_settings
@@ -394,7 +364,7 @@ impl SettingsStore {
     }
 
     /// Add or remove a set of local settings via a JSON string.
-    pub fn clear_local_settings(&mut self, root_id: usize, cx: &mut AppContext) -> Result<()> {
+    pub fn clear_local_settings(&mut self, root_id: usize, cx: &AppContext) -> Result<()> {
         self.raw_local_settings.retain(|k, _| k.0 != root_id);
         self.recompute_values(Some((root_id, "".as_ref())), cx)?;
         Ok(())
@@ -486,7 +456,7 @@ impl SettingsStore {
     fn recompute_values(
         &mut self,
         changed_local_path: Option<(usize, &Path)>,
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<()> {
         // Reload the global and local values for every setting.
         let mut user_settings_stack = Vec::<DeserializedSetting>::new();
@@ -587,7 +557,7 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
         &self,
         default_value: &DeserializedSetting,
         user_values: &[DeserializedSetting],
-        cx: &mut AppContext,
+        cx: &AppContext,
     ) -> Result<Box<dyn Any>> {
         let default_value = default_value.0.downcast_ref::<T::FileContent>().unwrap();
         let values: SmallVec<[&T::FileContent; 6]> = user_values
@@ -871,431 +841,432 @@ pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T>
     Ok(serde_json_lenient::from_str(content)?)
 }
 
-// #[cfg(test)]
-// mod tests {
-//     use super::*;
-//     use serde_derive::Deserialize;
-//     use unindent::Unindent;
-
-//     #[gpui::test]
-//     fn test_settings_store_basic(cx: &mut AppContext) {
-//         let mut store = SettingsStore::default();
-//         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();
-
-//         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(),
-//             }
-//         );
-
-//         store
-//             .set_user_settings(
-//                 r#"{
-//                     "turbo": true,
-//                     "user": { "age": 31 },
-//                     "key1": "a"
-//                 }"#,
-//                 cx,
-//             )
-//             .unwrap();
-
-//         assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
-//         assert_eq!(
-//             store.get::<UserSettings>(None),
-//             &UserSettings {
-//                 name: "John Doe".to_string(),
-//                 age: 31,
-//                 staff: false
-//             }
-//         );
-
-//         store
-//             .set_local_settings(
-//                 1,
-//                 Path::new("/root1").into(),
-//                 Some(r#"{ "user": { "staff": true } }"#),
-//                 cx,
-//             )
-//             .unwrap();
-//         store
-//             .set_local_settings(
-//                 1,
-//                 Path::new("/root1/subdir").into(),
-//                 Some(r#"{ "user": { "name": "Jane Doe" } }"#),
-//                 cx,
-//             )
-//             .unwrap();
-
-//         store
-//             .set_local_settings(
-//                 1,
-//                 Path::new("/root2").into(),
-//                 Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
-//                 cx,
-//             )
-//             .unwrap();
-
-//         assert_eq!(
-//             store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
-//             &UserSettings {
-//                 name: "John Doe".to_string(),
-//                 age: 31,
-//                 staff: true
-//             }
-//         );
-//         assert_eq!(
-//             store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
-//             &UserSettings {
-//                 name: "Jane Doe".to_string(),
-//                 age: 31,
-//                 staff: true
-//             }
-//         );
-//         assert_eq!(
-//             store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
-//             &UserSettings {
-//                 name: "John Doe".to_string(),
-//                 age: 42,
-//                 staff: false
-//             }
-//         );
-//         assert_eq!(
-//             store.get::<MultiKeySettings>(Some((1, 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 AppContext) {
-//         let mut store = SettingsStore::default();
-//         store
-//             .set_default_settings(
-//                 r#"{
-//                     "turbo": true,
-//                     "user": {
-//                         "name": "John Doe",
-//                         "age": 30,
-//                         "staff": false
-//                     },
-//                     "key1": "x"
-//                 }"#,
-//                 cx,
-//             )
-//             .unwrap();
-//         store
-//             .set_user_settings(r#"{ "turbo": false }"#, cx)
-//             .unwrap();
-//         store.register_setting::<UserSettings>(cx);
-//         store.register_setting::<TurboSetting>(cx);
-
-//         assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
-//         assert_eq!(
-//             store.get::<UserSettings>(None),
-//             &UserSettings {
-//                 name: "John Doe".to_string(),
-//                 age: 30,
-//                 staff: false,
-//             }
-//         );
-
-//         store.register_setting::<MultiKeySettings>(cx);
-//         assert_eq!(
-//             store.get::<MultiKeySettings>(None),
-//             &MultiKeySettings {
-//                 key1: "x".into(),
-//                 key2: String::new(),
-//             }
-//         );
-//     }
-
-//     #[gpui::test]
-//     fn test_setting_store_update(cx: &mut AppContext) {
-//         let mut store = SettingsStore::default();
-//         store.register_setting::<MultiKeySettings>(cx);
-//         store.register_setting::<UserSettings>(cx);
-//         store.register_setting::<LanguageSettings>(cx);
-
-//         // entries added and updated
-//         check_settings_update::<LanguageSettings>(
-//             &mut store,
-//             r#"{
-//                 "languages": {
-//                     "JSON": {
-//                         "language_setting_1": true
-//                     }
-//                 }
-//             }"#
-//             .unindent(),
-//             |settings| {
-//                 settings
-//                     .languages
-//                     .get_mut("JSON")
-//                     .unwrap()
-//                     .language_setting_1 = Some(false);
-//                 settings.languages.insert(
-//                     "Rust".into(),
-//                     LanguageSettingEntry {
-//                         language_setting_2: Some(true),
-//                         ..Default::default()
-//                     },
-//                 );
-//             },
-//             r#"{
-//                 "languages": {
-//                     "Rust": {
-//                         "language_setting_2": true
-//                     },
-//                     "JSON": {
-//                         "language_setting_1": false
-//                     }
-//                 }
-//             }"#
-//             .unindent(),
-//             cx,
-//         );
-
-//         // weird formatting
-//         check_settings_update::<UserSettings>(
-//             &mut store,
-//             r#"{
-//                 "user":   { "age": 36, "name": "Max", "staff": true }
-//             }"#
-//             .unindent(),
-//             |settings| settings.age = Some(37),
-//             r#"{
-//                 "user":   { "age": 37, "name": "Max", "staff": true }
-//             }"#
-//             .unindent(),
-//             cx,
-//         );
-
-//         // single-line formatting, other keys
-//         check_settings_update::<MultiKeySettings>(
-//             &mut store,
-//             r#"{ "one": 1, "two": 2 }"#.unindent(),
-//             |settings| settings.key1 = Some("x".into()),
-//             r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
-//             cx,
-//         );
-
-//         // empty object
-//         check_settings_update::<UserSettings>(
-//             &mut store,
-//             r#"{
-//                 "user": {}
-//             }"#
-//             .unindent(),
-//             |settings| settings.age = Some(37),
-//             r#"{
-//                 "user": {
-//                     "age": 37
-//                 }
-//             }"#
-//             .unindent(),
-//             cx,
-//         );
-
-//         // no content
-//         check_settings_update::<UserSettings>(
-//             &mut store,
-//             r#""#.unindent(),
-//             |settings| settings.age = Some(37),
-//             r#"{
-//                 "user": {
-//                     "age": 37
-//                 }
-//             }
-//             "#
-//             .unindent(),
-//             cx,
-//         );
-
-//         check_settings_update::<UserSettings>(
-//             &mut store,
-//             r#"{
-//             }
-//             "#
-//             .unindent(),
-//             |settings| settings.age = Some(37),
-//             r#"{
-//                 "user": {
-//                     "age": 37
-//                 }
-//             }
-//             "#
-//             .unindent(),
-//             cx,
-//         );
-//     }
-
-//     fn check_settings_update<T: Setting>(
-//         store: &mut SettingsStore,
-//         old_json: String,
-//         update: fn(&mut T::FileContent),
-//         expected_new_json: String,
-//         cx: &mut AppContext,
-//     ) {
-//         store.set_user_settings(&old_json, cx).ok();
-//         let edits = store.edits_for_update::<T>(&old_json, update);
-//         let mut new_json = old_json;
-//         for (range, replacement) in edits.into_iter() {
-//             new_json.replace_range(range, &replacement);
-//         }
-//         pretty_assertions::assert_eq!(new_json, expected_new_json);
-//     }
-
-//     #[derive(Debug, PartialEq, Deserialize)]
-//     struct UserSettings {
-//         name: String,
-//         age: u32,
-//         staff: bool,
-//     }
-
-//     #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
-//     struct UserSettingsJson {
-//         name: Option<String>,
-//         age: Option<u32>,
-//         staff: Option<bool>,
-//     }
-
-//     impl Setting for UserSettings {
-//         const KEY: Option<&'static str> = Some("user");
-//         type FileContent = UserSettingsJson;
-
-//         fn load(
-//             default_value: &UserSettingsJson,
-//             user_values: &[&UserSettingsJson],
-//             _: &AppContext,
-//         ) -> Result<Self> {
-//             Self::load_via_json_merge(default_value, user_values)
-//         }
-//     }
-
-//     #[derive(Debug, Deserialize, PartialEq)]
-//     struct TurboSetting(bool);
-
-//     impl Setting for TurboSetting {
-//         const KEY: Option<&'static str> = Some("turbo");
-//         type FileContent = Option<bool>;
-
-//         fn load(
-//             default_value: &Option<bool>,
-//             user_values: &[&Option<bool>],
-//             _: &AppContext,
-//         ) -> Result<Self> {
-//             Self::load_via_json_merge(default_value, user_values)
-//         }
-//     }
-
-//     #[derive(Clone, Debug, PartialEq, Deserialize)]
-//     struct MultiKeySettings {
-//         #[serde(default)]
-//         key1: String,
-//         #[serde(default)]
-//         key2: String,
-//     }
-
-//     #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-//     struct MultiKeySettingsJson {
-//         key1: Option<String>,
-//         key2: Option<String>,
-//     }
-
-//     impl Setting for MultiKeySettings {
-//         const KEY: Option<&'static str> = None;
-
-//         type FileContent = MultiKeySettingsJson;
-
-//         fn load(
-//             default_value: &MultiKeySettingsJson,
-//             user_values: &[&MultiKeySettingsJson],
-//             _: &AppContext,
-//         ) -> Result<Self> {
-//             Self::load_via_json_merge(default_value, user_values)
-//         }
-//     }
-
-//     #[derive(Debug, Deserialize)]
-//     struct JournalSettings {
-//         pub path: String,
-//         pub hour_format: HourFormat,
-//     }
-
-//     #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
-//     #[serde(rename_all = "snake_case")]
-//     enum HourFormat {
-//         Hour12,
-//         Hour24,
-//     }
-
-//     #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
-//     struct JournalSettingsJson {
-//         pub path: Option<String>,
-//         pub hour_format: Option<HourFormat>,
-//     }
-
-//     impl Setting for JournalSettings {
-//         const KEY: Option<&'static str> = Some("journal");
-
-//         type FileContent = JournalSettingsJson;
-
-//         fn load(
-//             default_value: &JournalSettingsJson,
-//             user_values: &[&JournalSettingsJson],
-//             _: &AppContext,
-//         ) -> Result<Self> {
-//             Self::load_via_json_merge(default_value, user_values)
-//         }
-//     }
-
-//     #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-//     struct LanguageSettings {
-//         #[serde(default)]
-//         languages: HashMap<String, LanguageSettingEntry>,
-//     }
-
-//     #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
-//     struct LanguageSettingEntry {
-//         language_setting_1: Option<bool>,
-//         language_setting_2: Option<bool>,
-//     }
-
-//     impl Setting for LanguageSettings {
-//         const KEY: Option<&'static str> = None;
-
-//         type FileContent = Self;
-
-//         fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
-//             Self::load_via_json_merge(default_value, user_values)
-//         }
-//     }
-// }
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use serde_derive::Deserialize;
+    use unindent::Unindent;
+
+    #[gpui2::test]
+    fn test_settings_store_basic(cx: &mut AppContext) {
+        let mut store = SettingsStore::default();
+        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();
+
+        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(),
+            }
+        );
+
+        store
+            .set_user_settings(
+                r#"{
+                 "turbo": true,
+                 "user": { "age": 31 },
+                 "key1": "a"
+                 }"#,
+                cx,
+            )
+            .unwrap();
+
+        assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(true));
+        assert_eq!(
+            store.get::<UserSettings>(None),
+            &UserSettings {
+                name: "John Doe".to_string(),
+                age: 31,
+                staff: false
+            }
+        );
+
+        store
+            .set_local_settings(
+                1,
+                Path::new("/root1").into(),
+                Some(r#"{ "user": { "staff": true } }"#),
+                cx,
+            )
+            .unwrap();
+        store
+            .set_local_settings(
+                1,
+                Path::new("/root1/subdir").into(),
+                Some(r#"{ "user": { "name": "Jane Doe" } }"#),
+                cx,
+            )
+            .unwrap();
+
+        store
+            .set_local_settings(
+                1,
+                Path::new("/root2").into(),
+                Some(r#"{ "user": { "age": 42 }, "key2": "b" }"#),
+                cx,
+            )
+            .unwrap();
+
+        assert_eq!(
+            store.get::<UserSettings>(Some((1, Path::new("/root1/something")))),
+            &UserSettings {
+                name: "John Doe".to_string(),
+                age: 31,
+                staff: true
+            }
+        );
+        assert_eq!(
+            store.get::<UserSettings>(Some((1, Path::new("/root1/subdir/something")))),
+            &UserSettings {
+                name: "Jane Doe".to_string(),
+                age: 31,
+                staff: true
+            }
+        );
+        assert_eq!(
+            store.get::<UserSettings>(Some((1, Path::new("/root2/something")))),
+            &UserSettings {
+                name: "John Doe".to_string(),
+                age: 42,
+                staff: false
+            }
+        );
+        assert_eq!(
+            store.get::<MultiKeySettings>(Some((1, Path::new("/root2/something")))),
+            &MultiKeySettings {
+                key1: "a".to_string(),
+                key2: "b".to_string(),
+            }
+        );
+    }
+
+    #[gpui2::test]
+    fn test_setting_store_assign_json_before_register(cx: &mut AppContext) {
+        let mut store = SettingsStore::default();
+        store
+            .set_default_settings(
+                r#"{
+                     "turbo": true,
+                     "user": {
+                     "name": "John Doe",
+                     "age": 30,
+                     "staff": false
+                     },
+                     "key1": "x"
+                     }"#,
+                cx,
+            )
+            .unwrap();
+        store
+            .set_user_settings(r#"{ "turbo": false }"#, cx)
+            .unwrap();
+        store.register_setting::<UserSettings>(cx);
+        store.register_setting::<TurboSetting>(cx);
+
+        assert_eq!(store.get::<TurboSetting>(None), &TurboSetting(false));
+        assert_eq!(
+            store.get::<UserSettings>(None),
+            &UserSettings {
+                name: "John Doe".to_string(),
+                age: 30,
+                staff: false,
+            }
+        );
+
+        store.register_setting::<MultiKeySettings>(cx);
+        assert_eq!(
+            store.get::<MultiKeySettings>(None),
+            &MultiKeySettings {
+                key1: "x".into(),
+                key2: String::new(),
+            }
+        );
+    }
+
+    #[gpui2::test]
+    fn test_setting_store_update(cx: &mut AppContext) {
+        let mut store = SettingsStore::default();
+        store.register_setting::<MultiKeySettings>(cx);
+        store.register_setting::<UserSettings>(cx);
+        store.register_setting::<LanguageSettings>(cx);
+
+        // entries added and updated
+        check_settings_update::<LanguageSettings>(
+            &mut store,
+            r#"{
+                "languages": {
+                    "JSON": {
+                        "language_setting_1": true
+                    }
+                }
+            }"#
+            .unindent(),
+            |settings| {
+                settings
+                    .languages
+                    .get_mut("JSON")
+                    .unwrap()
+                    .language_setting_1 = Some(false);
+                settings.languages.insert(
+                    "Rust".into(),
+                    LanguageSettingEntry {
+                        language_setting_2: Some(true),
+                        ..Default::default()
+                    },
+                );
+            },
+            r#"{
+                "languages": {
+                    "Rust": {
+                        "language_setting_2": true
+                    },
+                    "JSON": {
+                        "language_setting_1": false
+                    }
+                }
+            }"#
+            .unindent(),
+            cx,
+        );
+
+        // weird formatting
+        check_settings_update::<UserSettings>(
+            &mut store,
+            r#"{
+                "user":   { "age": 36, "name": "Max", "staff": true }
+            }"#
+            .unindent(),
+            |settings| settings.age = Some(37),
+            r#"{
+                "user":   { "age": 37, "name": "Max", "staff": true }
+            }"#
+            .unindent(),
+            cx,
+        );
+
+        // single-line formatting, other keys
+        check_settings_update::<MultiKeySettings>(
+            &mut store,
+            r#"{ "one": 1, "two": 2 }"#.unindent(),
+            |settings| settings.key1 = Some("x".into()),
+            r#"{ "key1": "x", "one": 1, "two": 2 }"#.unindent(),
+            cx,
+        );
+
+        // empty object
+        check_settings_update::<UserSettings>(
+            &mut store,
+            r#"{
+                "user": {}
+            }"#
+            .unindent(),
+            |settings| settings.age = Some(37),
+            r#"{
+                "user": {
+                    "age": 37
+                }
+            }"#
+            .unindent(),
+            cx,
+        );
+
+        // no content
+        check_settings_update::<UserSettings>(
+            &mut store,
+            r#""#.unindent(),
+            |settings| settings.age = Some(37),
+            r#"{
+                "user": {
+                    "age": 37
+                }
+            }
+            "#
+            .unindent(),
+            cx,
+        );
+
+        check_settings_update::<UserSettings>(
+            &mut store,
+            r#"{
+                     }
+                     "#
+            .unindent(),
+            |settings| settings.age = Some(37),
+            r#"{
+                "user": {
+                    "age": 37
+                }
+            }
+            "#
+            .unindent(),
+            cx,
+        );
+    }
+
+    #[track_caller]
+    fn check_settings_update<T: Settings>(
+        store: &mut SettingsStore,
+        old_json: String,
+        update: fn(&mut T::FileContent),
+        expected_new_json: String,
+        cx: &mut AppContext,
+    ) {
+        store.set_user_settings(&old_json, cx).ok();
+        let edits = store.edits_for_update::<T>(&old_json, update);
+        let mut new_json = old_json;
+        for (range, replacement) in edits.into_iter() {
+            new_json.replace_range(range, &replacement);
+        }
+        pretty_assertions::assert_eq!(new_json, expected_new_json);
+    }
+
+    #[derive(Debug, PartialEq, Deserialize)]
+    struct UserSettings {
+        name: String,
+        age: u32,
+        staff: bool,
+    }
+
+    #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
+    struct UserSettingsJson {
+        name: Option<String>,
+        age: Option<u32>,
+        staff: Option<bool>,
+    }
+
+    impl Settings for UserSettings {
+        const KEY: Option<&'static str> = Some("user");
+        type FileContent = UserSettingsJson;
+
+        fn load(
+            default_value: &UserSettingsJson,
+            user_values: &[&UserSettingsJson],
+            _: &AppContext,
+        ) -> Result<Self> {
+            Self::load_via_json_merge(default_value, user_values)
+        }
+    }
+
+    #[derive(Debug, Deserialize, PartialEq)]
+    struct TurboSetting(bool);
+
+    impl Settings for TurboSetting {
+        const KEY: Option<&'static str> = Some("turbo");
+        type FileContent = Option<bool>;
+
+        fn load(
+            default_value: &Option<bool>,
+            user_values: &[&Option<bool>],
+            _: &AppContext,
+        ) -> Result<Self> {
+            Self::load_via_json_merge(default_value, user_values)
+        }
+    }
+
+    #[derive(Clone, Debug, PartialEq, Deserialize)]
+    struct MultiKeySettings {
+        #[serde(default)]
+        key1: String,
+        #[serde(default)]
+        key2: String,
+    }
+
+    #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+    struct MultiKeySettingsJson {
+        key1: Option<String>,
+        key2: Option<String>,
+    }
+
+    impl Settings for MultiKeySettings {
+        const KEY: Option<&'static str> = None;
+
+        type FileContent = MultiKeySettingsJson;
+
+        fn load(
+            default_value: &MultiKeySettingsJson,
+            user_values: &[&MultiKeySettingsJson],
+            _: &AppContext,
+        ) -> Result<Self> {
+            Self::load_via_json_merge(default_value, user_values)
+        }
+    }
+
+    #[derive(Debug, Deserialize)]
+    struct JournalSettings {
+        pub path: String,
+        pub hour_format: HourFormat,
+    }
+
+    #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
+    #[serde(rename_all = "snake_case")]
+    enum HourFormat {
+        Hour12,
+        Hour24,
+    }
+
+    #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)]
+    struct JournalSettingsJson {
+        pub path: Option<String>,
+        pub hour_format: Option<HourFormat>,
+    }
+
+    impl Settings for JournalSettings {
+        const KEY: Option<&'static str> = Some("journal");
+
+        type FileContent = JournalSettingsJson;
+
+        fn load(
+            default_value: &JournalSettingsJson,
+            user_values: &[&JournalSettingsJson],
+            _: &AppContext,
+        ) -> Result<Self> {
+            Self::load_via_json_merge(default_value, user_values)
+        }
+    }
+
+    #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+    struct LanguageSettings {
+        #[serde(default)]
+        languages: HashMap<String, LanguageSettingEntry>,
+    }
+
+    #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+    struct LanguageSettingEntry {
+        language_setting_1: Option<bool>,
+        language_setting_2: Option<bool>,
+    }
+
+    impl Settings for LanguageSettings {
+        const KEY: Option<&'static str> = None;
+
+        type FileContent = Self;
+
+        fn load(default_value: &Self, user_values: &[&Self], _: &AppContext) -> Result<Self> {
+            Self::load_via_json_merge(default_value, user_values)
+        }
+    }
+}

crates/theme2/src/settings.rs 🔗

@@ -8,7 +8,7 @@ use schemars::{
 };
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
-use settings2::{Settings, SettingsJsonSchemaParams};
+use settings2::SettingsJsonSchemaParams;
 use std::sync::Arc;
 use util::ResultExt as _;
 

crates/theme2/src/theme2.rs 🔗

@@ -6,7 +6,6 @@ pub use registry::*;
 pub use settings::*;
 
 use gpui2::{AppContext, HighlightStyle, Hsla, SharedString};
-use settings2::Settings;
 use std::sync::Arc;
 
 pub fn init(cx: &mut AppContext) {