From 8d176c0cd205e381c4709458283fd0066faa13b4 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 17 Nov 2025 16:47:49 -0700 Subject: [PATCH 1/6] Don't call me Maybe --- crates/settings/src/settings_content.rs | 215 ------------------ .../settings/src/settings_content/project.rs | 6 +- crates/settings/src/vscode_import.rs | 2 +- crates/settings_ui/src/page_data.rs | 4 +- crates/settings_ui/src/settings_ui.rs | 1 - crates/worktree/src/worktree_settings.rs | 2 +- 6 files changed, 7 insertions(+), 223 deletions(-) diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 42b88bd3654159..9cd8ff46e8bea5 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -1039,218 +1039,3 @@ impl std::fmt::Display for DelayMs { write!(f, "{}ms", self.0) } } - -/// A wrapper type that distinguishes between an explicitly set value (including null) and an unset value. -/// -/// This is useful for configuration where you need to differentiate between: -/// - A field that is not present in the configuration file (`Maybe::Unset`) -/// - A field that is explicitly set to `null` (`Maybe::Set(None)`) -/// - A field that is explicitly set to a value (`Maybe::Set(Some(value))`) -/// -/// # Examples -/// -/// In JSON: -/// - `{}` (field missing) deserializes to `Maybe::Unset` -/// - `{"field": null}` deserializes to `Maybe::Set(None)` -/// - `{"field": "value"}` deserializes to `Maybe::Set(Some("value"))` -/// -/// WARN: This type should not be wrapped in an option inside of settings, otherwise the default `serde_json` behavior -/// of treating `null` and missing as the `Option::None` will be used -#[derive(Debug, Clone, PartialEq, Eq, strum::EnumDiscriminants, Default)] -#[strum_discriminants(derive(strum::VariantArray, strum::VariantNames, strum::FromRepr))] -pub enum Maybe { - /// An explicitly set value, which may be `None` (representing JSON `null`) or `Some(value)`. - Set(Option), - /// A value that was not present in the configuration. - #[default] - Unset, -} - -impl merge_from::MergeFrom for Maybe { - fn merge_from(&mut self, other: &Self) { - if self.is_unset() { - *self = other.clone(); - } - } -} - -impl From>> for Maybe { - fn from(value: Option>) -> Self { - match value { - Some(value) => Maybe::Set(value), - None => Maybe::Unset, - } - } -} - -impl Maybe { - pub fn is_set(&self) -> bool { - matches!(self, Maybe::Set(_)) - } - - pub fn is_unset(&self) -> bool { - matches!(self, Maybe::Unset) - } - - pub fn into_inner(self) -> Option { - match self { - Maybe::Set(value) => value, - Maybe::Unset => None, - } - } - - pub fn as_ref(&self) -> Option<&Option> { - match self { - Maybe::Set(value) => Some(value), - Maybe::Unset => None, - } - } -} - -impl serde::Serialize for Maybe { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - match self { - Maybe::Set(value) => value.serialize(serializer), - Maybe::Unset => serializer.serialize_none(), - } - } -} - -impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for Maybe { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Option::::deserialize(deserializer).map(Maybe::Set) - } -} - -impl JsonSchema for Maybe { - fn schema_name() -> std::borrow::Cow<'static, str> { - format!("Nullable<{}>", T::schema_name()).into() - } - - fn json_schema(generator: &mut schemars::generate::SchemaGenerator) -> schemars::Schema { - let mut schema = generator.subschema_for::>(); - // Add description explaining that null is an explicit value - let description = if let Some(existing_desc) = - schema.get("description").and_then(|desc| desc.as_str()) - { - format!( - "{}. Note: `null` is treated as an explicit value, different from omitting the field entirely.", - existing_desc - ) - } else { - "This field supports explicit `null` values. Omitting the field is different from setting it to `null`.".to_string() - }; - - schema.insert("description".to_string(), description.into()); - - schema - } -} - -#[cfg(test)] -mod tests { - use super::*; - use serde_json; - - #[test] - fn test_maybe() { - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct TestStruct { - #[serde(default)] - #[serde(skip_serializing_if = "Maybe::is_unset")] - field: Maybe, - } - - #[derive(Debug, PartialEq, Serialize, Deserialize)] - struct NumericTest { - #[serde(default)] - value: Maybe, - } - - let json = "{}"; - let result: TestStruct = serde_json::from_str(json).unwrap(); - assert!(result.field.is_unset()); - assert_eq!(result.field, Maybe::Unset); - - let json = r#"{"field": null}"#; - let result: TestStruct = serde_json::from_str(json).unwrap(); - assert!(result.field.is_set()); - assert_eq!(result.field, Maybe::Set(None)); - - let json = r#"{"field": "hello"}"#; - let result: TestStruct = serde_json::from_str(json).unwrap(); - assert!(result.field.is_set()); - assert_eq!(result.field, Maybe::Set(Some("hello".to_string()))); - - let test = TestStruct { - field: Maybe::Unset, - }; - let json = serde_json::to_string(&test).unwrap(); - assert_eq!(json, "{}"); - - let test = TestStruct { - field: Maybe::Set(None), - }; - let json = serde_json::to_string(&test).unwrap(); - assert_eq!(json, r#"{"field":null}"#); - - let test = TestStruct { - field: Maybe::Set(Some("world".to_string())), - }; - let json = serde_json::to_string(&test).unwrap(); - assert_eq!(json, r#"{"field":"world"}"#); - - let default_maybe: Maybe = Maybe::default(); - assert!(default_maybe.is_unset()); - - let unset: Maybe = Maybe::Unset; - assert!(unset.is_unset()); - assert!(!unset.is_set()); - - let set_none: Maybe = Maybe::Set(None); - assert!(set_none.is_set()); - assert!(!set_none.is_unset()); - - let set_some: Maybe = Maybe::Set(Some("value".to_string())); - assert!(set_some.is_set()); - assert!(!set_some.is_unset()); - - let original = TestStruct { - field: Maybe::Set(Some("test".to_string())), - }; - let json = serde_json::to_string(&original).unwrap(); - let deserialized: TestStruct = serde_json::from_str(&json).unwrap(); - assert_eq!(original, deserialized); - - let json = r#"{"value": 42}"#; - let result: NumericTest = serde_json::from_str(json).unwrap(); - assert_eq!(result.value, Maybe::Set(Some(42))); - - let json = r#"{"value": null}"#; - let result: NumericTest = serde_json::from_str(json).unwrap(); - assert_eq!(result.value, Maybe::Set(None)); - - let json = "{}"; - let result: NumericTest = serde_json::from_str(json).unwrap(); - assert_eq!(result.value, Maybe::Unset); - - // Test JsonSchema implementation - use schemars::schema_for; - let schema = schema_for!(Maybe); - let schema_json = serde_json::to_value(&schema).unwrap(); - - // Verify the description mentions that null is an explicit value - let description = schema_json["description"].as_str().unwrap(); - assert!( - description.contains("null") && description.contains("explicit"), - "Schema description should mention that null is an explicit value. Got: {}", - description - ); - } -} diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs index b6bebd76e28a31..1dc5292891819e 100644 --- a/crates/settings/src/settings_content/project.rs +++ b/crates/settings/src/settings_content/project.rs @@ -8,7 +8,7 @@ use settings_macros::MergeFrom; use util::serde::default_true; use crate::{ - AllLanguageSettingsContent, DelayMs, ExtendingVec, Maybe, ProjectTerminalSettingsContent, + AllLanguageSettingsContent, DelayMs, ExtendingVec, ProjectTerminalSettingsContent, SlashCommandSettings, }; @@ -61,8 +61,8 @@ pub struct WorktreeSettingsContent { /// /// Default: null #[serde(default)] - #[serde(skip_serializing_if = "Maybe::is_unset")] - pub project_name: Maybe, + #[serde(skip_serializing_if = "Option::is_none")] + pub project_name: Option, /// Whether to prevent this project from being shared in public channels. /// diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 4b87d6f5f30c07..5644cd7a1a8463 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -870,7 +870,7 @@ impl VsCodeSettings { fn worktree_settings_content(&self) -> WorktreeSettingsContent { WorktreeSettingsContent { - project_name: crate::Maybe::Unset, + project_name: None, prevent_sharing_in_public_channels: false, file_scan_exclusions: self .read_value("files.watcherExclude") diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index d776b9787eb804..a6baaf94842955 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -33,10 +33,10 @@ pub(crate) fn settings_data(cx: &App) -> Vec { SettingField { json_path: Some("project_name"), pick: |settings_content| { - settings_content.project.worktree.project_name.as_ref()?.as_ref().or(DEFAULT_EMPTY_STRING) + settings_content.project.worktree.project_name.as_ref().or(DEFAULT_EMPTY_STRING) }, write: |settings_content, value| { - settings_content.project.worktree.project_name = settings::Maybe::Set(value.filter(|name| !name.is_empty())); + settings_content.project.worktree.project_name = value.filter(|name| !name.is_empty()); }, } ), diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 1f32716f063919..7b90464633c47c 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -507,7 +507,6 @@ fn init_renderers(cx: &mut App) { .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) - .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) .add_basic_renderer::(render_dropdown) diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 94e83a16decd6b..97723829dd78a3 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -66,7 +66,7 @@ impl Settings for WorktreeSettings { .collect(); Self { - project_name: worktree.project_name.into_inner(), + project_name: worktree.project_name, prevent_sharing_in_public_channels: worktree.prevent_sharing_in_public_channels, file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions") .log_err() From 0dfcca0847d5f42f93c49b46f86bd6937ddca4bf Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 17 Nov 2025 20:53:16 -0700 Subject: [PATCH 2/6] Use fallible options for settings --- Cargo.lock | 290 ++++++------------ Cargo.toml | 1 - crates/settings/Cargo.toml | 1 - crates/settings/src/fallible_options.rs | 112 +++++++ crates/settings/src/settings.rs | 1 + crates/settings/src/settings_content.rs | 59 ++-- crates/settings/src/settings_content/agent.rs | 19 +- .../settings/src/settings_content/editor.rs | 23 +- .../src/settings_content/extension.rs | 6 +- .../settings/src/settings_content/language.rs | 38 +-- .../src/settings_content/language_model.rs | 63 ++-- .../settings/src/settings_content/project.rs | 44 ++- .../settings/src/settings_content/terminal.rs | 11 +- crates/settings/src/settings_content/theme.rs | 56 ++-- .../src/settings_content/workspace.rs | 25 +- crates/settings/src/settings_store.rs | 37 +-- crates/settings/src/vscode_import.rs | 2 +- crates/settings_macros/src/settings_macros.rs | 52 +++- crates/settings_ui/src/page_data.rs | 2 +- 19 files changed, 437 insertions(+), 405 deletions(-) create mode 100644 crates/settings/src/fallible_options.rs diff --git a/Cargo.lock b/Cargo.lock index 8a68187705d129..7154329ee6f2f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -183,7 +183,7 @@ dependencies = [ "regex", "reqwest_client", "rust-embed", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -238,7 +238,7 @@ checksum = "ecf16c18fea41282d6bbadd1549a06be6836bddb1893f44a6235f340fa24e2af" dependencies = [ "anyhow", "derive_more 2.0.1", - "schemars 1.0.4", + "schemars", "serde", "serde_json", ] @@ -298,7 +298,7 @@ dependencies = [ "language_model", "paths", "project", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", @@ -372,7 +372,7 @@ dependencies = [ "release_channel", "rope", "rules_library", - "schemars 1.0.4", + "schemars", "search", "serde", "serde_json", @@ -627,7 +627,7 @@ dependencies = [ "chrono", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -2033,7 +2033,7 @@ dependencies = [ "aws-sdk-bedrockruntime", "aws-smithy-types", "futures 0.3.31", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "strum 0.27.2", @@ -2578,7 +2578,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9225bdcf4e4a9a4c08bf16607908eb2fbf746828d5e0b5e019726dbf6571f201" dependencies = [ - "darling 0.20.11", + "darling", "proc-macro2", "quote", "syn 2.0.106", @@ -2839,7 +2839,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eadd868a2ce9ca38de7eeafdcec9c7065ef89b42b32f0839278d55f35c54d1ff" dependencies = [ "heck 0.4.1", - "indexmap 2.11.4", + "indexmap", "log", "proc-macro2", "quote", @@ -3209,7 +3209,7 @@ dependencies = [ "indoc", "ordered-float 2.10.1", "rustc-hash 2.1.1", - "schemars 1.0.4", + "schemars", "serde", "strum 0.27.2", ] @@ -3485,7 +3485,7 @@ dependencies = [ name = "collections" version = "0.1.0" dependencies = [ - "indexmap 2.11.4", + "indexmap", "rustc-hash 2.1.1", ] @@ -3694,7 +3694,7 @@ dependencies = [ "net", "parking_lot", "postage", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -4444,7 +4444,7 @@ checksum = "d74b6bcf49ebbd91f1b1875b706ea46545032a14003b5557b7dfa4bbeba6766e" dependencies = [ "cc", "codespan-reporting 0.13.0", - "indexmap 2.11.4", + "indexmap", "proc-macro2", "quote", "scratch", @@ -4459,7 +4459,7 @@ checksum = "94ca2ad69673c4b35585edfa379617ac364bccd0ba0adf319811ba3a74ffa48a" dependencies = [ "clap", "codespan-reporting 0.13.0", - "indexmap 2.11.4", + "indexmap", "proc-macro2", "quote", "syn 2.0.106", @@ -4477,7 +4477,7 @@ version = "1.0.187" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a8ebf0b6138325af3ec73324cb3a48b64d57721f17291b151206782e61f66cd" dependencies = [ - "indexmap 2.11.4", + "indexmap", "proc-macro2", "quote", "syn 2.0.106", @@ -4506,7 +4506,7 @@ dependencies = [ "parking_lot", "paths", "proto", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -4525,7 +4525,7 @@ name = "dap-types" version = "0.0.1" source = "git+https://github.com/zed-industries/dap-types?rev=1b461b310481d01e02b2603c16d7144b926339f8#1b461b310481d01e02b2603c16d7144b926339f8" dependencies = [ - "schemars 1.0.4", + "schemars", "serde", "serde_json", ] @@ -4562,18 +4562,8 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", + "darling_core", + "darling_macro", ] [[package]] @@ -4590,38 +4580,13 @@ dependencies = [ "syn 2.0.106", ] -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.106", -] - [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.106", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core 0.21.3", + "darling_core", "quote", "syn 2.0.106", ] @@ -4771,7 +4736,7 @@ dependencies = [ "pretty_assertions", "project", "rpc", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", @@ -4810,7 +4775,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", ] @@ -4925,7 +4890,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae5c625eda104c228c06ecaf988d1c60e542176bd7a490e60eeda3493244c0c9" dependencies = [ - "darling 0.20.11", + "darling", "proc-macro2", "quote", "syn 2.0.106", @@ -5401,7 +5366,7 @@ dependencies = [ "release_channel", "rope", "rpc", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -6133,7 +6098,7 @@ dependencies = [ "picker", "pretty_assertions", "project", - "schemars 1.0.4", + "schemars", "search", "serde", "serde_json", @@ -6980,7 +6945,7 @@ dependencies = [ "derive_more 2.0.1", "derive_setters", "gh-workflow-macros", - "indexmap 2.11.4", + "indexmap", "merge", "serde", "serde_json", @@ -7015,7 +6980,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" dependencies = [ "fallible-iterator", - "indexmap 2.11.4", + "indexmap", "stable_deref_trait", ] @@ -7045,7 +7010,7 @@ dependencies = [ "rand 0.9.2", "regex", "rope", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "smol", @@ -7131,7 +7096,7 @@ dependencies = [ "project", "recent_projects", "remote", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -7235,7 +7200,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -7339,7 +7304,7 @@ dependencies = [ "refineable", "reqwest_client", "resvg", - "schemars 1.0.4", + "schemars", "seahash", "semantic_version", "serde", @@ -7427,7 +7392,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.11.4", + "indexmap", "slab", "tokio", "tokio-util", @@ -7446,7 +7411,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.3.1", - "indexmap 2.11.4", + "indexmap", "slab", "tokio", "tokio-util", @@ -8209,17 +8174,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8" -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", - "serde", -] - [[package]] name = "indexmap" version = "2.11.4" @@ -8608,7 +8562,7 @@ dependencies = [ "language", "paths", "project", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -8810,7 +8764,7 @@ dependencies = [ "rand 0.9.2", "regex", "rpc", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -8883,7 +8837,7 @@ dependencies = [ "open_router", "parking_lot", "proto", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -8932,7 +8886,7 @@ dependencies = [ "partial-json-fixer", "project", "release_channel", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -9449,7 +9403,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", ] @@ -9512,7 +9466,7 @@ dependencies = [ "parking_lot", "postage", "release_channel", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "smol", @@ -10092,7 +10046,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "strum 0.27.2", @@ -10192,7 +10146,7 @@ dependencies = [ "half", "hashbrown 0.15.5", "hexf-parse", - "indexmap 2.11.4", + "indexmap", "log", "num-traits", "once_cell", @@ -10866,7 +10820,7 @@ checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "crc32fast", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap", "memchr", ] @@ -10921,7 +10875,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -10944,7 +10898,7 @@ dependencies = [ "notifications", "picker", "project", - "schemars 1.0.4", + "schemars", "serde", "settings", "telemetry", @@ -11029,7 +10983,7 @@ dependencies = [ "futures 0.3.31", "http_client", "log", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -11044,7 +10998,7 @@ dependencies = [ "anyhow", "futures 0.3.31", "http_client", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -11925,7 +11879,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", - "indexmap 2.11.4", + "indexmap", ] [[package]] @@ -12041,7 +11995,7 @@ dependencies = [ "env_logger 0.11.8", "gpui", "menu", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "theme", @@ -12158,7 +12112,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" dependencies = [ "base64 0.22.1", - "indexmap 2.11.4", + "indexmap", "quick-xml 0.38.3", "serde", "time", @@ -12321,7 +12275,7 @@ dependencies = [ "comfy-table", "either", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap", "itoa", "num-traits", "polars-arrow", @@ -12498,7 +12452,7 @@ dependencies = [ "either", "hashbrown 0.15.5", "hex", - "indexmap 2.11.4", + "indexmap", "libm", "memchr", "num-traits", @@ -12610,7 +12564,7 @@ version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6c1ab13e04d5167661a9854ed1ea0482b2ed9b8a0f1118dabed7cd994a85e3" dependencies = [ - "indexmap 2.11.4", + "indexmap", "polars-error", "polars-utils", "serde", @@ -12713,7 +12667,7 @@ dependencies = [ "flate2", "foldhash 0.1.5", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap", "libc", "memmap2", "num-traits", @@ -13030,7 +12984,7 @@ dependencies = [ "gpui", "http_client", "image", - "indexmap 2.11.4", + "indexmap", "itertools 0.14.0", "language", "log", @@ -13047,7 +13001,7 @@ dependencies = [ "release_channel", "remote", "rpc", - "schemars 1.0.4", + "schemars", "semver", "serde", "serde_json", @@ -13111,7 +13065,7 @@ dependencies = [ "pretty_assertions", "project", "rayon", - "schemars 1.0.4", + "schemars", "search", "serde", "serde_json", @@ -13980,7 +13934,7 @@ dependencies = [ "prost 0.9.0", "release_channel", "rpc", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -14869,24 +14823,12 @@ dependencies = [ "anyhow", "clap", "env_logger 0.11.8", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "theme", ] -[[package]] -name = "schemars" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" -dependencies = [ - "dyn-clone", - "ref-cast", - "serde", - "serde_json", -] - [[package]] name = "schemars" version = "1.0.4" @@ -14894,7 +14836,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" dependencies = [ "dyn-clone", - "indexmap 2.11.4", + "indexmap", "ref-cast", "schemars_derive", "serde", @@ -15106,7 +15048,7 @@ dependencies = [ "lsp", "menu", "project", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -15256,7 +15198,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.11.4", + "indexmap", "itoa", "memchr", "ryu", @@ -15270,7 +15212,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e033097bf0d2b59a62b42c18ebbb797503839b26afdda2c4e1415cb6c813540" dependencies = [ - "indexmap 2.11.4", + "indexmap", "itoa", "memchr", "ryu", @@ -15340,44 +15282,13 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6093cd8c01b25262b84927e0f7151692158fab02d961e04c979d3903eba7ecc5" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.11.4", - "schemars 0.9.0", - "schemars 1.0.4", - "serde_core", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7e6c180db0816026a61afa1cff5344fb7ebded7e4d3062772179f2501481c27" -dependencies = [ - "darling 0.21.3", - "proc-macro2", - "quote", - "syn 2.0.106", -] - [[package]] name = "serde_yaml" version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.11.4", + "indexmap", "itoa", "ryu", "serde", @@ -15425,12 +15336,11 @@ dependencies = [ "pretty_assertions", "release_channel", "rust-embed", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", "serde_repr", - "serde_with", "settings_json", "settings_macros", "smallvec", @@ -15509,7 +15419,7 @@ dependencies = [ "pretty_assertions", "project", "release_channel", - "schemars 1.0.4", + "schemars", "search", "serde", "session", @@ -15813,7 +15723,7 @@ dependencies = [ "indoc", "parking_lot", "paths", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", @@ -16019,7 +15929,7 @@ dependencies = [ "futures-util", "hashbrown 0.15.5", "hashlink 0.10.0", - "indexmap 2.11.4", + "indexmap", "log", "memchr", "once_cell", @@ -16958,7 +16868,7 @@ dependencies = [ "menu", "picker", "project", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "settings", @@ -17037,7 +16947,7 @@ dependencies = [ "parking_lot", "pretty_assertions", "proto", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", @@ -17140,7 +17050,7 @@ dependencies = [ "rand 0.9.2", "regex", "release_channel", - "schemars 1.0.4", + "schemars", "serde", "settings", "smol", @@ -17186,7 +17096,7 @@ dependencies = [ "project", "rand 0.9.2", "regex", - "schemars 1.0.4", + "schemars", "search", "serde", "serde_json", @@ -17238,7 +17148,7 @@ dependencies = [ "palette", "parking_lot", "refineable", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", @@ -17268,7 +17178,7 @@ dependencies = [ "clap", "collections", "gpui", - "indexmap 2.11.4", + "indexmap", "log", "palette", "serde", @@ -17522,7 +17432,7 @@ dependencies = [ "project", "remote", "rpc", - "schemars 1.0.4", + "schemars", "serde", "settings", "smallvec", @@ -17711,7 +17621,7 @@ version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" dependencies = [ - "indexmap 2.11.4", + "indexmap", "serde_core", "serde_spanned 1.0.3", "toml_datetime 0.7.3", @@ -17744,7 +17654,7 @@ version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.11.4", + "indexmap", "serde", "serde_spanned 0.6.9", "toml_datetime 0.6.11", @@ -17758,7 +17668,7 @@ version = "0.23.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" dependencies = [ - "indexmap 2.11.4", + "indexmap", "toml_datetime 0.7.3", "toml_parser", "winnow", @@ -18418,7 +18328,7 @@ dependencies = [ "icons", "itertools 0.14.0", "menu", - "schemars 1.0.4", + "schemars", "serde", "settings", "smallvec", @@ -18692,7 +18602,7 @@ dependencies = [ "rand 0.9.2", "regex", "rust-embed", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "serde_json_lenient", @@ -18804,7 +18714,7 @@ name = "vercel" version = "0.1.0" dependencies = [ "anyhow", - "schemars 1.0.4", + "schemars", "serde", "strum 0.27.2", ] @@ -18854,7 +18764,7 @@ dependencies = [ "project_panel", "regex", "release_channel", - "schemars 1.0.4", + "schemars", "search", "serde", "serde_json", @@ -19116,7 +19026,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fd83062c17b9f4985d438603cde0a5e8c5c8198201a6937f778b607924c7da2" dependencies = [ "anyhow", - "indexmap 2.11.4", + "indexmap", "serde", "serde_derive", "serde_json", @@ -19134,7 +19044,7 @@ dependencies = [ "anyhow", "auditable-serde", "flate2", - "indexmap 2.11.4", + "indexmap", "serde", "serde_derive", "serde_json", @@ -19164,7 +19074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84e5df6dba6c0d7fafc63a450f1738451ed7a0b52295d83e868218fa286bf708" dependencies = [ "bitflags 2.9.4", - "indexmap 2.11.4", + "indexmap", "semver", ] @@ -19176,7 +19086,7 @@ checksum = "d06bfa36ab3ac2be0dee563380147a5b81ba10dd8885d7fbbc9eb574be67d185" dependencies = [ "bitflags 2.9.4", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap", "semver", "serde", ] @@ -19189,7 +19099,7 @@ checksum = "0f51cad774fb3c9461ab9bccc9c62dfb7388397b5deda31bf40e8108ccd678b2" dependencies = [ "bitflags 2.9.4", "hashbrown 0.15.5", - "indexmap 2.11.4", + "indexmap", "semver", ] @@ -19218,7 +19128,7 @@ dependencies = [ "cfg-if", "encoding_rs", "hashbrown 0.14.5", - "indexmap 2.11.4", + "indexmap", "libc", "log", "mach2 0.4.3", @@ -19342,7 +19252,7 @@ dependencies = [ "cranelift-bitset", "cranelift-entity", "gimli 0.31.1", - "indexmap 2.11.4", + "indexmap", "log", "object 0.36.7", "postcard", @@ -19467,7 +19377,7 @@ checksum = "8358319c2dd1e4db79e3c1c5d3a5af84956615343f9f89f4e4996a36816e06e6" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.11.4", + "indexmap", "wit-parser 0.221.3", ] @@ -20620,7 +20530,7 @@ checksum = "d8a39a15d1ae2077688213611209849cad40e9e5cccf6e61951a425850677ff3" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.11.4", + "indexmap", "wasm-metadata 0.201.0", "wit-bindgen-core 0.22.0", "wit-component 0.201.0", @@ -20634,7 +20544,7 @@ checksum = "9d0809dc5ba19e2e98661bf32fc0addc5a3ca5bf3a6a7083aa6ba484085ff3ce" dependencies = [ "anyhow", "heck 0.5.0", - "indexmap 2.11.4", + "indexmap", "prettyplease", "syn 2.0.106", "wasm-metadata 0.227.1", @@ -20679,7 +20589,7 @@ checksum = "421c0c848a0660a8c22e2fd217929a0191f14476b68962afd2af89fd22e39825" dependencies = [ "anyhow", "bitflags 2.9.4", - "indexmap 2.11.4", + "indexmap", "log", "serde", "serde_derive", @@ -20698,7 +20608,7 @@ checksum = "635c3adc595422cbf2341a17fb73a319669cc8d33deed3a48368a841df86b676" dependencies = [ "anyhow", "bitflags 2.9.4", - "indexmap 2.11.4", + "indexmap", "log", "serde", "serde_derive", @@ -20717,7 +20627,7 @@ checksum = "196d3ecfc4b759a8573bf86a9b3f8996b304b3732e4c7de81655f875f6efdca6" dependencies = [ "anyhow", "id-arena", - "indexmap 2.11.4", + "indexmap", "log", "semver", "serde", @@ -20735,7 +20645,7 @@ checksum = "896112579ed56b4a538b07a3d16e562d101ff6265c46b515ce0c701eef16b2ac" dependencies = [ "anyhow", "id-arena", - "indexmap 2.11.4", + "indexmap", "log", "semver", "serde", @@ -20753,7 +20663,7 @@ checksum = "ddf445ed5157046e4baf56f9138c124a0824d4d1657e7204d71886ad8ce2fc11" dependencies = [ "anyhow", "id-arena", - "indexmap 2.11.4", + "indexmap", "log", "semver", "serde", @@ -20803,7 +20713,7 @@ dependencies = [ "pretty_assertions", "project", "remote", - "schemars 1.0.4", + "schemars", "serde", "serde_json", "session", @@ -20918,7 +20828,7 @@ name = "x_ai" version = "0.1.0" dependencies = [ "anyhow", - "schemars 1.0.4", + "schemars", "serde", "strum 0.27.2", ] @@ -21017,7 +20927,7 @@ dependencies = [ "cargo_toml", "clap", "gh-workflow", - "indexmap 2.11.4", + "indexmap", "indoc", "serde", "toml 0.8.23", @@ -21471,7 +21381,7 @@ name = "zed_actions" version = "0.1.0" dependencies = [ "gpui", - "schemars 1.0.4", + "schemars", "serde", "uuid", ] @@ -21878,7 +21788,7 @@ dependencies = [ "crc32fast", "crossbeam-utils", "displaydoc", - "indexmap 2.11.4", + "indexmap", "num_enum", "thiserror 1.0.69", ] diff --git a/Cargo.toml b/Cargo.toml index be56964f753cde..eebc5d95bea358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -640,7 +640,6 @@ serde_json_lenient = { version = "0.2", features = [ serde_path_to_error = "0.1.17" serde_repr = "0.1" serde_urlencoded = "0.7" -serde_with = "3.4.0" sha2 = "0.10" shellexpand = "2.1.0" shlex = "1.3.0" diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index af0d5a55f363fd..1f1513d6216f6a 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -34,7 +34,6 @@ serde.workspace = true serde_json.workspace = true serde_json_lenient.workspace = true serde_repr.workspace = true -serde_with.workspace = true settings_json.workspace = true settings_macros.workspace = true smallvec.workspace = true diff --git a/crates/settings/src/fallible_options.rs b/crates/settings/src/fallible_options.rs new file mode 100644 index 00000000000000..169b59f9d2559a --- /dev/null +++ b/crates/settings/src/fallible_options.rs @@ -0,0 +1,112 @@ +use std::cell::RefCell; + +use serde::Deserialize; + +use crate::ParseStatus; + +thread_local! { + static ERRORS: RefCell>> = RefCell::new(None); +} + +pub(crate) fn parse_json<'de, T>(json: &'de str) -> (Option, ParseStatus) +where + T: Deserialize<'de>, +{ + ERRORS.with_borrow_mut(|errors| { + errors.replace(Vec::default()); + }); + + let mut deserializer = serde_json_lenient::Deserializer::from_str(json); + let value = T::deserialize(&mut deserializer); + let value = match value { + Ok(value) => value, + Err(error) => { + return ( + None, + ParseStatus::Failed { + error: error.to_string(), + }, + ); + } + }; + + if let Some(errors) = ERRORS.with_borrow_mut(|errors| errors.take().filter(|e| !e.is_empty())) { + let error = errors + .into_iter() + .map(|e| e.to_string()) + .flat_map(|e| ["\n".to_owned(), e]) + .skip(1) + .collect::(); + return (Some(value), ParseStatus::Failed { error }); + } + + (Some(value), ParseStatus::Success) +} + +pub(crate) fn deserialize<'de, D, T>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, + T: serde::Deserialize<'de> + FallibleOption, +{ + match T::deserialize(deserializer) { + Ok(value) => Ok(value), + Err(e) => ERRORS.with_borrow_mut(|errors| { + if let Some(errors) = errors { + errors.push(anyhow::anyhow!("{}", e)); + Ok(Default::default()) + } else { + Err(e) + } + }), + } +} + +pub trait FallibleOption: Default {} +impl FallibleOption for Option {} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + use settings_macros::with_fallible_options; + + use crate::ParseStatus; + + #[with_fallible_options] + #[derive(Deserialize, Debug, PartialEq)] + struct Foo { + foo: Option, + bar: Option, + baz: Option, + } + + #[test] + fn test_fallible() { + let input = r#" + {"foo": "bar", + "bar": "foo", + "baz": 3, + } + "#; + + let (settings, result) = crate::fallible_options::parse_json::(&input); + assert_eq!( + settings.unwrap(), + Foo { + foo: Some("bar".into()).into(), + bar: None.into(), + baz: None.into(), + } + ); + + assert!(crate::parse_json_with_comments::(&input).is_err()); + + let ParseStatus::Failed { error } = result else { + panic!("Expected parse to fail") + }; + + assert_eq!( + error, + "invalid type: string \"foo\", expected usize at line 3 column 24\ninvalid type: integer `3`, expected a boolean at line 4 column 20".to_string() + ) + } +} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index fc097d474e92a6..108cca71fe2941 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,5 +1,6 @@ mod base_keymap_setting; mod editable_setting_control; +mod fallible_options; mod keymap_file; pub mod merge_from; mod serde_helper; diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 9cd8ff46e8bea5..230e1ffd48b9cc 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -23,8 +23,7 @@ use gpui::{App, SharedString}; use release_channel::ReleaseChannel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use std::collections::BTreeSet; use std::env; use std::sync::Arc; @@ -32,7 +31,7 @@ pub use util::serde::default_true; use crate::{ActiveSettingsProfileName, merge_from}; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct SettingsContent { #[serde(flatten)] @@ -169,7 +168,7 @@ impl SettingsContent { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct UserSettingsContent { #[serde(flatten)] @@ -260,7 +259,7 @@ impl strum::VariantNames for BaseKeymapContent { ]; } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct TitleBarSettingsContent { /// Whether to show the branch icon beside branch switcher in the title bar. @@ -294,7 +293,7 @@ pub struct TitleBarSettingsContent { } /// Configuration of audio in Zed. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct AudioSettingsContent { /// Opt into the new audio system. @@ -338,7 +337,7 @@ pub struct AudioSettingsContent { } /// Control what info is collected by Zed. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug, MergeFrom)] pub struct TelemetrySettingsContent { /// Send debug info like crash reports. @@ -360,7 +359,7 @@ impl Default for TelemetrySettingsContent { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone, MergeFrom)] pub struct DebuggerSettingsContent { /// Determines the stepping granularity. @@ -441,7 +440,7 @@ pub enum DockPosition { } /// Settings for slash commands. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct SlashCommandSettings { /// Settings for the `/cargo-workspace` slash command. @@ -449,7 +448,7 @@ pub struct SlashCommandSettings { } /// Settings for the `/cargo-workspace` slash command. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct CargoWorkspaceCommandSettings { /// Whether `/cargo-workspace` is enabled. @@ -457,7 +456,7 @@ pub struct CargoWorkspaceCommandSettings { } /// Configuration of voice calls in Zed. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. @@ -471,7 +470,7 @@ pub struct CallSettingsContent { pub share_on_join: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct GitPanelSettingsContent { /// Whether to show the panel button in the status bar. @@ -535,7 +534,7 @@ pub enum StatusStyle { LabelColor, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, )] @@ -543,7 +542,7 @@ pub struct ScrollbarSettings { pub show: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct NotificationPanelSettingsContent { /// Whether to show the panel button in the status bar. @@ -561,7 +560,7 @@ pub struct NotificationPanelSettingsContent { pub default_width: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct PanelSettingsContent { /// Whether to show the panel button in the status bar. @@ -579,7 +578,7 @@ pub struct PanelSettingsContent { pub default_width: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct MessageEditorSettings { /// Whether to automatically replace emoji shortcodes with emoji characters. @@ -589,7 +588,7 @@ pub struct MessageEditorSettings { pub auto_replace_emoji_shortcode: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct FileFinderSettingsContent { /// Whether to show file icons in the file finder. @@ -664,7 +663,7 @@ pub enum FileFinderWidthContent { Full, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)] pub struct VimSettingsContent { pub default_mode: Option, @@ -697,7 +696,7 @@ pub enum UseSystemClipboard { } /// The settings for cursor shape. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] pub struct CursorShapeSettings { /// Cursor shape for the normal mode. @@ -719,7 +718,7 @@ pub struct CursorShapeSettings { } /// Settings specific to journaling -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct JournalSettingsContent { /// The path of the directory where journal entries are stored. @@ -740,7 +739,7 @@ pub enum HourFormat { Hour24, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct OutlinePanelSettingsContent { /// Whether to show the outline panel button in the status bar. @@ -835,7 +834,7 @@ pub enum ShowIndentGuides { Never, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default, )] @@ -853,7 +852,7 @@ pub enum LineIndicatorFormat { } /// The settings for the image viewer. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] pub struct ImageViewerSettingsContent { /// The unit to use for displaying image file sizes. @@ -862,7 +861,7 @@ pub struct ImageViewerSettingsContent { pub unit: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Copy, @@ -885,7 +884,7 @@ pub enum ImageFileSizeUnit { Decimal, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct RemoteSettingsContent { pub ssh_connections: Option>, @@ -893,7 +892,7 @@ pub struct RemoteSettingsContent { pub read_ssh_config: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct SshConnection { pub host: SharedString, @@ -922,7 +921,7 @@ pub struct WslConnection { pub projects: BTreeSet, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Debug, Default, Serialize, PartialEq, Eq, PartialOrd, Ord, Deserialize, JsonSchema, )] @@ -930,19 +929,17 @@ pub struct SshProject { pub paths: Vec, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)] pub struct SshPortForwardOption { - #[serde(skip_serializing_if = "Option::is_none")] pub local_host: Option, pub local_port: u16, - #[serde(skip_serializing_if = "Option::is_none")] pub remote_host: Option, pub remote_port: u16, } /// Settings for configuring REPL display and behavior. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ReplSettingsContent { /// Maximum number of lines to keep in REPL's scrollback buffer. diff --git a/crates/settings/src/settings_content/agent.rs b/crates/settings/src/settings_content/agent.rs index 425b5f05ff46fa..8568309a7b3ff5 100644 --- a/crates/settings/src/settings_content/agent.rs +++ b/crates/settings/src/settings_content/agent.rs @@ -2,13 +2,12 @@ use collections::{HashMap, IndexMap}; use gpui::SharedString; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use std::{borrow::Cow, path::PathBuf, sync::Arc}; use crate::DockPosition; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)] pub struct AgentSettingsContent { /// Whether the Agent is enabled. @@ -166,7 +165,7 @@ impl AgentSettingsContent { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct AgentProfileContent { pub name: Arc, @@ -180,7 +179,7 @@ pub struct AgentProfileContent { pub default_model: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ContextServerPresetContent { pub tools: IndexMap, bool>, @@ -215,7 +214,7 @@ pub enum NotifyWhenAgentWaiting { Never, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct LanguageModelSelection { pub provider: LanguageModelProviderSetting, @@ -231,7 +230,7 @@ pub enum CompletionMode { Burn, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct LanguageModelParameters { pub provider: Option, @@ -290,7 +289,7 @@ impl From<&str> for LanguageModelProviderSetting { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug)] pub struct AllAgentServersSettings { pub gemini: Option, @@ -302,7 +301,7 @@ pub struct AllAgentServersSettings { pub custom: HashMap, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct BuiltinAgentServerSettings { /// Absolute path to a binary to be used when launching this agent. @@ -334,7 +333,7 @@ pub struct BuiltinAgentServerSettings { pub default_mode: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct CustomAgentServerSettings { #[serde(rename = "command")] diff --git a/crates/settings/src/settings_content/editor.rs b/crates/settings/src/settings_content/editor.rs index 4ef5f3e427b8ca..9ec5542e9bc1aa 100644 --- a/crates/settings/src/settings_content/editor.rs +++ b/crates/settings/src/settings_content/editor.rs @@ -4,14 +4,13 @@ use std::num; use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use crate::{ DelayMs, DiagnosticSeverityContent, ShowScrollbar, serialize_f32_with_two_decimal_places, }; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct EditorSettingsContent { /// Whether the cursor blinks in the editor. @@ -254,7 +253,7 @@ impl RelativeLineNumbers { } // Toolbar related settings -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ToolbarContent { /// Whether to display breadcrumbs in the editor toolbar. @@ -281,7 +280,7 @@ pub struct ToolbarContent { } /// Scrollbar related settings -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] pub struct ScrollbarContent { /// When to show the scrollbar in the editor. @@ -317,7 +316,7 @@ pub struct ScrollbarContent { } /// Sticky scroll related settings -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct StickyScrollContent { /// Whether sticky scroll is enabled. @@ -327,7 +326,7 @@ pub struct StickyScrollContent { } /// Minimap related settings -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct MinimapContent { /// When to show the minimap in the editor. @@ -362,7 +361,7 @@ pub struct MinimapContent { } /// Forcefully enable or disable the scrollbar for each axis -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] pub struct ScrollbarAxesContent { /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. @@ -377,7 +376,7 @@ pub struct ScrollbarAxesContent { } /// Gutter related settings -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct GutterContent { /// Whether to show line numbers in the gutter. @@ -754,7 +753,7 @@ pub enum SnippetSortOrder { } /// Default options for buffer and project search items. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct SearchSettingsContent { /// Whether to show the project search button in the status bar. @@ -771,7 +770,7 @@ pub struct SearchSettingsContent { pub center_on_match: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct JupyterContent { @@ -787,7 +786,7 @@ pub struct JupyterContent { } /// Whether to allow drag and drop text selection in buffer. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct DragAndDropSelectionContent { /// When true, enables drag and drop text selection in buffer. diff --git a/crates/settings/src/settings_content/extension.rs b/crates/settings/src/settings_content/extension.rs index f8abb5283ff023..2fefd4ef38aeb9 100644 --- a/crates/settings/src/settings_content/extension.rs +++ b/crates/settings/src/settings_content/extension.rs @@ -3,10 +3,9 @@ use std::sync::Arc; use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ExtensionSettingsContent { /// The extensions that should be automatically installed by Zed. @@ -20,7 +19,6 @@ pub struct ExtensionSettingsContent { #[serde(default)] pub auto_update_extensions: HashMap, bool>, /// The capabilities granted to extensions. - #[serde(default)] pub granted_extension_capabilities: Option>, } diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index fc11dd4956a509..45b1cc0ea3f700 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -4,20 +4,17 @@ use collections::{HashMap, HashSet}; use gpui::{Modifiers, SharedString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use std::sync::Arc; use crate::{ExtendingVec, merge_from}; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] pub struct AllLanguageSettingsContent { /// The settings for enabling/disabling features. - #[serde(default)] pub features: Option, /// The edit prediction settings. - #[serde(default)] pub edit_predictions: Option, /// The default language settings. #[serde(flatten)] @@ -59,7 +56,7 @@ impl merge_from::MergeFrom for AllLanguageSettingsContent { } /// The settings for enabling/disabling features. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct FeaturesContent { @@ -94,7 +91,7 @@ impl EditPredictionProvider { } /// The contents of the edit prediction settings. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct EditPredictionSettingsContent { /// A list of globs representing files that edit predictions should be disabled for. @@ -113,7 +110,7 @@ pub struct EditPredictionSettingsContent { pub enabled_in_text_threads: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct CopilotSettingsContent { /// HTTP/HTTPS proxy to use for Copilot. @@ -206,7 +203,7 @@ pub enum SoftWrap { } /// The settings for a particular language. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageSettingsContent { /// How many columns a tab should occupy. @@ -407,7 +404,7 @@ pub enum ShowWhitespaceSetting { Trailing, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct WhitespaceMapContent { pub space: Option, @@ -439,7 +436,7 @@ pub enum RewrapBehavior { Anywhere, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct JsxTagAutoCloseSettingsContent { /// Enables or disables auto-closing of JSX tags. @@ -447,7 +444,7 @@ pub struct JsxTagAutoCloseSettingsContent { } /// The settings for inlay hints. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct InlayHintSettingsContent { /// Global switch to toggle hints on and off. @@ -529,7 +526,7 @@ impl InlayHintKind { } /// Controls how completions are processed for this language. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub struct CompletionSettingsContent { @@ -614,7 +611,7 @@ pub enum WordsCompletionMode { /// Allows to enable/disable formatting with Prettier /// and configure default Prettier, used when no project-level Prettier installation is found. /// Prettier formatting is disabled by default. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct PrettierSettingsContent { /// Enables or disables formatting with Prettier for a given language. @@ -768,7 +765,7 @@ struct LanguageServerSpecifierContent { } /// The settings for indent guides. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct IndentGuideSettingsContent { /// Whether to display indent guides in the editor. @@ -794,7 +791,7 @@ pub struct IndentGuideSettingsContent { } /// The task settings for a particular language. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, Deserialize, PartialEq, Serialize, JsonSchema, MergeFrom)] pub struct LanguageTaskSettingsContent { /// Extra task variables to set for a particular language. @@ -811,7 +808,7 @@ pub struct LanguageTaskSettingsContent { } /// Map from language name to settings. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageToSettingsMap(pub HashMap); @@ -867,6 +864,9 @@ pub enum IndentGuideBackgroundColoring { #[cfg(test)] mod test { + + use crate::{ParseStatus, fallible_options}; + use super::*; #[test] @@ -926,8 +926,8 @@ mod test { #[test] fn test_formatter_deserialization_invalid() { let raw_auto = "{\"formatter\": {}}"; - let result: Result = serde_json::from_str(raw_auto); - assert!(result.is_err()); + let (_, result) = fallible_options::parse_json::(raw_auto); + assert!(matches!(result, ParseStatus::Failed { .. })); } #[test] diff --git a/crates/settings/src/settings_content/language_model.rs b/crates/settings/src/settings_content/language_model.rs index 50ad812142e154..0a746c1284c1d9 100644 --- a/crates/settings/src/settings_content/language_model.rs +++ b/crates/settings/src/settings_content/language_model.rs @@ -1,12 +1,11 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use std::sync::Arc; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AllLanguageModelSettingsContent { pub anthropic: Option, @@ -25,14 +24,14 @@ pub struct AllLanguageModelSettingsContent { pub zed_dot_dev: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AnthropicSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct AnthropicAvailableModel { /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc @@ -54,7 +53,7 @@ pub struct AnthropicAvailableModel { pub mode: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AmazonBedrockSettingsContent { pub available_models: Option>, @@ -64,7 +63,7 @@ pub struct AmazonBedrockSettingsContent { pub authentication_method: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct BedrockAvailableModel { pub name: String, @@ -88,14 +87,14 @@ pub enum BedrockAuthMethodContent { Automatic, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OllamaSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OllamaAvailableModel { /// The model name in the Ollama API (e.g. "llama3.2:latest") @@ -136,14 +135,14 @@ impl Default for KeepAlive { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct LmStudioSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LmStudioAvailableModel { pub name: String, @@ -153,14 +152,14 @@ pub struct LmStudioAvailableModel { pub supports_images: bool, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct DeepseekSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct DeepseekAvailableModel { pub name: String, @@ -169,14 +168,14 @@ pub struct DeepseekAvailableModel { pub max_output_tokens: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct MistralSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct MistralAvailableModel { pub name: String, @@ -189,14 +188,14 @@ pub struct MistralAvailableModel { pub supports_thinking: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenAiSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiAvailableModel { pub name: String, @@ -216,14 +215,14 @@ pub enum OpenAiReasoningEffort { High, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleSettingsContent { pub api_url: String, pub available_models: Vec, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleAvailableModel { pub name: String, @@ -235,7 +234,7 @@ pub struct OpenAiCompatibleAvailableModel { pub capabilities: OpenAiCompatibleModelCapabilities, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleModelCapabilities { pub tools: bool, @@ -255,14 +254,14 @@ impl Default for OpenAiCompatibleModelCapabilities { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct VercelSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct VercelAvailableModel { pub name: String, @@ -272,14 +271,14 @@ pub struct VercelAvailableModel { pub max_completion_tokens: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct GoogleSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GoogleAvailableModel { pub name: String, @@ -288,14 +287,14 @@ pub struct GoogleAvailableModel { pub mode: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct XAiSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct XaiAvailableModel { pub name: String, @@ -308,13 +307,13 @@ pub struct XaiAvailableModel { pub parallel_tool_calls: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct ZedDotDevSettingsContent { pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ZedDotDevAvailableModel { /// The provider of the language model. @@ -351,14 +350,14 @@ pub enum ZedDotDevAvailableProvider { Google, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenRouterSettingsContent { pub api_url: Option, pub available_models: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenRouterAvailableModel { pub name: String, @@ -372,7 +371,7 @@ pub struct OpenRouterAvailableModel { pub provider: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenRouterProvider { order: Option>, @@ -401,7 +400,7 @@ fn default_true() -> bool { } /// Configuration for caching language model messages. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageModelCacheConfiguration { pub max_cache_anchors: usize, diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs index 1dc5292891819e..55c058467b7c14 100644 --- a/crates/settings/src/settings_content/project.rs +++ b/crates/settings/src/settings_content/project.rs @@ -3,8 +3,7 @@ use std::{path::PathBuf, sync::Arc}; use collections::{BTreeMap, HashMap}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use util::serde::default_true; use crate::{ @@ -12,7 +11,7 @@ use crate::{ SlashCommandSettings, }; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ProjectSettingsContent { #[serde(flatten)] @@ -32,7 +31,6 @@ pub struct ProjectSettingsContent { #[serde(default)] pub lsp: HashMap, LspSettings>, - #[serde(default)] pub terminal: Option, /// Configuration for Debugger-related features @@ -53,15 +51,13 @@ pub struct ProjectSettingsContent { pub git_hosting_providers: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct WorktreeSettingsContent { /// The displayed name of this project. If not set or null, the root directory name /// will be displayed. /// /// Default: null - #[serde(default)] - #[serde(skip_serializing_if = "Option::is_none")] pub project_name: Option, /// Whether to prevent this project from being shared in public channels. @@ -103,7 +99,7 @@ pub struct WorktreeSettingsContent { pub hidden_files: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash)] #[serde(rename_all = "snake_case")] pub struct LspSettings { @@ -140,7 +136,7 @@ impl Default for LspSettings { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash, )] @@ -151,7 +147,7 @@ pub struct BinarySettings { pub ignore_system_version: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash, )] @@ -161,7 +157,7 @@ pub struct FetchSettings { } /// Common language server settings. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GlobalLspSettingsContent { /// Whether to show the LSP servers button in the status bar. @@ -170,18 +166,16 @@ pub struct GlobalLspSettingsContent { pub button: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct DapSettingsContent { pub binary: Option, - #[serde(default)] pub args: Option>, - #[serde(default)] pub env: Option>, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, )] @@ -234,7 +228,7 @@ impl ContextServerSettingsContent { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom)] pub struct ContextServerCommand { #[serde(rename = "command")] @@ -270,7 +264,7 @@ impl std::fmt::Debug for ContextServerCommand { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GitSettings { /// Whether or not to show the git gutter. @@ -320,7 +314,7 @@ pub enum GitGutterSetting { Hide, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct InlineBlameSettings { @@ -349,7 +343,7 @@ pub struct InlineBlameSettings { pub show_commit_summary: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct BlameSettings { @@ -359,7 +353,7 @@ pub struct BlameSettings { pub show_avatar: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct BranchPickerSettingsContent { @@ -391,7 +385,7 @@ pub enum GitHunkStyleSetting { UnstagedHollow, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct DiagnosticsSettingsContent { /// Whether to show the project diagnostics button in the status bar. @@ -407,7 +401,7 @@ pub struct DiagnosticsSettingsContent { pub inline: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, )] @@ -423,7 +417,7 @@ pub struct LspPullDiagnosticsSettingsContent { pub debounce_ms: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Eq, )] @@ -452,7 +446,7 @@ pub struct InlineDiagnosticsSettingsContent { pub max_severity: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct NodeBinarySettings { /// The path to the Node binary. @@ -500,7 +494,7 @@ pub enum DiagnosticSeverityContent { } /// A custom Git hosting provider. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GitHostingProviderConfig { /// The type of the provider. diff --git a/crates/settings/src/settings_content/terminal.rs b/crates/settings/src/settings_content/terminal.rs index 723156bc3ad2d5..c75b986bb81775 100644 --- a/crates/settings/src/settings_content/terminal.rs +++ b/crates/settings/src/settings_content/terminal.rs @@ -4,8 +4,7 @@ use collections::HashMap; use gpui::{AbsoluteLength, FontFeatures, SharedString, px}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use crate::FontFamilyName; @@ -32,7 +31,7 @@ pub struct ProjectTerminalSettingsContent { pub detect_venv: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct TerminalSettingsContent { #[serde(flatten)] @@ -201,7 +200,7 @@ pub enum WorkingDirectory { Always { directory: String }, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default, )] @@ -339,7 +338,7 @@ pub enum AlternateScroll { } // Toolbar related settings -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct TerminalToolbarContent { /// Whether to display the terminal title in breadcrumbs inside the terminal pane. @@ -386,7 +385,7 @@ pub enum VenvSettings { conda_manager: Option, }, } -#[skip_serializing_none] +#[with_fallible_options] pub struct VenvSettingsContent<'a> { pub activate_script: ActivateScript, pub venv_name: &'a str, diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index 8b87cc15196b7a..920e99db56abfa 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/crates/settings/src/settings_content/theme.rs @@ -4,90 +4,72 @@ use schemars::{JsonSchema, JsonSchema_repr}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use std::{fmt::Display, sync::Arc}; -use serde_with::skip_serializing_none; - use crate::serialize_f32_with_two_decimal_places; /// Settings for rendering text in UI and text buffers. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ThemeSettingsContent { /// The default font size for text in the UI. - #[serde(default)] #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] pub ui_font_size: Option, /// The name of a font to use for rendering in the UI. - #[serde(default)] pub ui_font_family: Option, /// 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>, /// The OpenType features to enable for text in the UI. - #[serde(default)] #[schemars(default = "default_font_features")] pub ui_font_features: Option, /// The weight of the UI font in CSS units from 100 to 900. - #[serde(default)] #[schemars(default = "default_buffer_font_weight")] pub ui_font_weight: Option, /// The name of a font to use for rendering in text buffers. - #[serde(default)] pub buffer_font_family: Option, /// The font fallbacks to use for rendering in text buffers. - #[serde(default)] #[schemars(extend("uniqueItems" = true))] pub buffer_font_fallbacks: Option>, /// The default font size for rendering in text buffers. - #[serde(default)] #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] pub buffer_font_size: Option, /// The weight of the editor font in CSS units from 100 to 900. - #[serde(default)] #[schemars(default = "default_buffer_font_weight")] pub buffer_font_weight: Option, /// The buffer's line height. - #[serde(default)] pub buffer_line_height: Option, /// The OpenType features to enable for rendering in text buffers. - #[serde(default)] #[schemars(default = "default_font_features")] pub buffer_font_features: Option, /// The font size for agent responses in the agent panel. Falls back to the UI font size if unset. - #[serde(default)] #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] pub agent_ui_font_size: Option, /// The font size for user messages in the agent panel. - #[serde(default)] #[serde(serialize_with = "crate::serialize_optional_f32_with_two_decimal_places")] pub agent_buffer_font_size: Option, /// The name of the Zed theme to use. - #[serde(default)] pub theme: Option, /// The name of the icon theme to use. - #[serde(default)] pub icon_theme: Option, /// UNSTABLE: Expect many elements to be broken. /// // Controls the density of the UI. - #[serde(rename = "unstable.ui_density", default)] + #[serde(rename = "unstable.ui_density")] pub ui_density: Option, /// How much to fade out unused code. - #[serde(default)] #[schemars(range(min = 0.0, max = 0.9))] pub unnecessary_code_fade: Option, /// 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)] + #[serde(rename = "experimental.theme_overrides")] pub experimental_theme_overrides: Option, /// Overrides per theme @@ -270,7 +252,7 @@ impl UiDensity { } /// Font family name. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct FontFamilyName(pub Arc); @@ -345,11 +327,11 @@ where } /// The content of a serialized theme. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct ThemeStyleContent { - #[serde(default, rename = "background.appearance")] + #[serde(rename = "background.appearance")] pub window_background_appearance: Option, #[serde(default)] @@ -380,18 +362,18 @@ pub struct PlayerColorContent { } /// Theme name. -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct ThemeName(pub Arc); /// Icon Theme Name -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct IconThemeName(pub Arc); -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct ThemeColorsContent { @@ -925,19 +907,27 @@ pub struct ThemeColorsContent { pub vim_mode_text: Option, } -#[skip_serializing_none] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct HighlightStyleContent { pub color: Option, - #[serde(deserialize_with = "treat_error_as_none")] + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "treat_error_as_none" + )] pub background_color: Option, - #[serde(deserialize_with = "treat_error_as_none")] + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "treat_error_as_none" + )] pub font_style: Option, - #[serde(deserialize_with = "treat_error_as_none")] + #[serde( + skip_serializing_if = "Option::is_none", + deserialize_with = "treat_error_as_none" + )] pub font_weight: Option, } @@ -959,7 +949,7 @@ where Ok(T::deserialize(value).ok()) } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct StatusColorsContent { diff --git a/crates/settings/src/settings_content/workspace.rs b/crates/settings/src/settings_content/workspace.rs index fc4c7fdbda553c..f078c873179d2b 100644 --- a/crates/settings/src/settings_content/workspace.rs +++ b/crates/settings/src/settings_content/workspace.rs @@ -3,15 +3,14 @@ use std::num::NonZeroUsize; use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; -use settings_macros::MergeFrom; +use settings_macros::{MergeFrom, with_fallible_options}; use crate::{ CenteredPaddingSettings, DelayMs, DockPosition, DockSide, InactiveOpacity, ScrollbarSettingsContent, ShowIndentGuides, serialize_optional_f32_with_two_decimal_places, }; -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct WorkspaceSettingsContent { /// Active pane styling settings. @@ -112,7 +111,7 @@ pub struct WorkspaceSettingsContent { pub zoomed_padding: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ItemSettingsContent { /// Whether to show the Git file status on a tab item. @@ -142,7 +141,7 @@ pub struct ItemSettingsContent { pub show_close_button: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct PreviewTabsSettingsContent { /// Whether to show opened editors as preview tabs. @@ -244,7 +243,7 @@ pub enum ActivateOnClose { LeftNeighbour, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct ActivePaneModifiers { @@ -350,7 +349,7 @@ pub enum RestoreOnStartupBehavior { LastSession, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct TabBarSettingsContent { /// Whether or not to show the tab bar in the editor. @@ -367,13 +366,13 @@ pub struct TabBarSettingsContent { pub show_tab_bar_buttons: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq, Eq)] pub struct StatusBarSettingsContent { /// Whether to show the status bar. /// /// Default: true - #[serde(rename = "experimental.show", default)] + #[serde(rename = "experimental.show")] pub show: Option, /// Whether to display the active language button in the status bar. /// @@ -465,7 +464,7 @@ pub enum PaneSplitDirectionVertical { #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] #[serde(rename_all = "snake_case")] -#[skip_serializing_none] +#[with_fallible_options] pub struct CenteredLayoutSettings { /// The relative width of the left padding of the central pane from the /// workspace when the centered layout is used. @@ -510,7 +509,7 @@ impl OnLastWindowClosed { } } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct ProjectPanelAutoOpenSettings { /// Whether to automatically open newly created files in the editor. @@ -527,7 +526,7 @@ pub struct ProjectPanelAutoOpenSettings { pub on_drop: Option, } -#[skip_serializing_none] +#[with_fallible_options] #[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct ProjectPanelSettingsContent { /// Whether to show the project panel button in the status bar. @@ -663,7 +662,7 @@ pub enum ProjectPanelSortMode { FilesFirst, } -#[skip_serializing_none] +#[with_fallible_options] #[derive( Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, Default, )] diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 5c4a97fa137323..181b8b417879be 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -32,7 +32,7 @@ pub type EditorconfigProperties = ec4rs::Properties; use crate::{ ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent, - LanguageToSettingsMap, ThemeName, VsCodeSettings, WorktreeId, + LanguageToSettingsMap, ThemeName, VsCodeSettings, WorktreeId, fallible_options, merge_from::MergeFrom, settings_content::{ ExtensionsSettingsContent, ProjectSettingsContent, SettingsContent, UserSettingsContent, @@ -666,44 +666,31 @@ impl SettingsStore { file: SettingsFile, ) -> (Option, SettingsParseResult) { let mut migration_status = MigrationStatus::NotNeeded; - let settings: SettingsContentType = if user_settings_content.is_empty() { - parse_json_with_comments("{}").expect("Empty settings should always be valid") + let (settings, parse_status) = if user_settings_content.is_empty() { + fallible_options::parse_json("{}") } else { let migration_res = migrator::migrate_settings(user_settings_content); - let content = match &migration_res { - Ok(Some(content)) => content, - Ok(None) => user_settings_content, - Err(_) => user_settings_content, - }; - let parse_result = parse_json_with_comments(content); - migration_status = match migration_res { + migration_status = match &migration_res { Ok(Some(_)) => MigrationStatus::Succeeded, Ok(None) => MigrationStatus::NotNeeded, Err(err) => MigrationStatus::Failed { error: err.to_string(), }, }; - match parse_result { - Ok(settings) => settings, - Err(err) => { - let result = SettingsParseResult { - parse_status: ParseStatus::Failed { - error: err.to_string(), - }, - migration_status, - }; - self.file_errors.insert(file, result.clone()); - return (None, result); - } - } + let content = match &migration_res { + Ok(Some(content)) => content, + Ok(None) => user_settings_content, + Err(_) => user_settings_content, + }; + fallible_options::parse_json(content) }; let result = SettingsParseResult { - parse_status: ParseStatus::Success, + parse_status, migration_status, }; self.file_errors.insert(file, result.clone()); - return (Some(settings), result); + return (settings, result); } pub fn error_for_file(&self, file: SettingsFile) -> Option { diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 5644cd7a1a8463..d13d4ba20f87ba 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -183,7 +183,7 @@ impl VsCodeSettings { disable_ai: None, editor: self.editor_settings_content(), extension: ExtensionSettingsContent::default(), - file_finder: None, + file_finder: None.into(), git: self.git_settings_content(), git_panel: self.git_panel_settings_content(), global_lsp_settings: None, diff --git a/crates/settings_macros/src/settings_macros.rs b/crates/settings_macros/src/settings_macros.rs index f6da25d7bc0c3f..9f91cebe0afd3d 100644 --- a/crates/settings_macros/src/settings_macros.rs +++ b/crates/settings_macros/src/settings_macros.rs @@ -1,6 +1,9 @@ use proc_macro::TokenStream; + use quote::quote; -use syn::{Data, DeriveInput, Fields, parse_macro_input}; +use syn::{ + Data, DeriveInput, Field, Fields, ItemEnum, ItemStruct, Type, parse_macro_input, parse_quote, +}; /// Derives the `MergeFrom` trait for a struct. /// @@ -100,3 +103,50 @@ pub fn derive_register_setting(input: TokenStream) -> TokenStream { } .into() } + +// Adds serde attributes to each field with type Option: +// #serde(skip_serializing_if = "Option::is_none", deserialize_with = "settings::deserialize_fallible") +#[proc_macro_attribute] +pub fn with_fallible_options(_args: TokenStream, input: TokenStream) -> TokenStream { + fn apply_on_fields(fields: &mut Fields) { + match fields { + Fields::Unit => {} + Fields::Named(fields) => { + for field in &mut fields.named { + add_if_option(field) + } + } + Fields::Unnamed(fields) => { + for field in &mut fields.unnamed { + add_if_option(field) + } + } + } + } + + fn add_if_option(field: &mut Field) { + match &field.ty { + Type::Path(syn::TypePath { qself: None, path }) + if path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments[0].ident == "Option" => {} + _ => return, + } + let attr = parse_quote!( + #[serde(default, skip_serializing_if = "Option::is_none", deserialize_with="crate::fallible_options::deserialize")] + ); + field.attrs.push(attr); + } + + if let Ok(mut input) = syn::parse::(input.clone()) { + apply_on_fields(&mut input.fields); + quote!(#input).into() + } else if let Ok(mut input) = syn::parse::(input) { + for variant in &mut input.variants { + apply_on_fields(&mut variant.fields); + } + quote!(#input).into() + } else { + panic!("with_fallible_options can only be applied to struct or enum definitions."); + } +} diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index a6baaf94842955..968f7be28d3ef6 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -2525,7 +2525,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec { settings_content .file_finder .get_or_insert_default() - .include_ignored = value; + .include_ignored = value.into(); }, } ), From 4e297b9c48a8ce6d7fc206d3f26660b631ed4e8b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Nov 2025 21:48:30 -0700 Subject: [PATCH 3/6] Tidy up settings error notifications --- crates/settings/src/settings.rs | 2 +- crates/zed/src/main.rs | 11 +-- crates/zed/src/zed.rs | 151 +++++++++++++++----------------- 3 files changed, 73 insertions(+), 91 deletions(-) diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index 108cca71fe2941..5f07ebe52f8997 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -34,7 +34,7 @@ pub use settings_file::*; pub use settings_json::*; pub use settings_store::{ InvalidSettingsError, LocalSettingsKind, MigrationStatus, ParseStatus, Settings, SettingsFile, - SettingsJsonSchemaParams, SettingsKey, SettingsLocation, SettingsStore, + SettingsJsonSchemaParams, SettingsKey, SettingsLocation, SettingsParseResult, SettingsStore, }; pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 9dba1b427d35db..fd3592ec7e9e4b 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -50,8 +50,8 @@ use workspace::{ use zed::{ OpenListener, OpenRequest, RawOpenRequest, app_menus, build_window_options, derive_paths_with_position, edit_prediction_registry, handle_cli_connection, - handle_keymap_file_changes, handle_settings_changed, handle_settings_file_changes, - initialize_workspace, open_paths_with_positions, + handle_keymap_file_changes, handle_settings_file_changes, initialize_workspace, + open_paths_with_positions, }; use crate::zed::{OpenRequestKind, eager_load_active_theme_and_icon_theme}; @@ -411,12 +411,7 @@ pub fn main() { } settings::init(cx); zlog_settings::init(cx); - handle_settings_file_changes( - user_settings_file_rx, - global_settings_file_rx, - cx, - handle_settings_changed, - ); + handle_settings_file_changes(user_settings_file_rx, global_settings_file_rx, cx); handle_keymap_file_changes(user_keymap_file_rx, cx); let user_agent = format!( diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index e90cf59f38e69b..df46794bb83332 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -58,7 +58,7 @@ use rope::Rope; use search::project_search::ProjectSearchBar; use settings::{ BaseKeymap, DEFAULT_KEYMAP_PATH, InvalidSettingsError, KeybindSource, KeymapFile, - KeymapFileLoadResult, Settings, SettingsStore, VIM_KEYMAP_PATH, + KeymapFileLoadResult, MigrationStatus, Settings, SettingsStore, VIM_KEYMAP_PATH, initial_local_debug_tasks_content, initial_project_settings_content, initial_tasks_content, update_settings_file, }; @@ -1363,44 +1363,63 @@ fn open_log_file(workspace: &mut Workspace, window: &mut Window, cx: &mut Contex .detach(); } -pub fn handle_settings_file_changes( - mut user_settings_file_rx: mpsc::UnboundedReceiver, - mut global_settings_file_rx: mpsc::UnboundedReceiver, - cx: &mut App, - settings_changed: impl Fn(Option, &mut App) + 'static, -) { - MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx); +fn notify_settings_errors(result: settings::SettingsParseResult, is_user: bool, cx: &mut App) { + if let settings::ParseStatus::Failed { error: err } = &result.parse_status { + let settings_type = if is_user { "user" } else { "global" }; + log::error!("Failed to load {} settings: {err}", settings_type); + } - // Helper function to process settings content - let process_settings = move |content: String, - is_user: bool, - store: &mut SettingsStore, - cx: &mut App| - -> bool { - let result = if is_user { - store.set_user_settings(&content, cx) - } else { - store.set_global_settings(&content, cx) - }; + let error = match result.parse_status { + settings::ParseStatus::Failed { error } => Some(anyhow::format_err!(error)), + settings::ParseStatus::Success => None, + }; + struct SettingsParseErrorNotification; + let id = NotificationId::unique::(); - let id = NotificationId::Named("failed-to-migrate-settings".into()); - // Apply migrations to both user and global settings - let content_migrated = match result.migration_status { - settings::MigrationStatus::Succeeded => { - dismiss_app_notification(&id, cx); - true - } - settings::MigrationStatus::NotNeeded => { - dismiss_app_notification(&id, cx); + let showed_parse_error = match error { + Some(error) => { + if let Some(InvalidSettingsError::LocalSettings { .. }) = + error.downcast_ref::() + { false + // Local settings errors are displayed by the projects + } else { + show_app_notification(id, cx, move |cx| { + cx.new(|cx| { + MessageNotification::new(format!("Invalid user settings file\n{error}"), cx) + .primary_message("Open Settings File") + .primary_icon(IconName::Settings) + .primary_on_click(|window, cx| { + window.dispatch_action( + zed_actions::OpenSettingsFile.boxed_clone(), + cx, + ); + cx.emit(DismissEvent); + }) + }) + }); + true } - settings::MigrationStatus::Failed { error: err } => { + } + None => { + dismiss_app_notification(&id, cx); + false + } + }; + let id = NotificationId::Named("failed-to-migrate-settings".into()); + + match result.migration_status { + settings::MigrationStatus::Succeeded | settings::MigrationStatus::NotNeeded => { + dismiss_app_notification(&id, cx); + } + settings::MigrationStatus::Failed { error: err } => { + if !showed_parse_error { show_app_notification(id, cx, move |cx| { cx.new(|cx| { MessageNotification::new( format!( "Failed to migrate settings\n\ - {err}" + {err}" ), cx, ) @@ -1412,26 +1431,17 @@ pub fn handle_settings_file_changes( }) }) }); - // notify user here - false } - }; - - if let settings::ParseStatus::Failed { error: err } = &result.parse_status { - let settings_type = if is_user { "user" } else { "global" }; - log::error!("Failed to load {} settings: {err}", settings_type); } - - settings_changed( - match result.parse_status { - settings::ParseStatus::Failed { error } => Some(anyhow::format_err!(error)), - settings::ParseStatus::Success => None, - }, - cx, - ); - - content_migrated }; +} + +pub fn handle_settings_file_changes( + mut user_settings_file_rx: mpsc::UnboundedReceiver, + mut global_settings_file_rx: mpsc::UnboundedReceiver, + cx: &mut App, +) { + MigrationNotification::set_global(cx.new(|_| MigrationNotification), cx); // Initial load of both settings files let global_content = cx @@ -1444,8 +1454,8 @@ pub fn handle_settings_file_changes( .unwrap(); SettingsStore::update_global(cx, |store, cx| { - process_settings(global_content, false, store, cx); - process_settings(user_content, true, store, cx); + notify_settings_errors(store.set_user_settings(&user_content, cx), true, cx); + notify_settings_errors(store.set_global_settings(&global_content, cx), false, cx); }); // Watch for changes in both files @@ -1462,7 +1472,14 @@ pub fn handle_settings_file_changes( }; let result = cx.update_global(|store: &mut SettingsStore, cx| { - let migrating_in_memory = process_settings(content, is_user, store, cx); + let result = if is_user { + store.set_user_settings(&content, cx) + } else { + store.set_global_settings(&content, cx) + }; + let migrating_in_memory = + matches!(&result.migration_status, MigrationStatus::Succeeded); + notify_settings_errors(result, is_user, cx); if let Some(notifier) = MigrationNotification::try_global(cx) { notifier.update(cx, |_, cx| { cx.emit(MigrationEvent::ContentChanged { @@ -1725,36 +1742,6 @@ pub fn load_default_keymap(cx: &mut App) { } } -pub fn handle_settings_changed(error: Option, cx: &mut App) { - struct SettingsParseErrorNotification; - let id = NotificationId::unique::(); - - match error { - Some(error) => { - if let Some(InvalidSettingsError::LocalSettings { .. }) = - error.downcast_ref::() - { - // Local settings errors are displayed by the projects - return; - } - show_app_notification(id, cx, move |cx| { - cx.new(|cx| { - MessageNotification::new(format!("Invalid user settings file\n{error}"), cx) - .primary_message("Open Settings File") - .primary_icon(IconName::Settings) - .primary_on_click(|window, cx| { - window.dispatch_action(zed_actions::OpenSettingsFile.boxed_clone(), cx); - cx.emit(DismissEvent); - }) - }) - }); - } - None => { - dismiss_app_notification(&id, cx); - } - } -} - pub fn open_new_ssh_project_from_project( workspace: &mut Workspace, paths: Vec, @@ -4497,7 +4484,7 @@ mod tests { app_state.fs.clone(), PathBuf::from("/global_settings.json"), ); - handle_settings_file_changes(settings_rx, global_settings_rx, cx, |_, _| {}); + handle_settings_file_changes(settings_rx, global_settings_rx, cx); handle_keymap_file_changes(keymap_rx, cx); }); workspace @@ -4615,7 +4602,7 @@ mod tests { app_state.fs.clone(), PathBuf::from("/global_settings.json"), ); - handle_settings_file_changes(settings_rx, global_settings_rx, cx, |_, _| {}); + handle_settings_file_changes(settings_rx, global_settings_rx, cx); handle_keymap_file_changes(keymap_rx, cx); }); From b9ff8deefda1fcbe5045f3c34695302e0238e20d Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Nov 2025 21:55:59 -0700 Subject: [PATCH 4/6] cliphy --- crates/settings/src/fallible_options.rs | 8 ++++---- crates/settings/src/vscode_import.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/settings/src/fallible_options.rs b/crates/settings/src/fallible_options.rs index 169b59f9d2559a..e0eea451f1fe86 100644 --- a/crates/settings/src/fallible_options.rs +++ b/crates/settings/src/fallible_options.rs @@ -5,7 +5,7 @@ use serde::Deserialize; use crate::ParseStatus; thread_local! { - static ERRORS: RefCell>> = RefCell::new(None); + static ERRORS: RefCell>> = const { RefCell::new(None) }; } pub(crate) fn parse_json<'de, T>(json: &'de str) -> (Option, ParseStatus) @@ -92,9 +92,9 @@ mod tests { assert_eq!( settings.unwrap(), Foo { - foo: Some("bar".into()).into(), - bar: None.into(), - baz: None.into(), + foo: Some("bar".into()), + bar: None, + baz: None, } ); diff --git a/crates/settings/src/vscode_import.rs b/crates/settings/src/vscode_import.rs index 7bd989d6aedf73..f5df817dcd0f4a 100644 --- a/crates/settings/src/vscode_import.rs +++ b/crates/settings/src/vscode_import.rs @@ -183,7 +183,7 @@ impl VsCodeSettings { disable_ai: None, editor: self.editor_settings_content(), extension: ExtensionSettingsContent::default(), - file_finder: None.into(), + file_finder: None, git: self.git_settings_content(), git_panel: self.git_panel_settings_content(), global_lsp_settings: None, From ed8108a35bf48268050596b4e7f3dfaaa6a8ade8 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Nov 2025 21:59:04 -0700 Subject: [PATCH 5/6] docks --- crates/settings_macros/src/settings_macros.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/settings_macros/src/settings_macros.rs b/crates/settings_macros/src/settings_macros.rs index 9f91cebe0afd3d..bad786991da950 100644 --- a/crates/settings_macros/src/settings_macros.rs +++ b/crates/settings_macros/src/settings_macros.rs @@ -105,7 +105,7 @@ pub fn derive_register_setting(input: TokenStream) -> TokenStream { } // Adds serde attributes to each field with type Option: -// #serde(skip_serializing_if = "Option::is_none", deserialize_with = "settings::deserialize_fallible") +// #serde(default, skip_serializing_if = "Option::is_none", deserialize_with = "settings::deserialize_fallible") #[proc_macro_attribute] pub fn with_fallible_options(_args: TokenStream, input: TokenStream) -> TokenStream { fn apply_on_fields(fields: &mut Fields) { From 260bb8d0f9992f2d6cbff812ce678da50d4b3213 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 20 Nov 2025 22:10:36 -0700 Subject: [PATCH 6/6] revert unintended change --- crates/settings_ui/src/page_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/settings_ui/src/page_data.rs b/crates/settings_ui/src/page_data.rs index fccca2c91f12f1..76874c2ad9594c 100644 --- a/crates/settings_ui/src/page_data.rs +++ b/crates/settings_ui/src/page_data.rs @@ -2525,7 +2525,7 @@ pub(crate) fn settings_data(cx: &App) -> Vec { settings_content .file_finder .get_or_insert_default() - .include_ignored = value.into(); + .include_ignored = value; }, } ),