From 4f8ff64452ea33a97e3c990ba47cc6bbf12b259d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Fri, 6 Feb 2026 11:50:36 -0500 Subject: [PATCH] Fix settings migrations for nested platform/channel/profile keys (#48550) Previously, some settings migrations only operated on root-level keys and missed settings nested under platform keys (`macos`, `linux`, etc.), channel keys (`nightly`, `stable`, etc.), or profile blocks. This fixes migrations to recurse into those nested locations. Also fixes `m_2026_02_02` to gracefully skip when `edit_predictions` is not an object (e.g. `true`) instead of bailing and aborting the entire migration chain. Release Notes: - Fixed settings migrations to correctly handle settings nested under platform, channel, or profile keys. --- Cargo.lock | 1 + crates/migrator/Cargo.toml | 1 + crates/migrator/src/migrations.rs | 109 ++++ .../src/migrations/m_2025_10_01/settings.rs | 2 +- .../src/migrations/m_2025_10_02/settings.rs | 2 +- .../src/migrations/m_2025_10_16/settings.rs | 2 +- .../src/migrations/m_2025_10_17/settings.rs | 8 +- .../src/migrations/m_2025_10_21/settings.rs | 8 +- .../src/migrations/m_2025_11_25/settings.rs | 20 +- .../src/migrations/m_2026_02_02/settings.rs | 8 +- .../src/migrations/m_2026_02_03/settings.rs | 13 +- crates/migrator/src/migrator.rs | 469 ++++++++++++++++++ crates/migrator/src/patterns.rs | 1 - crates/migrator/src/patterns/settings.rs | 21 - crates/settings/src/settings.rs | 17 +- .../settings_content/src/settings_content.rs | 54 +- 16 files changed, 673 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3ea3eb05237ff2fd40ad615a1d64e6bc407e5f57..9c6093504f4e6088bd98fd92930236181f89441b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10137,6 +10137,7 @@ dependencies = [ "pretty_assertions", "serde_json", "serde_json_lenient", + "settings_content", "settings_json", "streaming-iterator", "tree-sitter", diff --git a/crates/migrator/Cargo.toml b/crates/migrator/Cargo.toml index e0a75784749c2d3a2a981b44cbbe449a7685c605..cb97f7fb6ced682443fa703846ea633e8f654736 100644 --- a/crates/migrator/Cargo.toml +++ b/crates/migrator/Cargo.toml @@ -22,6 +22,7 @@ tree-sitter-json.workspace = true tree-sitter.workspace = true serde_json_lenient.workspace = true serde_json.workspace = true +settings_content.workspace = true settings_json.workspace = true [dev-dependencies] diff --git a/crates/migrator/src/migrations.rs b/crates/migrator/src/migrations.rs index 6fbb29d155f1fc613d1f33f54787922e3d4cc01a..66b7131a06637036c71149957dead64e3ef4e399 100644 --- a/crates/migrator/src/migrations.rs +++ b/crates/migrator/src/migrations.rs @@ -1,3 +1,112 @@ +use anyhow::Result; +use serde_json::Value; +use settings_content::{PlatformOverrides, ReleaseChannelOverrides}; + +/// Applies a migration callback to the root settings object as well as all +/// nested platform, release-channel, and profile override objects. +pub(crate) fn migrate_settings( + value: &mut Value, + mut migrate_one: impl FnMut(&mut serde_json::Map) -> Result<()>, +) -> Result<()> { + let Some(root_object) = value.as_object_mut() else { + return Ok(()); + }; + + migrate_one(root_object)?; + + let override_keys = ReleaseChannelOverrides::OVERRIDE_KEYS + .iter() + .copied() + .chain(PlatformOverrides::OVERRIDE_KEYS.iter().copied()); + + for key in override_keys { + if let Some(sub_object) = root_object.get_mut(key) { + if let Some(sub_map) = sub_object.as_object_mut() { + migrate_one(sub_map)?; + } + } + } + + if let Some(profiles) = root_object.get_mut("profiles") { + if let Some(profiles_object) = profiles.as_object_mut() { + for (_profile_name, profile_settings) in profiles_object.iter_mut() { + if let Some(profile_map) = profile_settings.as_object_mut() { + migrate_one(profile_map)?; + } + } + } + } + + Ok(()) +} + +/// Applies a migration callback to a value and its `languages` children, +/// at the root level as well as all nested platform, release-channel, and +/// profile override objects. +pub(crate) fn migrate_language_setting( + value: &mut Value, + migrate_fn: fn(&mut Value, path: &[&str]) -> Result<()>, +) -> Result<()> { + fn apply_to_value_and_languages( + value: &mut Value, + prefix: &[&str], + migrate_fn: fn(&mut Value, path: &[&str]) -> Result<()>, + ) -> Result<()> { + migrate_fn(value, prefix)?; + let languages = value + .as_object_mut() + .and_then(|obj| obj.get_mut("languages")) + .and_then(|languages| languages.as_object_mut()); + if let Some(languages) = languages { + for (language_name, language) in languages.iter_mut() { + let mut path: Vec<&str> = prefix.to_vec(); + path.push("languages"); + path.push(language_name); + migrate_fn(language, &path)?; + } + } + Ok(()) + } + + if !value.is_object() { + return Ok(()); + } + + apply_to_value_and_languages(value, &[], migrate_fn)?; + + let Some(root_object) = value.as_object_mut() else { + return Ok(()); + }; + + let override_keys = ReleaseChannelOverrides::OVERRIDE_KEYS + .iter() + .copied() + .chain(PlatformOverrides::OVERRIDE_KEYS.iter().copied()); + + for key in override_keys { + if let Some(sub_value) = root_object.get_mut(key) { + apply_to_value_and_languages(sub_value, &[key], migrate_fn)?; + } + } + + if let Some(profiles) = root_object.get_mut("profiles") { + if let Some(profiles_object) = profiles.as_object_mut() { + let profile_names: Vec = profiles_object.keys().cloned().collect(); + for profile_name in &profile_names { + if let Some(profile_settings) = profiles_object.get_mut(profile_name.as_str()) { + apply_to_value_and_languages( + profile_settings, + &["profiles", profile_name], + migrate_fn, + )?; + } + } + } + } + + Ok(()) +} + pub(crate) mod m_2025_01_02 { mod settings; diff --git a/crates/migrator/src/migrations/m_2025_10_01/settings.rs b/crates/migrator/src/migrations/m_2025_10_01/settings.rs index 84cf95049154b44048e92982fd00a11a3514bc16..c0230070245493a0ed2379e940121a13ed292a75 100644 --- a/crates/migrator/src/migrations/m_2025_10_01/settings.rs +++ b/crates/migrator/src/migrations/m_2025_10_01/settings.rs @@ -1,4 +1,4 @@ -use crate::patterns::migrate_language_setting; +use crate::migrations::migrate_language_setting; use anyhow::Result; use serde_json::Value; diff --git a/crates/migrator/src/migrations/m_2025_10_02/settings.rs b/crates/migrator/src/migrations/m_2025_10_02/settings.rs index cb0d63ca8570952818e74e021f5dd2edc2523786..8942008e63219b9b8f529d979af502547f1a68ac 100644 --- a/crates/migrator/src/migrations/m_2025_10_02/settings.rs +++ b/crates/migrator/src/migrations/m_2025_10_02/settings.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde_json::Value; -use crate::patterns::migrate_language_setting; +use crate::migrations::migrate_language_setting; pub fn remove_formatters_on_save(value: &mut Value) -> Result<()> { migrate_language_setting(value, remove_formatters_on_save_inner) diff --git a/crates/migrator/src/migrations/m_2025_10_16/settings.rs b/crates/migrator/src/migrations/m_2025_10_16/settings.rs index 3fa8c509b1f3910f48603a10a0fd0f448992c151..c3d60f7db481d28885d14754cdcdaca28df226d7 100644 --- a/crates/migrator/src/migrations/m_2025_10_16/settings.rs +++ b/crates/migrator/src/migrations/m_2025_10_16/settings.rs @@ -1,7 +1,7 @@ use anyhow::Result; use serde_json::Value; -use crate::patterns::migrate_language_setting; +use crate::migrations::migrate_language_setting; pub fn restore_code_actions_on_format(value: &mut Value) -> Result<()> { migrate_language_setting(value, restore_code_actions_on_format_inner) diff --git a/crates/migrator/src/migrations/m_2025_10_17/settings.rs b/crates/migrator/src/migrations/m_2025_10_17/settings.rs index 519ec740346ed5cb954477b2ae4f0cff341a21b2..21b73eaa037eb4bc9ef54eed2ddf4e839a8fdf29 100644 --- a/crates/migrator/src/migrations/m_2025_10_17/settings.rs +++ b/crates/migrator/src/migrations/m_2025_10_17/settings.rs @@ -1,8 +1,14 @@ use anyhow::Result; use serde_json::Value; +use crate::migrations::migrate_settings; + pub fn make_file_finder_include_ignored_an_enum(value: &mut Value) -> Result<()> { - let Some(file_finder) = value.get_mut("file_finder") else { + migrate_settings(value, migrate_one) +} + +fn migrate_one(obj: &mut serde_json::Map) -> Result<()> { + let Some(file_finder) = obj.get_mut("file_finder") else { return Ok(()); }; diff --git a/crates/migrator/src/migrations/m_2025_10_21/settings.rs b/crates/migrator/src/migrations/m_2025_10_21/settings.rs index 1f78f9332741a50f851006c525863e51abc94784..90ec730956aace0ab3390575bd5b3a400956f652 100644 --- a/crates/migrator/src/migrations/m_2025_10_21/settings.rs +++ b/crates/migrator/src/migrations/m_2025_10_21/settings.rs @@ -1,8 +1,14 @@ use anyhow::Result; use serde_json::Value; +use crate::migrations::migrate_settings; + pub fn make_relative_line_numbers_an_enum(value: &mut Value) -> Result<()> { - let Some(relative_line_numbers) = value.get_mut("relative_line_numbers") else { + migrate_settings(value, migrate_one) +} + +fn migrate_one(obj: &mut serde_json::Map) -> Result<()> { + let Some(relative_line_numbers) = obj.get_mut("relative_line_numbers") else { return Ok(()); }; diff --git a/crates/migrator/src/migrations/m_2025_11_25/settings.rs b/crates/migrator/src/migrations/m_2025_11_25/settings.rs index 944eee8a119714b7d9839e2ddf13ec61db4c18d2..fa37a301915e82ec2798a8821076f32d2c7c450c 100644 --- a/crates/migrator/src/migrations/m_2025_11_25/settings.rs +++ b/crates/migrator/src/migrations/m_2025_11_25/settings.rs @@ -1,14 +1,18 @@ use anyhow::Result; use serde_json::Value; -pub fn remove_context_server_source(settings: &mut Value) -> Result<()> { - if let Some(obj) = settings.as_object_mut() { - if let Some(context_servers) = obj.get_mut("context_servers") { - if let Some(servers) = context_servers.as_object_mut() { - for (_, server) in servers.iter_mut() { - if let Some(server_obj) = server.as_object_mut() { - server_obj.remove("source"); - } +use crate::migrations::migrate_settings; + +pub fn remove_context_server_source(value: &mut Value) -> Result<()> { + migrate_settings(value, migrate_one) +} + +fn migrate_one(obj: &mut serde_json::Map) -> Result<()> { + if let Some(context_servers) = obj.get_mut("context_servers") { + if let Some(servers) = context_servers.as_object_mut() { + for (_, server) in servers.iter_mut() { + if let Some(server_obj) = server.as_object_mut() { + server_obj.remove("source"); } } } diff --git a/crates/migrator/src/migrations/m_2026_02_02/settings.rs b/crates/migrator/src/migrations/m_2026_02_02/settings.rs index f28f34969172201245f2d20349855c0e2b8e54ae..53ef04aa5bc3eb54b4c3cfeb2905532d2a80c1d1 100644 --- a/crates/migrator/src/migrations/m_2026_02_02/settings.rs +++ b/crates/migrator/src/migrations/m_2026_02_02/settings.rs @@ -1,11 +1,13 @@ use anyhow::Result; use serde_json::Value; +use crate::migrations::migrate_settings; + pub fn move_edit_prediction_provider_to_edit_predictions(value: &mut Value) -> Result<()> { - let Some(obj) = value.as_object_mut() else { - return Ok(()); - }; + migrate_settings(value, migrate_one) +} +fn migrate_one(obj: &mut serde_json::Map) -> Result<()> { let Some(features) = obj.get_mut("features") else { return Ok(()); }; diff --git a/crates/migrator/src/migrations/m_2026_02_03/settings.rs b/crates/migrator/src/migrations/m_2026_02_03/settings.rs index ff0b626730a3b29c5b60fbfc1095c57d2c492359..49aed219d814488660a671e813bf4042c95d0824 100644 --- a/crates/migrator/src/migrations/m_2026_02_03/settings.rs +++ b/crates/migrator/src/migrations/m_2026_02_03/settings.rs @@ -1,11 +1,16 @@ use anyhow::Result; use serde_json::Value; +use crate::migrations::migrate_settings; + pub fn migrate_experimental_sweep_mercury(value: &mut Value) -> Result<()> { - let Some(obj) = value.as_object_mut() else { - return Ok(()); - }; + migrate_settings(value, |obj| { + migrate_one(obj); + Ok(()) + }) +} +fn migrate_one(obj: &mut serde_json::Map) { if let Some(edit_predictions) = obj.get_mut("edit_predictions") { if let Some(edit_predictions_obj) = edit_predictions.as_object_mut() { migrate_provider_field(edit_predictions_obj, "provider"); @@ -17,8 +22,6 @@ pub fn migrate_experimental_sweep_mercury(value: &mut Value) -> Result<()> { migrate_provider_field(features_obj, "edit_prediction_provider"); } } - - Ok(()) } fn migrate_provider_field(obj: &mut serde_json::Map, field_name: &str) { diff --git a/crates/migrator/src/migrator.rs b/crates/migrator/src/migrator.rs index 6219a5caed68ccd14913a3bce83d8c6cb0f19ae9..e9892e28c05768fa4e0384810d9ad891b8723515 100644 --- a/crates/migrator/src/migrator.rs +++ b/crates/migrator/src/migrator.rs @@ -2219,6 +2219,165 @@ mod tests { .unindent(), ), ); + + // Platform key: settings nested inside "linux" should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum, + )], + &r#" + { + "linux": { + "file_finder": { + "include_ignored": true + } + } + } + "# + .unindent(), + Some( + &r#" + { + "linux": { + "file_finder": { + "include_ignored": "all" + } + } + } + "# + .unindent(), + ), + ); + + // Profile: settings nested inside profiles should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_17::make_file_finder_include_ignored_an_enum, + )], + &r#" + { + "profiles": { + "work": { + "file_finder": { + "include_ignored": false + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "profiles": { + "work": { + "file_finder": { + "include_ignored": "indexed" + } + } + } + } + "# + .unindent(), + ), + ); + } + + #[test] + fn test_make_relative_line_numbers_an_enum() { + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_21::make_relative_line_numbers_an_enum, + )], + &r#"{ }"#.unindent(), + None, + ); + + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_21::make_relative_line_numbers_an_enum, + )], + &r#"{ + "relative_line_numbers": true + }"# + .unindent(), + Some( + &r#"{ + "relative_line_numbers": "enabled" + }"# + .unindent(), + ), + ); + + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_21::make_relative_line_numbers_an_enum, + )], + &r#"{ + "relative_line_numbers": false + }"# + .unindent(), + Some( + &r#"{ + "relative_line_numbers": "disabled" + }"# + .unindent(), + ), + ); + + // Platform key: settings nested inside "macos" should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_21::make_relative_line_numbers_an_enum, + )], + &r#" + { + "macos": { + "relative_line_numbers": true + } + } + "# + .unindent(), + Some( + &r#" + { + "macos": { + "relative_line_numbers": "enabled" + } + } + "# + .unindent(), + ), + ); + + // Profile: settings nested inside profiles should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_10_21::make_relative_line_numbers_an_enum, + )], + &r#" + { + "profiles": { + "dev": { + "relative_line_numbers": false + } + } + } + "# + .unindent(), + Some( + &r#" + { + "profiles": { + "dev": { + "relative_line_numbers": "disabled" + } + } + } + "# + .unindent(), + ), + ); } #[test] @@ -2267,6 +2426,84 @@ mod tests { .unindent(), ), ); + + // Platform key: settings nested inside "linux" should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_11_25::remove_context_server_source, + )], + &r#" + { + "linux": { + "context_servers": { + "my_server": { + "source": "extension", + "settings": { + "key": "value" + } + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "linux": { + "context_servers": { + "my_server": { + "settings": { + "key": "value" + } + } + } + } + } + "# + .unindent(), + ), + ); + + // Profile: settings nested inside profiles should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2025_11_25::remove_context_server_source, + )], + &r#" + { + "profiles": { + "work": { + "context_servers": { + "my_server": { + "source": "custom", + "command": "foo", + "args": ["bar"] + } + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "profiles": { + "work": { + "context_servers": { + "my_server": { + "command": "foo", + "args": ["bar"] + } + } + } + } + } + "# + .unindent(), + ), + ); } #[test] @@ -2470,6 +2707,117 @@ mod tests { .unindent(), None, ); + + // Platform key: settings nested inside "macos" should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions, + )], + &r#" + { + "macos": { + "features": { + "edit_prediction_provider": "copilot" + } + } + } + "# + .unindent(), + Some( + &r#" + { + "macos": { + "edit_predictions": { + "provider": "copilot" + } + } + } + "# + .unindent(), + ), + ); + + // Profile: settings nested inside profiles should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions, + )], + &r#" + { + "profiles": { + "work": { + "features": { + "edit_prediction_provider": "copilot" + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "profiles": { + "work": { + "edit_predictions": { + "provider": "copilot" + } + } + } + } + "# + .unindent(), + ), + ); + + // Combined: root + platform + profile should all be migrated simultaneously + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2026_02_02::move_edit_prediction_provider_to_edit_predictions, + )], + &r#" + { + "features": { + "edit_prediction_provider": "copilot" + }, + "macos": { + "features": { + "edit_prediction_provider": "zed" + } + }, + "profiles": { + "work": { + "features": { + "edit_prediction_provider": "supermaven" + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "edit_predictions": { + "provider": "copilot" + }, + "macos": { + "edit_predictions": { + "provider": "zed" + } + }, + "profiles": { + "work": { + "edit_predictions": { + "provider": "supermaven" + } + } + } + } + "# + .unindent(), + ), + ); } #[test] @@ -2591,5 +2939,126 @@ mod tests { .unindent(), None, ); + + // Platform key: settings nested inside "linux" should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2026_02_03::migrate_experimental_sweep_mercury, + )], + &r#" + { + "linux": { + "edit_predictions": { + "provider": { + "experimental": "sweep" + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "linux": { + "edit_predictions": { + "provider": "sweep" + } + } + } + "# + .unindent(), + ), + ); + + // Profile: settings nested inside profiles should be migrated + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2026_02_03::migrate_experimental_sweep_mercury, + )], + &r#" + { + "profiles": { + "dev": { + "edit_predictions": { + "provider": { + "experimental": "mercury" + } + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "profiles": { + "dev": { + "edit_predictions": { + "provider": "mercury" + } + } + } + } + "# + .unindent(), + ), + ); + + // Combined: root + platform + profile should all be migrated simultaneously + assert_migrate_settings_with_migrations( + &[MigrationType::Json( + migrations::m_2026_02_03::migrate_experimental_sweep_mercury, + )], + &r#" + { + "edit_predictions": { + "provider": { + "experimental": "sweep" + } + }, + "linux": { + "edit_predictions": { + "provider": { + "experimental": "mercury" + } + } + }, + "profiles": { + "dev": { + "edit_predictions": { + "provider": { + "experimental": "sweep" + } + } + } + } + } + "# + .unindent(), + Some( + &r#" + { + "edit_predictions": { + "provider": "sweep" + }, + "linux": { + "edit_predictions": { + "provider": "mercury" + } + }, + "profiles": { + "dev": { + "edit_predictions": { + "provider": "sweep" + } + } + } + } + "# + .unindent(), + ), + ); } } diff --git a/crates/migrator/src/patterns.rs b/crates/migrator/src/patterns.rs index 4132c93d9367a8dee200200e03dcc46ee073e67f..3848baf23ba0d324995f18e3a53921948291153b 100644 --- a/crates/migrator/src/patterns.rs +++ b/crates/migrator/src/patterns.rs @@ -10,5 +10,4 @@ pub(crate) use settings::{ SETTINGS_ASSISTANT_PATTERN, SETTINGS_ASSISTANT_TOOLS_PATTERN, SETTINGS_DUPLICATED_AGENT_PATTERN, SETTINGS_EDIT_PREDICTIONS_ASSISTANT_PATTERN, SETTINGS_LANGUAGES_PATTERN, SETTINGS_NESTED_KEY_VALUE_PATTERN, SETTINGS_ROOT_KEY_VALUE_PATTERN, - migrate_language_setting, }; diff --git a/crates/migrator/src/patterns/settings.rs b/crates/migrator/src/patterns/settings.rs index a068cce23b013a3435188c03ceebe866883c4e6d..72fd02b153a5cf6e3158790f1c5d09a9f643ebf9 100644 --- a/crates/migrator/src/patterns/settings.rs +++ b/crates/migrator/src/patterns/settings.rs @@ -108,24 +108,3 @@ pub const SETTINGS_DUPLICATED_AGENT_PATTERN: &str = r#"(document (#eq? @agent1 "agent") (#eq? @agent2 "agent") )"#; - -/// Migrate language settings, -/// calls `migrate_fn` with the top level object as well as all language settings under the "languages" key -/// Fails early if `migrate_fn` returns an error at any point -pub fn migrate_language_setting( - value: &mut serde_json::Value, - migrate_fn: fn(&mut serde_json::Value, path: &[&str]) -> anyhow::Result<()>, -) -> anyhow::Result<()> { - migrate_fn(value, &[])?; - let languages = value - .as_object_mut() - .and_then(|obj| obj.get_mut("languages")) - .and_then(|languages| languages.as_object_mut()); - if let Some(languages) = languages { - for (language_name, language) in languages.iter_mut() { - let path = vec!["languages", language_name]; - migrate_fn(language, &path)?; - } - } - Ok(()) -} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index d66699e8119136b8e1bfe195d6ff08a7afac5dbe..9049c95eb9529b9a490687e1130af273b7496970 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -24,7 +24,7 @@ pub mod private { } use gpui::{App, Global}; -use release_channel::ReleaseChannel; + use rust_embed::RustEmbed; use std::env; use std::{borrow::Cow, fmt, str}; @@ -73,21 +73,12 @@ impl UserSettingsContentExt for UserSettingsContent { } fn for_release_channel(&self) -> Option<&SettingsContent> { - match *release_channel::RELEASE_CHANNEL { - ReleaseChannel::Dev => self.dev.as_deref(), - ReleaseChannel::Nightly => self.nightly.as_deref(), - ReleaseChannel::Preview => self.preview.as_deref(), - ReleaseChannel::Stable => self.stable.as_deref(), - } + self.release_channel_overrides + .get_by_key(release_channel::RELEASE_CHANNEL.dev_name()) } fn for_os(&self) -> Option<&SettingsContent> { - match env::consts::OS { - "macos" => self.macos.as_deref(), - "linux" => self.linux.as_deref(), - "windows" => self.windows.as_deref(), - _ => None, - } + self.platform_overrides.get_by_key(env::consts::OS) } } diff --git a/crates/settings_content/src/settings_content.rs b/crates/settings_content/src/settings_content.rs index 78451efc2f8186c587eb5a474271d81cc1106925..8644e44f84c8f1b8b38d4e1bff266b685dbbcd66 100644 --- a/crates/settings_content/src/settings_content.rs +++ b/crates/settings_content/src/settings_content.rs @@ -32,6 +32,37 @@ use collections::{HashMap, IndexMap}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings_macros::{MergeFrom, with_fallible_options}; + +/// Defines a settings override struct where each field is +/// `Option>`, along with: +/// - `OVERRIDE_KEYS`: a `&[&str]` of the field names (the JSON keys) +/// - `get_by_key(&self, key) -> Option<&SettingsContent>`: accessor by key +/// +/// The field list is the single source of truth for the override key strings. +macro_rules! settings_overrides { + ( + $(#[$attr:meta])* + pub struct $name:ident { $($field:ident),* $(,)? } + ) => { + $(#[$attr])* + pub struct $name { + $(pub $field: Option>,)* + } + + impl $name { + /// The JSON override keys, derived from the field names on this struct. + pub const OVERRIDE_KEYS: &[&str] = &[$(stringify!($field)),*]; + + /// Look up an override by its JSON key name. + pub fn get_by_key(&self, key: &str) -> Option<&SettingsContent> { + match key { + $(stringify!($field) => self.$field.as_deref(),)* + _ => None, + } + } + } + } +} use std::collections::BTreeSet; use std::sync::Arc; pub use util::serde::default_true; @@ -217,20 +248,29 @@ impl RootUserSettings for UserSettingsContent { } } +settings_overrides! { + #[with_fallible_options] + #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] + pub struct ReleaseChannelOverrides { dev, nightly, preview, stable } +} + +settings_overrides! { + #[with_fallible_options] + #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] + pub struct PlatformOverrides { macos, linux, windows } +} + #[with_fallible_options] #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct UserSettingsContent { #[serde(flatten)] pub content: Box, - pub dev: Option>, - pub nightly: Option>, - pub preview: Option>, - pub stable: Option>, + #[serde(flatten)] + pub release_channel_overrides: ReleaseChannelOverrides, - pub macos: Option>, - pub windows: Option>, - pub linux: Option>, + #[serde(flatten)] + pub platform_overrides: PlatformOverrides, #[serde(default)] pub profiles: IndexMap,