From 947fa3cfc845a6be66376943245b4ae49c6799c2 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 15 Sep 2025 16:49:01 -0600 Subject: [PATCH] VSCode import tests Co-authored-by: Ben Kunkle --- crates/settings/src/base_keymap_setting.rs | 2 +- crates/settings/src/settings_store.rs | 556 +++++++-------------- crates/settings/src/vscode_import.rs | 7 +- crates/theme/src/settings.rs | 2 +- crates/title_bar/src/title_bar_settings.rs | 2 +- 5 files changed, 182 insertions(+), 387 deletions(-) diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index f02ee49e7c778bfec604b5af9e7410de73eb9e01..b7ac08f620d1aba93178c8cd74e3a271a8689a82 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -139,7 +139,7 @@ pub struct BaseKeymapSetting { } impl Settings for BaseKeymap { - fn from_file(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Option { + fn from_default(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Option { s.base_keymap.map(Into::into) } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 92213dc10fe35805d035e76d7952603cd5b1aa1a..6e4f0aff1fa723b086154b69f18688514ff2f168 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -58,7 +58,7 @@ pub trait Settings: 'static + Send + Sync + Sized { /// user settings match the current version of the settings. const PRESERVED_KEYS: Option<&'static [&'static str]> = None; - fn from_file(content: &SettingsContent, cx: &mut App) -> Option; + fn from_default(content: &SettingsContent, cx: &mut App) -> Option; fn refine(&mut self, content: &SettingsContent, cx: &mut App); @@ -322,6 +322,10 @@ impl SettingsStore { refinements.push(extension_settings) } + if let Some(global_settings) = self.global_settings.as_ref() { + refinements.push(global_settings) + } + if let Some(user_settings) = self.user_settings.as_ref() { refinements.push(&user_settings.content); if let Some(release_channel) = user_settings.for_release_channel() { @@ -338,8 +342,12 @@ impl SettingsStore { if let Some(server_settings) = self.server_settings.as_ref() { refinements.push(server_settings) } - // todo!() unwrap... - let mut value = T::from_file(&self.default_settings, cx).unwrap(); + let Some(mut value) = T::from_default(&self.default_settings, cx) else { + panic!( + "{}::from_file return None for default.json", + type_name::() + ) + }; for refinement in refinements { value.refine(refinement, cx) } @@ -903,6 +911,10 @@ impl SettingsStore { refinements.push(extension_settings) } + if let Some(global_settings) = self.global_settings.as_ref() { + refinements.push(global_settings) + } + if let Some(user_settings) = self.user_settings.as_ref() { refinements.push(&user_settings.content); if let Some(release_channel) = user_settings.for_release_channel() { @@ -1040,7 +1052,7 @@ impl Debug for SettingsStore { impl AnySettingValue for SettingValue { fn from_file(&self, s: &SettingsContent, cx: &mut App) -> Option> { - T::from_file(s, cx).map(|result| Box::new(result) as _) + T::from_default(s, cx).map(|result| Box::new(result) as _) } fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) { @@ -1109,7 +1121,7 @@ mod tests { use std::num::NonZeroU32; use crate::{ - TitleBarSettingsContent, TitleBarVisibilityContent, default_settings, + TitleBarSettingsContent, TitleBarVisibilityContent, VsCodeSettingsSource, default_settings, settings_content::LanguageSettingsContent, test_settings, }; @@ -1123,7 +1135,7 @@ mod tests { } impl Settings for AutoUpdateSetting { - fn from_file(content: &SettingsContent, _: &mut App) -> Option { + fn from_default(content: &SettingsContent, _: &mut App) -> Option { content .auto_update .map(|auto_update| AutoUpdateSetting { auto_update }) @@ -1145,7 +1157,7 @@ mod tests { } impl Settings for TitleBarSettings { - fn from_file(content: &SettingsContent, _: &mut App) -> Option { + fn from_default(content: &SettingsContent, _: &mut App) -> Option { let content = content.title_bar.clone()?; Some(TitleBarSettings { show: content.show?, @@ -1160,7 +1172,18 @@ mod tests { self.show.merge_from(&content.show) } - fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {} + fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { + let mut show = None; + + vscode.enum_setting("window.titleBarStyle", &mut show, |value| match value { + "never" => Some(TitleBarVisibilityContent::Never), + "always" => Some(TitleBarVisibilityContent::Always), + _ => None, + }); + if let Some(show) = show { + content.title_bar.get_or_insert_default().show.replace(show); + } + } } #[derive(Debug, PartialEq)] @@ -1170,7 +1193,7 @@ mod tests { } impl Settings for DefaultLanguageSettings { - fn from_file(content: &SettingsContent, _: &mut App) -> Option { + fn from_default(content: &SettingsContent, _: &mut App) -> Option { let content = &content.project.all_languages.defaults; Some(DefaultLanguageSettings { tab_size: content.tab_size?, @@ -1185,7 +1208,17 @@ mod tests { .merge_from(&content.preferred_line_length); } - fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {} + fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { + let content = &mut content.project.all_languages.defaults; + + if let Some(size) = vscode + .read_value("editor.tabSize") + .and_then(|v| v.as_u64()) + .and_then(|n| NonZeroU32::new(n as u32)) + { + content.tab_size = Some(size); + } + } } #[gpui::test] @@ -1337,9 +1370,6 @@ mod tests { #[gpui::test] fn test_setting_store_update(cx: &mut App) { let mut store = SettingsStore::new(cx, &test_settings()); - // store.register_setting::(cx); - // store.register_setting::(cx); - // store.register_setting::(cx); // entries added and updated check_settings_update( @@ -1519,381 +1549,151 @@ mod tests { ); } - // #[gpui::test] - // fn test_vscode_import(cx: &mut App) { - // let mut store = SettingsStore::new(cx); - // store.register_setting::(cx); - // store.register_setting::(cx); - // store.register_setting::(cx); - // store.register_setting::(cx); - - // // create settings that werent present - // check_vscode_import( - // &mut store, - // r#"{ - // } - // "# - // .unindent(), - // r#" { "user.age": 37 } "#.to_owned(), - // r#"{ - // "user": { - // "age": 37 - // } - // } - // "# - // .unindent(), - // cx, - // ); - - // // persist settings that were present - // check_vscode_import( - // &mut store, - // r#"{ - // "user": { - // "staff": true, - // "age": 37 - // } - // } - // "# - // .unindent(), - // r#"{ "user.age": 42 }"#.to_owned(), - // r#"{ - // "user": { - // "staff": true, - // "age": 42 - // } - // } - // "# - // .unindent(), - // cx, - // ); - - // // don't clobber settings that aren't present in vscode - // check_vscode_import( - // &mut store, - // r#"{ - // "user": { - // "staff": true, - // "age": 37 - // } - // } - // "# - // .unindent(), - // r#"{}"#.to_owned(), - // r#"{ - // "user": { - // "staff": true, - // "age": 37 - // } - // } - // "# - // .unindent(), - // cx, - // ); - - // // custom enum - // check_vscode_import( - // &mut store, - // r#"{ - // "journal": { - // "hour_format": "hour12" - // } - // } - // "# - // .unindent(), - // r#"{ "time_format": "24" }"#.to_owned(), - // r#"{ - // "journal": { - // "hour_format": "hour24" - // } - // } - // "# - // .unindent(), - // cx, - // ); - - // // Multiple keys for one setting - // check_vscode_import( - // &mut store, - // r#"{ - // "key1": "value" - // } - // "# - // .unindent(), - // r#"{ - // "key_1_first": "hello", - // "key_1_second": "world" - // }"# - // .to_owned(), - // r#"{ - // "key1": "hello world" - // } - // "# - // .unindent(), - // cx, - // ); - - // // Merging lists together entries added and updated - // check_vscode_import( - // &mut store, - // r#"{ - // "languages": { - // "JSON": { - // "language_setting_1": true - // }, - // "Rust": { - // "language_setting_2": true - // } - // } - // }"# - // .unindent(), - // r#"{ - // "vscode_languages": [ - // { - // "name": "JavaScript", - // "language_setting_1": true - // }, - // { - // "name": "Rust", - // "language_setting_2": false - // } - // ] - // }"# - // .to_owned(), - // r#"{ - // "languages": { - // "JavaScript": { - // "language_setting_1": true - // }, - // "JSON": { - // "language_setting_1": true - // }, - // "Rust": { - // "language_setting_2": false - // } - // } - // }"# - // .unindent(), - // cx, - // ); - // } - - // fn check_vscode_import( - // store: &mut SettingsStore, - // old: String, - // vscode: String, - // expected: String, - // cx: &mut App, - // ) { - // store.set_user_settings(&old, cx).ok(); - // let new = store.get_vscode_edits( - // old, - // &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(), - // ); - // pretty_assertions::assert_eq!(new, expected); - // } - - // #[derive(Debug, PartialEq, Deserialize, SettingsUi)] - // struct UserSettings { - // name: String, - // age: u32, - // staff: bool, - // } - - // #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] - // #[settings_key(key = "user")] - // struct UserSettingsContent { - // name: Option, - // age: Option, - // staff: Option, - // } - - // impl Settings for UserSettings { - // type FileContent = UserSettingsContent; - - // fn load(sources: SettingsSources, _: &mut App) -> Result { - // sources.json_merge() - // } - - // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - // vscode.u32_setting("user.age", &mut current.age); - // } - // } - - // #[derive(Debug, Deserialize, PartialEq)] - // struct TurboSetting(bool); - - // #[derive( - // Copy, - // Clone, - // PartialEq, - // Eq, - // Debug, - // Default, - // serde::Serialize, - // serde::Deserialize, - // SettingsUi, - // SettingsKey, - // JsonSchema, - // )] - // #[serde(default)] - // #[settings_key(None)] - // pub struct TurboSettingContent { - // turbo: Option, - // } - - // impl Settings for TurboSetting { - // type FileContent = TurboSettingContent; - - // fn load(sources: SettingsSources, _: &mut App) -> Result { - // Ok(Self( - // sources - // .user - // .or(sources.server) - // .unwrap_or(sources.default) - // .turbo - // .unwrap_or_default(), - // )) - // } - - // fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {} - // } - - // #[derive(Clone, Debug, PartialEq, Deserialize)] - // struct MultiKeySettings { - // #[serde(default)] - // key1: String, - // #[serde(default)] - // key2: String, - // } - - // #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] - // #[settings_key(None)] - // struct MultiKeySettingsJson { - // key1: Option, - // key2: Option, - // } - - // impl Settings for MultiKeySettings { - // type FileContent = MultiKeySettingsJson; + #[gpui::test] + fn test_vscode_import(cx: &mut App) { + let mut store = SettingsStore::new(cx, &test_settings()); + store.register_setting::(cx); + store.register_setting::(cx); + store.register_setting::(cx); - // fn load(sources: SettingsSources, _: &mut App) -> Result { - // sources.json_merge() - // } + // create settings that werent present + check_vscode_import( + &mut store, + r#"{ + } + "# + .unindent(), + r#" { "editor.tabSize": 37 } "#.to_owned(), + r#"{ + "tab_size": 37 + } + "# + .unindent(), + cx, + ); - // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - // let first_value = vscode.read_string("key_1_first"); - // let second_value = vscode.read_string("key_1_second"); + // persist settings that were present + check_vscode_import( + &mut store, + r#"{ + "preferred_line_length": 99, + } + "# + .unindent(), + r#"{ "editor.tabSize": 42 }"#.to_owned(), + r#"{ + "tab_size": 42, + "preferred_line_length": 99, + } + "# + .unindent(), + cx, + ); - // if let Some((first, second)) = first_value.zip(second_value) { - // current.key1 = Some(format!("{} {}", first, second)); - // } - // } - // } + // don't clobber settings that aren't present in vscode + check_vscode_import( + &mut store, + r#"{ + "preferred_line_length": 99, + "tab_size": 42 + } + "# + .unindent(), + r#"{}"#.to_owned(), + r#"{ + "preferred_line_length": 99, + "tab_size": 42 + } + "# + .unindent(), + cx, + ); - // #[derive(Debug, Deserialize)] - // struct JournalSettings { - // #[expect(unused)] - // pub path: String, - // #[expect(unused)] - // pub hour_format: HourFormat, - // } + // custom enum + check_vscode_import( + &mut store, + r#"{ + "title_bar": { + "show": "always" + } + } + "# + .unindent(), + r#"{ "window.titleBarStyle": "never" }"#.to_owned(), + r#"{ + "title_bar": { + "show": "never" + } + } + "# + .unindent(), + cx, + ); + } - // #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] - // #[serde(rename_all = "snake_case")] - // enum HourFormat { - // Hour12, - // Hour24, - // } + #[track_caller] + fn check_vscode_import( + store: &mut SettingsStore, + old: String, + vscode: String, + expected: String, + cx: &mut App, + ) { + store.set_user_settings(&old, cx).ok(); + let new = store.get_vscode_edits( + old, + &VsCodeSettings::from_str(&vscode, VsCodeSettingsSource::VsCode).unwrap(), + ); + pretty_assertions::assert_eq!(new, expected); + } - // #[derive( - // Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey, - // )] - // #[settings_key(key = "journal")] - // struct JournalSettingsJson { - // pub path: Option, - // pub hour_format: Option, - // } + #[gpui::test] + fn test_global_settings(cx: &mut App) { + let mut store = SettingsStore::new(cx, &test_settings()); + store.register_setting::(cx); - // impl Settings for JournalSettings { - // type FileContent = JournalSettingsJson; + // Set global settings - these should override defaults but not user settings + store + .set_global_settings( + r#"{ + "title_bar": { + "show": "never", + } + }"#, + cx, + ) + .unwrap(); - // fn load(sources: SettingsSources, _: &mut App) -> Result { - // sources.json_merge() - // } + // Before user settings, global settings should apply + assert_eq!( + store.get::(None), + &TitleBarSettings { + show: TitleBarVisibilityContent::Never, + show_branch_name: true, + } + ); - // fn import_from_vscode(vscode: &VsCodeSettings, current: &mut Self::FileContent) { - // vscode.enum_setting("time_format", &mut current.hour_format, |s| match s { - // "12" => Some(HourFormat::Hour12), - // "24" => Some(HourFormat::Hour24), - // _ => None, - // }); - // } - // } + // Set user settings - these should override both defaults and global + store + .set_user_settings( + r#"{ + "title_bar": { + "show": "always" + } + }"#, + cx, + ) + .unwrap(); - // #[gpui::test] - // fn test_global_settings(cx: &mut App) { - // let mut store = SettingsStore::new(cx); - // store.register_setting::(cx); - // store - // .set_default_settings( - // r#"{ - // "user": { - // "name": "John Doe", - // "age": 30, - // "staff": false - // } - // }"#, - // cx, - // ) - // .unwrap(); - - // // Set global settings - these should override defaults but not user settings - // store - // .set_global_settings( - // r#"{ - // "user": { - // "name": "Global User", - // "age": 35, - // "staff": true - // } - // }"#, - // cx, - // ) - // .unwrap(); - - // // Before user settings, global settings should apply - // assert_eq!( - // store.get::(None), - // &UserSettings { - // name: "Global User".to_string(), - // age: 35, - // staff: true, - // } - // ); - - // // Set user settings - these should override both defaults and global - // store - // .set_user_settings( - // r#"{ - // "user": { - // "age": 40 - // } - // }"#, - // cx, - // ) - // .unwrap(); - - // // User settings should override global settings - // assert_eq!( - // store.get::(None), - // &UserSettings { - // name: "Global User".to_string(), // Name from global settings - // age: 40, // Age from user settings - // staff: true, // Staff from global settings - // } - // ); - // } + // User settings should override global settings + assert_eq!( + store.get::(None), + &TitleBarSettings { + show: TitleBarVisibilityContent::Always, + show_branch_name: true, // Staff from global settings + } + ); + } // #[derive( // Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey, diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 53fbf797c3d9e56e49b1d96e7dabcac19ddde8e2..5792dcc12bd687faf50c91530f027b1f90d7ff92 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -78,12 +78,7 @@ impl VsCodeSettings { } pub fn read_value(&self, setting: &str) -> Option<&Value> { - if let Some(value) = self.content.get(setting) { - return Some(value); - } - // TODO: maybe check if it's in [platform] settings for current platform as a fallback - // TODO: deal with language specific settings - None + self.content.get(setting) } pub fn read_string(&self, setting: &str) -> Option<&str> { diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 568e9e1044ea281f1305864a18329466735f6102..71841a130410265ac45d03a284e2334746b5c0ca 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -796,7 +796,7 @@ fn font_fallbacks_from_settings( } impl settings::Settings for ThemeSettings { - fn from_file(content: &settings::SettingsContent, cx: &mut App) -> Option { + fn from_default(content: &settings::SettingsContent, cx: &mut App) -> Option { let content = &content.theme; let themes = ThemeRegistry::default_global(cx); let system_appearance = SystemAppearance::default_global(cx); diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 01939211c4f3c40dbeb17bbd0b9dd62a2247e723..9d0d8ff511a9315b2fc68bfc1fe3e346e836e6d4 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -24,7 +24,7 @@ pub struct TitleBarSettings { } impl Settings for TitleBarSettings { - fn from_file(s: &SettingsContent) -> Option { + fn from_default(s: &SettingsContent) -> Option { let content = s.title_bar?; TitleBarSettings { show: content.show?,