settings: Remove auxiliary Content types where possible (#16744)

Piotr Osiewicz created

Release Notes:

- N/A

Change summary

crates/auto_update/src/auto_update.rs                  |  23 
crates/call/src/call_settings.rs                       |  22 
crates/client/src/client.rs                            |  50 +
crates/collab/src/tests/editor_tests.rs                |   8 
crates/collab/src/tests/following_tests.rs             |   2 
crates/collab_ui/src/chat_panel.rs                     |   2 
crates/collab_ui/src/chat_panel/message_editor.rs      |   8 
crates/collab_ui/src/collab_panel.rs                   |   2 
crates/collab_ui/src/notification_panel.rs             |   2 
crates/collab_ui/src/panel_settings.rs                 |  74 +
crates/diagnostics/src/project_diagnostics_settings.rs |  20 
crates/editor/src/editor.rs                            |   4 
crates/editor/src/editor_settings.rs                   | 318 ++++-------
crates/editor/src/editor_settings_controls.rs          |  22 
crates/editor/src/editor_tests.rs                      |  18 
crates/editor/src/element.rs                           |  17 
crates/extension/src/extension_settings.rs             |  13 
crates/extensions_ui/src/extensions_ui.rs              |   2 
crates/go_to_line/src/cursor_position.rs               |  16 
crates/gpui/src/geometry.rs                            |   2 
crates/language/src/language_settings.rs               |   8 
crates/languages/src/json.rs                           |  29 +
crates/outline_panel/src/outline_panel.rs              |  16 
crates/outline_panel/src/outline_panel_settings.rs     |  72 +-
crates/performance/src/performance.rs                  | 184 ++++++
crates/project/src/project_settings.rs                 |  41 
crates/project_panel/src/project_panel.rs              |  28 
crates/project_panel/src/project_panel_settings.rs     |  92 +--
crates/recent_projects/src/dev_servers.rs              |   3 
crates/recent_projects/src/ssh_connections.rs          |  25 
crates/repl/src/jupyter_settings.rs                    |  28 
crates/tasks_ui/src/settings.rs                        |  18 
crates/vim/src/digraph.rs                              |   2 
crates/vim/src/normal.rs                               |   6 
crates/vim/src/normal/paste.rs                         |  12 
crates/vim/src/normal/scroll.rs                        |   2 
crates/vim/src/normal/search.rs                        |   4 
crates/vim/src/test.rs                                 |   2 
crates/vim/src/test/vim_test_context.rs                |   6 
crates/vim/src/vim.rs                                  |  32 
crates/welcome/src/base_keymap_picker.rs               |   2 
crates/welcome/src/base_keymap_setting.rs              |   6 
crates/welcome/src/welcome.rs                          |   2 
crates/workspace/src/item.rs                           |  70 +-
crates/workspace/src/workspace.rs                      |   8 
crates/workspace/src/workspace_settings.rs             | 132 ++--
crates/worktree/src/worktree_settings.rs               |  43 +
crates/worktree/src/worktree_tests.rs                  |  11 
crates/zed/src/zed.rs                                  |   2 
49 files changed, 829 insertions(+), 682 deletions(-)

Detailed changes

crates/auto_update/src/auto_update.rs 🔗

@@ -116,27 +116,30 @@ impl Drop for MacOsUnmounter {
     }
 }
 
-struct AutoUpdateSetting(bool);
-
 /// Whether or not to automatically check for updates.
-///
-/// Default: true
-#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
+#[derive(Clone, Copy, JsonSchema, Deserialize, Serialize)]
+#[serde(default)]
 #[serde(transparent)]
-struct AutoUpdateSettingContent(bool);
+struct AutoUpdateSetting(bool);
+
+impl Default for AutoUpdateSetting {
+    fn default() -> Self {
+        Self(true)
+    }
+}
 
 impl Settings for AutoUpdateSetting {
     const KEY: Option<&'static str> = Some("auto_update");
 
-    type FileContent = Option<AutoUpdateSettingContent>;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         let auto_update = [sources.release_channel, sources.user]
             .into_iter()
-            .find_map(|value| value.copied().flatten())
-            .unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
+            .find_map(|value| value.copied())
+            .unwrap_or(*sources.default);
 
-        Ok(Self(auto_update.0))
+        Ok(auto_update)
     }
 }
 

crates/call/src/call_settings.rs 🔗

@@ -4,30 +4,20 @@ use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 
-#[derive(Deserialize, Debug)]
-pub struct CallSettings {
-    pub mute_on_join: bool,
-    pub share_on_join: bool,
-}
-
 /// Configuration of voice calls in Zed.
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct CallSettingsContent {
+#[derive(Clone, Debug, Default, Deserialize, Serialize, JsonSchema)]
+#[serde(default)]
+pub struct CallSettings {
     /// Whether the microphone should be muted when joining a channel or a call.
-    ///
-    /// Default: false
-    pub mute_on_join: Option<bool>,
-
+    pub mute_on_join: bool,
     /// Whether your current project should be shared when joining an empty channel.
-    ///
-    /// Default: true
-    pub share_on_join: Option<bool>,
+    pub share_on_join: bool,
 }
 
 impl Settings for CallSettings {
     const KEY: Option<&'static str> = Some("calls");
 
-    type FileContent = CallSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()

crates/client/src/client.rs 🔗

@@ -99,20 +99,26 @@ pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(20);
 
 actions!(client, [SignIn, SignOut, Reconnect]);
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ClientSettingsContent {
-    server_url: Option<String>,
-}
-
-#[derive(Deserialize)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct ClientSettings {
+    /// The server to connect to. If the environment variable
+    /// ZED_SERVER_URL is set, it will override this setting.
     pub server_url: String,
 }
 
+impl Default for ClientSettings {
+    fn default() -> Self {
+        Self {
+            server_url: "https://zed.dev".to_owned(),
+        }
+    }
+}
+
 impl Settings for ClientSettings {
     const KEY: Option<&'static str> = None;
 
-    type FileContent = ClientSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         let mut result = sources.json_merge::<Self>()?;
@@ -124,19 +130,37 @@ impl Settings for ClientSettings {
 }
 
 #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)]
-pub struct ProxySettingsContent {
-    proxy: Option<String>,
-}
-
-#[derive(Deserialize, Default)]
+#[serde(default)]
 pub struct ProxySettings {
+    /// Set a proxy to use. The proxy protocol is specified by the URI scheme.
+    ///
+    /// Supported URI scheme: `http`, `https`, `socks4`, `socks4a`, `socks5`,
+    /// `socks5h`. `http` will be used when no scheme is specified.
+    ///
+    /// By default no proxy will be used, or Zed will try get proxy settings from
+    /// environment variables.
+    ///
+    /// Examples:
+    ///   - "proxy": "socks5://localhost:10808"
+    ///   - "proxy": "http://127.0.0.1:10809"
+    #[schemars(example = "Self::example_1")]
+    #[schemars(example = "Self::example_2")]
     pub proxy: Option<String>,
 }
 
+impl ProxySettings {
+    fn example_1() -> String {
+        "http://127.0.0.1:10809".to_owned()
+    }
+    fn example_2() -> String {
+        "socks5://localhost:10808".to_owned()
+    }
+}
+
 impl Settings for ProxySettings {
     const KEY: Option<&'static str> = None;
 
-    type FileContent = ProxySettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         Ok(Self {

crates/collab/src/tests/editor_tests.rs 🔗

@@ -2261,11 +2261,11 @@ async fn test_git_blame_is_forwarded(cx_a: &mut TestAppContext, cx_b: &mut TestA
     cx_a.update(editor::init);
     cx_b.update(editor::init);
     // Turn inline-blame-off by default so no state is transferred without us explicitly doing so
-    let inline_blame_off_settings = Some(InlineBlameSettings {
+    let inline_blame_off_settings = InlineBlameSettings {
         enabled: false,
-        delay_ms: None,
-        min_column: None,
-    });
+        delay_ms: 0,
+        min_column: 0,
+    };
     cx_a.update(|cx| {
         SettingsStore::update_global(cx, |store, cx| {
             store.update_user_settings::<ProjectSettings>(cx, |settings| {

crates/collab/src/tests/following_tests.rs 🔗

@@ -1649,7 +1649,7 @@ async fn test_following_into_excluded_file(
         cx.update(|cx| {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<WorktreeSettings>(cx, |settings| {
-                    settings.file_scan_exclusions = Some(vec!["**/.git".to_string()]);
+                    settings.file_scan_exclusions = vec!["**/.git".to_string()];
                 });
             });
         });

crates/collab_ui/src/chat_panel.rs 🔗

@@ -1108,7 +1108,7 @@ impl Panel for ChatPanel {
         settings::update_settings_file::<ChatPanelSettings>(
             self.fs.clone(),
             cx,
-            move |settings, _| settings.dock = Some(position),
+            move |settings, _| settings.dock = position,
         );
     }
 

crates/collab_ui/src/chat_panel/message_editor.rs 🔗

@@ -113,9 +113,7 @@ impl MessageEditor {
             editor.set_show_indent_guides(false, cx);
             editor.set_completion_provider(Box::new(MessageEditorCompletionProvider(this)));
             editor.set_auto_replace_emoji_shortcode(
-                MessageEditorSettings::get_global(cx)
-                    .auto_replace_emoji_shortcode
-                    .unwrap_or_default(),
+                MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode,
             );
         });
 
@@ -130,9 +128,7 @@ impl MessageEditor {
         cx.observe_global::<settings::SettingsStore>(|view, cx| {
             view.editor.update(cx, |editor, cx| {
                 editor.set_auto_replace_emoji_shortcode(
-                    MessageEditorSettings::get_global(cx)
-                        .auto_replace_emoji_shortcode
-                        .unwrap_or_default(),
+                    MessageEditorSettings::get_global(cx).auto_replace_emoji_shortcode,
                 )
             })
         })

crates/collab_ui/src/collab_panel.rs 🔗

@@ -2813,7 +2813,7 @@ impl Panel for CollabPanel {
         settings::update_settings_file::<CollaborationPanelSettings>(
             self.fs.clone(),
             cx,
-            move |settings, _| settings.dock = Some(position),
+            move |settings, _| settings.dock = position,
         );
     }
 

crates/collab_ui/src/notification_panel.rs 🔗

@@ -672,7 +672,7 @@ impl Panel for NotificationPanel {
         settings::update_settings_file::<NotificationPanelSettings>(
             self.fs.clone(),
             cx,
-            move |settings, _| settings.dock = Some(position),
+            move |settings, _| settings.dock = position,
         );
     }
 

crates/collab_ui/src/panel_settings.rs 🔗

@@ -2,58 +2,84 @@ use gpui::Pixels;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
+use ui::px;
 use workspace::dock::DockPosition;
 
-#[derive(Deserialize, Debug)]
+#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
+#[serde(default)]
 pub struct CollaborationPanelSettings {
+    /// Whether to show the panel button in the status bar.
     pub button: bool,
+    /// Where to dock the panel.
     pub dock: DockPosition,
+    /// Default width of the panel in pixels.
     pub default_width: Pixels,
 }
 
-#[derive(Deserialize, Debug)]
+impl Default for CollaborationPanelSettings {
+    fn default() -> Self {
+        Self {
+            button: true,
+            dock: DockPosition::Left,
+            default_width: px(240.),
+        }
+    }
+}
+
+#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
+#[serde(default)]
 pub struct ChatPanelSettings {
+    /// Whether to show the panel button in the status bar.
     pub button: bool,
+    /// Where to dock the panel.
     pub dock: DockPosition,
+    /// Default width of the panel in pixels.
     pub default_width: Pixels,
 }
 
-#[derive(Deserialize, Debug)]
+impl Default for ChatPanelSettings {
+    fn default() -> Self {
+        Self {
+            button: true,
+            dock: DockPosition::Right,
+            default_width: px(240.),
+        }
+    }
+}
+
+#[derive(Clone, Deserialize, Debug, JsonSchema, Serialize)]
+#[serde(default)]
 pub struct NotificationPanelSettings {
+    /// Whether to show the panel button in the status bar.
     pub button: bool,
+    /// Where to dock the panel.
     pub dock: DockPosition,
+    /// Default width of the panel in pixels.
     pub default_width: Pixels,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct PanelSettingsContent {
-    /// Whether to show the panel button in the status bar.
-    ///
-    /// Default: true
-    pub button: Option<bool>,
-    /// Where to dock the panel.
-    ///
-    /// Default: left
-    pub dock: Option<DockPosition>,
-    /// Default width of the panel in pixels.
-    ///
-    /// Default: 240
-    pub default_width: Option<f32>,
+impl Default for NotificationPanelSettings {
+    fn default() -> Self {
+        Self {
+            button: true,
+            dock: DockPosition::Right,
+            default_width: px(380.),
+        }
+    }
 }
 
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[serde(default)]
 pub struct MessageEditorSettings {
     /// Whether to automatically replace emoji shortcodes with emoji characters.
     /// For example: typing `:wave:` gets replaced with `👋`.
-    ///
-    /// Default: false
-    pub auto_replace_emoji_shortcode: Option<bool>,
+    pub auto_replace_emoji_shortcode: bool,
 }
 
 impl Settings for CollaborationPanelSettings {
     const KEY: Option<&'static str> = Some("collaboration_panel");
 
-    type FileContent = PanelSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,
@@ -66,7 +92,7 @@ impl Settings for CollaborationPanelSettings {
 impl Settings for ChatPanelSettings {
     const KEY: Option<&'static str> = Some("chat_panel");
 
-    type FileContent = PanelSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,
@@ -79,7 +105,7 @@ impl Settings for ChatPanelSettings {
 impl Settings for NotificationPanelSettings {
     const KEY: Option<&'static str> = Some("notification_panel");
 
-    type FileContent = PanelSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,
@@ -92,7 +118,7 @@ impl Settings for NotificationPanelSettings {
 impl Settings for MessageEditorSettings {
     const KEY: Option<&'static str> = Some("message_editor");
 
-    type FileContent = MessageEditorSettings;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,

crates/diagnostics/src/project_diagnostics_settings.rs 🔗

@@ -4,23 +4,25 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 
-#[derive(Deserialize, Debug)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
+#[serde(default)]
+/// Diagnostics configuration.
 pub struct ProjectDiagnosticsSettings {
+    /// Whether to show warnings or not by default.
     pub include_warnings: bool,
 }
 
-/// Diagnostics configuration.
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ProjectDiagnosticsSettingsContent {
-    /// Whether to show warnings or not by default.
-    ///
-    /// Default: true
-    include_warnings: Option<bool>,
+impl Default for ProjectDiagnosticsSettings {
+    fn default() -> Self {
+        Self {
+            include_warnings: true,
+        }
+    }
 }
 
 impl Settings for ProjectDiagnosticsSettings {
     const KEY: Option<&'static str> = Some("diagnostics");
-    type FileContent = ProjectDiagnosticsSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()

crates/editor/src/editor.rs 🔗

@@ -10639,7 +10639,7 @@ impl Editor {
         let fs = workspace.read(cx).app_state().fs.clone();
         let current_show = TabBarSettings::get_global(cx).show;
         update_settings_file::<TabBarSettings>(fs, cx, move |setting, _| {
-            setting.show = Some(!current_show);
+            setting.show = !current_show;
         });
     }
 
@@ -12562,7 +12562,7 @@ impl EditorSnapshot {
         let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
             matches!(
                 ProjectSettings::get_global(cx).git.git_gutter,
-                Some(GitGutterSetting::TrackedFiles)
+                GitGutterSetting::TrackedFiles
             )
         });
         let gutter_settings = EditorSettings::get_global(cx).gutter;

crates/editor/src/editor_settings.rs 🔗

@@ -3,38 +3,105 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 
-#[derive(Deserialize, Clone)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct EditorSettings {
+    /// Whether the cursor blinks in the editor.
     pub cursor_blink: bool,
+    /// How to highlight the current line in the editor.
     pub current_line_highlight: CurrentLineHighlight,
+    /// Whether to show the informational hover box when moving the mouse
+    /// over symbols in the editor.
     pub hover_popover_enabled: bool,
+    /// Whether to pop the completions menu while typing in an editor without
+    /// explicitly requesting it.
     pub show_completions_on_input: bool,
+    /// Whether to display inline and alongside documentation for items in the
+    /// completions menu.
     pub show_completion_documentation: bool,
+    /// The debounce delay before re-querying the language server for completion
+    /// documentation when not included in original completion list.
     pub completion_documentation_secondary_query_debounce: u64,
+    /// Whether to use additional LSP queries to format (and amend) the code after
+    /// every "trigger" symbol input, defined by LSP server capabilities.
     pub use_on_type_format: bool,
+    /// Toolbar related settings
     pub toolbar: Toolbar,
+    /// Scrollbar related settings
     pub scrollbar: Scrollbar,
+    /// Gutter related settings
     pub gutter: Gutter,
+    /// Whether the editor will scroll beyond the last line.
     pub scroll_beyond_last_line: ScrollBeyondLastLine,
+    /// The number of lines to keep above/below the cursor when auto-scrolling.
     pub vertical_scroll_margin: f32,
+    /// Scroll sensitivity multiplier. This multiplier is applied
+    /// to both the horizontal and vertical delta values while scrolling.
     pub scroll_sensitivity: f32,
+    /// Whether the line numbers on editors gutter are relative or not.
     pub relative_line_numbers: bool,
+    /// When to populate a new search's query based on the text under the cursor.
     pub seed_search_query_from_cursor: SeedQuerySetting,
     pub use_smartcase_search: bool,
+    /// The key to use for adding multiple cursors
     pub multi_cursor_modifier: MultiCursorModifier,
+    /// Hide the values of variables in `private` files, as defined by the
+    /// private_files setting. This only changes the visual representation,
+    /// the values are still present in the file and can be selected / copied / pasted
     pub redact_private_values: bool,
+
+    /// How many lines to expand the multibuffer excerpts by default
     pub expand_excerpt_lines: u32,
     pub middle_click_paste: bool,
+    /// What to do when multibuffer is double clicked in some of its excerpts
+    /// (parts of singleton buffers).
     #[serde(default)]
     pub double_click_in_multibuffer: DoubleClickInMultibuffer,
+    /// Whether the editor search results will loop
     pub search_wrap: bool,
     #[serde(default)]
     pub search: SearchSettings,
+    /// Show method signatures in the editor, when inside parentheses.
     pub auto_signature_help: bool,
+    /// Whether to show the signature help after completion or a bracket pair inserted.
+    /// If `auto_signature_help` is enabled, this setting will be treated as enabled also.
     pub show_signature_help_after_edits: bool,
+    /// Jupyter REPL settings.
     pub jupyter: Jupyter,
 }
 
+impl Default for EditorSettings {
+    fn default() -> Self {
+        Self {
+            cursor_blink: true,
+            current_line_highlight: CurrentLineHighlight::All,
+            hover_popover_enabled: true,
+            show_completions_on_input: true,
+            show_completion_documentation: true,
+            completion_documentation_secondary_query_debounce: 300,
+            use_on_type_format: true,
+            toolbar: Default::default(),
+            scrollbar: Default::default(),
+            gutter: Default::default(),
+            scroll_beyond_last_line: ScrollBeyondLastLine::OnePage,
+            vertical_scroll_margin: 3.,
+            scroll_sensitivity: 1.0,
+            relative_line_numbers: false,
+            seed_search_query_from_cursor: SeedQuerySetting::Always,
+            multi_cursor_modifier: MultiCursorModifier::Alt,
+            redact_private_values: false,
+            expand_excerpt_lines: 3,
+            double_click_in_multibuffer: DoubleClickInMultibuffer::Select,
+            search_wrap: true,
+            auto_signature_help: false,
+            show_signature_help_after_edits: true,
+            jupyter: Default::default(),
+            use_smartcase_search: false,
+            middle_click_paste: true,
+            search: SearchSettings::default(),
+        }
+    }
+}
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum CurrentLineHighlight {
@@ -72,48 +139,93 @@ pub enum DoubleClickInMultibuffer {
     Open,
 }
 
-#[derive(Debug, Clone, Deserialize)]
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
 pub struct Jupyter {
     /// Whether the Jupyter feature is enabled.
-    ///
-    /// Default: true
     pub enabled: bool,
 }
 
-#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
-#[serde(rename_all = "snake_case")]
-pub struct JupyterContent {
-    /// Whether the Jupyter feature is enabled.
-    ///
-    /// Default: true
-    pub enabled: Option<bool>,
+impl Default for Jupyter {
+    fn default() -> Self {
+        Self { enabled: true }
+    }
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(default)]
 pub struct Toolbar {
+    /// Whether to display breadcrumbs in the editor toolbar.
     pub breadcrumbs: bool,
+    /// Whether to display quick action buttons in the editor toolbar.
     pub quick_actions: bool,
+    /// Whether to show the selections menu in the editor toolbar
     pub selections_menu: bool,
 }
 
+impl Default for Toolbar {
+    fn default() -> Self {
+        Self {
+            breadcrumbs: true,
+            quick_actions: true,
+            selections_menu: true,
+        }
+    }
+}
+
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 pub struct Scrollbar {
+    /// When to show the scrollbar in the editor.
     pub show: ShowScrollbar,
+    /// Whether to show git diff indicators in the scrollbar.
     pub git_diff: bool,
+    /// Whether to show buffer search result indicators in the scrollbar.
     pub selected_symbol: bool,
+    /// Whether to show selected symbol occurrences in the scrollbar.
     pub search_results: bool,
+    /// Whether to show diagnostic indicators in the scrollbar.
     pub diagnostics: bool,
+    /// Whether to show cursor positions in the scrollbar.
     pub cursors: bool,
 }
 
+impl Default for Scrollbar {
+    fn default() -> Self {
+        Self {
+            show: ShowScrollbar::Auto,
+            git_diff: true,
+            selected_symbol: true,
+            search_results: true,
+            diagnostics: true,
+            cursors: true,
+        }
+    }
+}
+
+/// Gutter-related settings.
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[serde(default)]
 pub struct Gutter {
+    /// Whether to show line numbers in the gutter.
     pub line_numbers: bool,
+    /// Whether to show code action buttons in the gutter.
     pub code_actions: bool,
+    /// Whether to show runnable buttons in the gutter.
     pub runnables: bool,
+    /// Whether to show fold buttons in the gutter.
     pub folds: bool,
 }
 
+impl Default for Gutter {
+    fn default() -> Self {
+        Self {
+            line_numbers: true,
+            code_actions: true,
+            runnables: true,
+            folds: true,
+        }
+    }
+}
+
 /// When to show the scrollbar in the editor.
 ///
 /// Default: auto
@@ -171,188 +283,6 @@ pub struct SearchSettings {
     pub regex: bool,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct EditorSettingsContent {
-    /// Whether the cursor blinks in the editor.
-    ///
-    /// Default: true
-    pub cursor_blink: Option<bool>,
-    /// How to highlight the current line in the editor.
-    ///
-    /// Default: all
-    pub current_line_highlight: Option<CurrentLineHighlight>,
-    /// Whether to show the informational hover box when moving the mouse
-    /// over symbols in the editor.
-    ///
-    /// Default: true
-    pub hover_popover_enabled: Option<bool>,
-
-    /// Whether to pop the completions menu while typing in an editor without
-    /// explicitly requesting it.
-    ///
-    /// Default: true
-    pub show_completions_on_input: Option<bool>,
-    /// Whether to display inline and alongside documentation for items in the
-    /// completions menu.
-    ///
-    /// Default: true
-    pub show_completion_documentation: Option<bool>,
-    /// The debounce delay before re-querying the language server for completion
-    /// documentation when not included in original completion list.
-    ///
-    /// Default: 300 ms
-    pub completion_documentation_secondary_query_debounce: Option<u64>,
-    /// Whether to use additional LSP queries to format (and amend) the code after
-    /// every "trigger" symbol input, defined by LSP server capabilities.
-    ///
-    /// Default: true
-    pub use_on_type_format: Option<bool>,
-    /// Toolbar related settings
-    pub toolbar: Option<ToolbarContent>,
-    /// Scrollbar related settings
-    pub scrollbar: Option<ScrollbarContent>,
-    /// Gutter related settings
-    pub gutter: Option<GutterContent>,
-    /// Whether the editor will scroll beyond the last line.
-    ///
-    /// Default: one_page
-    pub scroll_beyond_last_line: Option<ScrollBeyondLastLine>,
-    /// The number of lines to keep above/below the cursor when auto-scrolling.
-    ///
-    /// Default: 3.
-    pub vertical_scroll_margin: Option<f32>,
-    /// Scroll sensitivity multiplier. This multiplier is applied
-    /// to both the horizontal and vertical delta values while scrolling.
-    ///
-    /// Default: 1.0
-    pub scroll_sensitivity: Option<f32>,
-    /// Whether the line numbers on editors gutter are relative or not.
-    ///
-    /// Default: false
-    pub relative_line_numbers: Option<bool>,
-    /// When to populate a new search's query based on the text under the cursor.
-    ///
-    /// Default: always
-    pub seed_search_query_from_cursor: Option<SeedQuerySetting>,
-    pub use_smartcase_search: Option<bool>,
-    /// The key to use for adding multiple cursors
-    ///
-    /// Default: alt
-    pub multi_cursor_modifier: Option<MultiCursorModifier>,
-    /// Hide the values of variables in `private` files, as defined by the
-    /// private_files setting. This only changes the visual representation,
-    /// the values are still present in the file and can be selected / copied / pasted
-    ///
-    /// Default: false
-    pub redact_private_values: Option<bool>,
-
-    /// How many lines to expand the multibuffer excerpts by default
-    ///
-    /// Default: 3
-    pub expand_excerpt_lines: Option<u32>,
-
-    /// Whether to enable middle-click paste on Linux
-    ///
-    /// Default: true
-    pub middle_click_paste: Option<bool>,
-
-    /// What to do when multibuffer is double clicked in some of its excerpts
-    /// (parts of singleton buffers).
-    ///
-    /// Default: select
-    pub double_click_in_multibuffer: Option<DoubleClickInMultibuffer>,
-    /// Whether the editor search results will loop
-    ///
-    /// Default: true
-    pub search_wrap: Option<bool>,
-
-    /// Defaults to use when opening a new buffer and project search items.
-    ///
-    /// Default: nothing is enabled
-    pub search: Option<SearchSettings>,
-
-    /// Whether to automatically show a signature help pop-up or not.
-    ///
-    /// Default: false
-    pub auto_signature_help: Option<bool>,
-
-    /// Whether to show the signature help pop-up after completions or bracket pairs inserted.
-    ///
-    /// Default: true
-    pub show_signature_help_after_edits: Option<bool>,
-
-    /// Jupyter REPL settings.
-    pub jupyter: Option<JupyterContent>,
-}
-
-// Toolbar related settings
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct ToolbarContent {
-    /// Whether to display breadcrumbs in the editor toolbar.
-    ///
-    /// Default: true
-    pub breadcrumbs: Option<bool>,
-    /// Whether to display quick action buttons in the editor toolbar.
-    ///
-    /// Default: true
-    pub quick_actions: Option<bool>,
-
-    /// Whether to show the selections menu in the editor toolbar
-    ///
-    /// Default: true
-    pub selections_menu: Option<bool>,
-}
-
-/// Scrollbar related settings
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)]
-pub struct ScrollbarContent {
-    /// When to show the scrollbar in the editor.
-    ///
-    /// Default: auto
-    pub show: Option<ShowScrollbar>,
-    /// Whether to show git diff indicators in the scrollbar.
-    ///
-    /// Default: true
-    pub git_diff: Option<bool>,
-    /// Whether to show buffer search result indicators in the scrollbar.
-    ///
-    /// Default: true
-    pub search_results: Option<bool>,
-    /// Whether to show selected symbol occurrences in the scrollbar.
-    ///
-    /// Default: true
-    pub selected_symbol: Option<bool>,
-    /// Whether to show diagnostic indicators in the scrollbar.
-    ///
-    /// Default: true
-    pub diagnostics: Option<bool>,
-    /// Whether to show cursor positions in the scrollbar.
-    ///
-    /// Default: true
-    pub cursors: Option<bool>,
-}
-
-/// Gutter related settings
-#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct GutterContent {
-    /// Whether to show line numbers in the gutter.
-    ///
-    /// Default: true
-    pub line_numbers: Option<bool>,
-    /// Whether to show code action buttons in the gutter.
-    ///
-    /// Default: true
-    pub code_actions: Option<bool>,
-    /// Whether to show runnable buttons in the gutter.
-    ///
-    /// Default: true
-    pub runnables: Option<bool>,
-    /// Whether to show fold buttons in the gutter.
-    ///
-    /// Default: true
-    pub folds: Option<bool>,
-}
-
 impl EditorSettings {
     pub fn jupyter_enabled(cx: &AppContext) -> bool {
         EditorSettings::get_global(cx).jupyter.enabled
@@ -362,7 +292,7 @@ impl EditorSettings {
 impl Settings for EditorSettings {
     const KEY: Option<&'static str> = None;
 
-    type FileContent = EditorSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,

crates/editor/src/editor_settings_controls.rs 🔗

@@ -1,7 +1,7 @@
 use std::sync::Arc;
 
 use gpui::{AppContext, FontFeatures, FontWeight};
-use project::project_settings::{InlineBlameSettings, ProjectSettings};
+use project::project_settings::ProjectSettings;
 use settings::{EditableSettingControl, Settings};
 use theme::{FontFamilyCache, ThemeSettings};
 use ui::{
@@ -296,14 +296,7 @@ impl EditableSettingControl for InlineGitBlameControl {
         value: Self::Value,
         _cx: &AppContext,
     ) {
-        if let Some(inline_blame) = settings.git.inline_blame.as_mut() {
-            inline_blame.enabled = value;
-        } else {
-            settings.git.inline_blame = Some(InlineBlameSettings {
-                enabled: false,
-                ..Default::default()
-            });
-        }
+        settings.git.inline_blame.enabled = value;
     }
 }
 
@@ -349,14 +342,7 @@ impl EditableSettingControl for LineNumbersControl {
         value: Self::Value,
         _cx: &AppContext,
     ) {
-        if let Some(gutter) = settings.gutter.as_mut() {
-            gutter.line_numbers = Some(value);
-        } else {
-            settings.gutter = Some(crate::editor_settings::GutterContent {
-                line_numbers: Some(value),
-                ..Default::default()
-            });
-        }
+        settings.gutter.line_numbers = value;
     }
 }
 
@@ -402,7 +388,7 @@ impl EditableSettingControl for RelativeLineNumbersControl {
         value: Self::Value,
         _cx: &AppContext,
     ) {
-        settings.relative_line_numbers = Some(value);
+        settings.relative_line_numbers = value;
     }
 }
 

crates/editor/src/editor_tests.rs 🔗

@@ -6964,7 +6964,7 @@ async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|settings, cx| {
             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-                settings.auto_signature_help = Some(true);
+                settings.auto_signature_help = true;
             });
         });
     });
@@ -7105,8 +7105,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|settings, cx| {
             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-                settings.auto_signature_help = Some(false);
-                settings.show_signature_help_after_edits = Some(false);
+                settings.auto_signature_help = false;
+                settings.show_signature_help_after_edits = false;
             });
         });
     });
@@ -7232,8 +7232,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|settings, cx| {
             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-                settings.auto_signature_help = Some(false);
-                settings.show_signature_help_after_edits = Some(true);
+                settings.auto_signature_help = false;
+                settings.show_signature_help_after_edits = true;
             });
         });
     });
@@ -7274,8 +7274,8 @@ async fn test_handle_input_with_different_show_signature_settings(cx: &mut gpui:
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|settings, cx| {
             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-                settings.auto_signature_help = Some(true);
-                settings.show_signature_help_after_edits = Some(false);
+                settings.auto_signature_help = true;
+                settings.show_signature_help_after_edits = false;
             });
         });
     });
@@ -7318,7 +7318,7 @@ async fn test_signature_help(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|settings, cx| {
             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-                settings.auto_signature_help = Some(true);
+                settings.auto_signature_help = true;
             });
         });
     });
@@ -7759,7 +7759,7 @@ async fn test_completion(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|settings, cx| {
             settings.update_user_settings::<EditorSettings>(cx, |settings| {
-                settings.show_completions_on_input = Some(false);
+                settings.show_completions_on_input = false;
             });
         })
     });

crates/editor/src/element.rs 🔗

@@ -1283,10 +1283,7 @@ impl EditorElement {
                 .row,
         );
 
-        let git_gutter_setting = ProjectSettings::get_global(cx)
-            .git
-            .git_gutter
-            .unwrap_or_default();
+        let git_gutter_setting = ProjectSettings::get_global(cx).git.git_gutter;
         let display_hunks = buffer_snapshot
             .git_diff_hunks_in_range(buffer_start_row..buffer_end_row)
             .map(|hunk| diff_hunk_to_display(&hunk, snapshot))
@@ -1366,12 +1363,10 @@ impl EditorElement {
             };
             let padded_line_end = line_end + em_width * INLINE_BLAME_PADDING_EM_WIDTHS;
 
-            let min_column_in_pixels = ProjectSettings::get_global(cx)
-                .git
-                .inline_blame
-                .and_then(|settings| settings.min_column)
-                .map(|col| self.column_pixels(col as usize, cx))
-                .unwrap_or(px(0.));
+            let min_column_in_pixels = self.column_pixels(
+                ProjectSettings::get_global(cx).git.inline_blame.min_column as usize,
+                cx,
+            );
             let min_start = content_origin.x - scroll_pixel_position.x + min_column_in_pixels;
 
             cmp::max(padded_line_end, min_start)
@@ -3331,7 +3326,7 @@ impl EditorElement {
             .unwrap_or_else(|| {
                 matches!(
                     ProjectSettings::get_global(cx).git.git_gutter,
-                    Some(GitGutterSetting::TrackedFiles)
+                    GitGutterSetting::TrackedFiles
                 )
             });
         if show_git_gutter {

crates/extension/src/extension_settings.rs 🔗

@@ -6,18 +6,25 @@ use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 use std::sync::Arc;
 
-#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)]
+#[derive(Deserialize, Serialize, Debug, Clone, JsonSchema)]
+#[serde(default)]
 pub struct ExtensionSettings {
     /// The extensions that should be automatically installed by Zed.
     ///
     /// This is used to make functionality provided by extensions (e.g., language support)
     /// available out-of-the-box.
-    #[serde(default)]
     pub auto_install_extensions: HashMap<Arc<str>, bool>,
-    #[serde(default)]
     pub auto_update_extensions: HashMap<Arc<str>, bool>,
 }
 
+impl Default for ExtensionSettings {
+    fn default() -> Self {
+        Self {
+            auto_install_extensions: HashMap::from_iter([("html".into(), true)]),
+            auto_update_extensions: Default::default(),
+        }
+    }
+}
 impl ExtensionSettings {
     /// Returns whether the given extension should be auto-installed.
     pub fn should_auto_install(&self, extension_id: &str) -> bool {

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -1000,7 +1000,7 @@ impl ExtensionsPage {
                             this.update_settings::<VimModeSetting>(
                                 selection,
                                 cx,
-                                |setting, value| *setting = Some(value),
+                                |setting, value| *setting = VimModeSetting(value),
                             );
                         }),
                     )),

crates/go_to_line/src/cursor_position.rs 🔗

@@ -180,18 +180,10 @@ pub(crate) enum LineIndicatorFormat {
     Long,
 }
 
-/// Whether or not to automatically check for updates.
-///
-/// Values: short, long
-/// Default: short
-#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)]
-#[serde(transparent)]
-pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
-
 impl Settings for LineIndicatorFormat {
     const KEY: Option<&'static str> = Some("line_indicator_format");
 
-    type FileContent = Option<LineIndicatorFormatContent>;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,
@@ -199,9 +191,9 @@ impl Settings for LineIndicatorFormat {
     ) -> anyhow::Result<Self> {
         let format = [sources.release_channel, sources.user]
             .into_iter()
-            .find_map(|value| value.copied().flatten())
-            .unwrap_or(sources.default.ok_or_else(Self::missing_default)?);
+            .find_map(|value| value.copied())
+            .unwrap_or(*sources.default);
 
-        Ok(format.0)
+        Ok(format)
     }
 }

crates/gpui/src/geometry.rs 🔗

@@ -5,6 +5,7 @@
 use core::fmt::Debug;
 use derive_more::{Add, AddAssign, Div, DivAssign, Mul, Neg, Sub, SubAssign};
 use refineable::Refineable;
+use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use std::{
     cmp::{self, PartialOrd},
@@ -2201,6 +2202,7 @@ impl From<Percentage> for Radians {
     PartialEq,
     Serialize,
     Deserialize,
+    JsonSchema,
 )]
 #[repr(transparent)]
 pub struct Pixels(pub f32);

crates/language/src/language_settings.rs 🔗

@@ -70,10 +70,10 @@ pub struct LanguageSettings {
     /// The column at which to soft-wrap lines, for buffers where soft-wrap
     /// is enabled.
     pub preferred_line_length: u32,
-    // Whether to show wrap guides (vertical rulers) in the editor.
-    // Setting this to true will show a guide at the 'preferred_line_length' value
-    // if softwrap is set to 'preferred_line_length', and will show any
-    // additional guides as specified by the 'wrap_guides' setting.
+    /// Whether to show wrap guides (vertical rulers) in the editor.
+    /// Setting this to true will show a guide at the 'preferred_line_length' value
+    /// if softwrap is set to 'preferred_line_length', and will show any
+    /// additional guides as specified by the 'wrap_guides' setting.
     pub show_wrap_guides: bool,
     /// Character counts at which to show wrap guides (vertical rulers) in the editor.
     pub wrap_guides: Vec<usize>,

crates/languages/src/json.rs 🔗

@@ -7,10 +7,13 @@ use feature_flags::FeatureFlagAppExt;
 use futures::StreamExt;
 use gpui::{AppContext, AsyncAppContext};
 use http_client::github::{latest_github_release, GitHubLspBinaryVersion};
-use language::{LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate};
+use language::{
+    CodeLabel, Language, LanguageRegistry, LanguageServerName, LspAdapter, LspAdapterDelegate,
+};
 use lsp::LanguageServerBinary;
 use node_runtime::NodeRuntime;
 use project::ContextProviderWithTasks;
+use rope::Rope;
 use serde_json::{json, Value};
 use settings::{KeymapFile, SettingsJsonSchemaParams, SettingsStore};
 use smol::{
@@ -202,6 +205,30 @@ impl LspAdapter for JsonLspAdapter {
         })))
     }
 
+    async fn label_for_completion(
+        &self,
+        item: &lsp::CompletionItem,
+        language: &Arc<Language>,
+    ) -> Option<CodeLabel> {
+        let text = if let Some(description) = item
+            .label_details
+            .as_ref()
+            .and_then(|label_details| label_details.description.as_ref())
+        {
+            format!("{} {}", item.label, description)
+        } else if let Some(detail) = &item.detail {
+            format!("{} {}", item.label, detail)
+        } else {
+            item.label.clone()
+        };
+        let rope = Rope::from(item.label.as_str());
+        let runs = language.highlight_text(&rope, 0..item.label.len());
+        Some(language::CodeLabel {
+            text,
+            runs,
+            filter_range: 0..item.label.len(),
+        })
+    }
     async fn workspace_configuration(
         self: Arc<Self>,
         _: &Arc<dyn LspAdapterDelegate>,

crates/outline_panel/src/outline_panel.rs 🔗

@@ -24,12 +24,12 @@ use editor::{
 use file_icons::FileIcons;
 use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
 use gpui::{
-    actions, anchored, deferred, div, impl_actions, px, uniform_list, Action, AnyElement,
-    AppContext, AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId,
-    EventEmitter, FocusHandle, FocusableView, HighlightStyle, InteractiveElement, IntoElement,
-    KeyContext, Model, MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render,
-    SharedString, Stateful, Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext,
-    VisualContext, WeakView, WindowContext,
+    actions, anchored, deferred, div, impl_actions, uniform_list, Action, AnyElement, AppContext,
+    AssetSource, AsyncWindowContext, ClipboardItem, DismissEvent, Div, ElementId, EventEmitter,
+    FocusHandle, FocusableView, HighlightStyle, InteractiveElement, IntoElement, KeyContext, Model,
+    MouseButton, MouseDownEvent, ParentElement, Pixels, Point, Render, SharedString, Stateful,
+    Styled, Subscription, Task, UniformListScrollHandle, View, ViewContext, VisualContext,
+    WeakView, WindowContext,
 };
 use itertools::Itertools;
 use language::{BufferId, BufferSnapshot, OffsetRangeExt, OutlineItem};
@@ -1938,7 +1938,7 @@ impl OutlinePanel {
             .child(
                 ListItem::new(item_id)
                     .indent_level(depth)
-                    .indent_step_size(px(settings.indent_size))
+                    .indent_step_size(settings.indent_size)
                     .selected(is_active)
                     .when_some(icon_element, |list_item, icon_element| {
                         list_item.child(h_flex().child(icon_element))
@@ -3801,7 +3801,7 @@ impl Panel for OutlinePanel {
                     DockPosition::Left | DockPosition::Bottom => OutlinePanelDockPosition::Left,
                     DockPosition::Right => OutlinePanelDockPosition::Right,
                 };
-                settings.dock = Some(dock);
+                settings.dock = dock;
             },
         );
     }

crates/outline_panel/src/outline_panel_settings.rs 🔗

@@ -1,4 +1,5 @@
-use gpui::Pixels;
+use anyhow;
+use gpui::{px, Pixels};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
@@ -10,66 +11,51 @@ pub enum OutlinePanelDockPosition {
     Right,
 }
 
-#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
+#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, JsonSchema)]
 pub struct OutlinePanelSettings {
-    pub button: bool,
-    pub default_width: Pixels,
-    pub dock: OutlinePanelDockPosition,
-    pub file_icons: bool,
-    pub folder_icons: bool,
-    pub git_status: bool,
-    pub indent_size: f32,
-    pub auto_reveal_entries: bool,
-    pub auto_fold_dirs: bool,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct OutlinePanelSettingsContent {
     /// Whether to show the outline panel button in the status bar.
-    ///
-    /// Default: true
-    pub button: Option<bool>,
+    pub button: bool,
     /// Customize default width (in pixels) taken by outline panel
-    ///
-    /// Default: 240
-    pub default_width: Option<f32>,
+    pub default_width: Pixels,
     /// The position of outline panel
-    ///
-    /// Default: left
-    pub dock: Option<OutlinePanelDockPosition>,
+    pub dock: OutlinePanelDockPosition,
     /// Whether to show file icons in the outline panel.
-    ///
-    /// Default: true
-    pub file_icons: Option<bool>,
+    pub file_icons: bool,
     /// Whether to show folder icons or chevrons for directories in the outline panel.
-    ///
-    /// Default: true
-    pub folder_icons: Option<bool>,
+    pub folder_icons: bool,
     /// Whether to show the git status in the outline panel.
-    ///
-    /// Default: true
-    pub git_status: Option<bool>,
+    pub git_status: bool,
     /// Amount of indentation (in pixels) for nested items.
-    ///
-    /// Default: 20
-    pub indent_size: Option<f32>,
+    pub indent_size: Pixels,
     /// Whether to reveal it in the outline panel automatically,
     /// when a corresponding project entry becomes active.
     /// Gitignored entries are never auto revealed.
-    ///
-    /// Default: true
-    pub auto_reveal_entries: Option<bool>,
+    pub auto_reveal_entries: bool,
     /// Whether to fold directories automatically
     /// when directory has only one directory inside.
-    ///
-    /// Default: true
-    pub auto_fold_dirs: Option<bool>,
+    pub auto_fold_dirs: bool,
+}
+
+impl Default for OutlinePanelSettings {
+    fn default() -> Self {
+        Self {
+            button: true,
+            default_width: px(240.),
+            dock: OutlinePanelDockPosition::Left,
+            file_icons: true,
+            folder_icons: true,
+            auto_fold_dirs: true,
+            auto_reveal_entries: true,
+            indent_size: px(20.),
+            git_status: true,
+        }
+    }
 }
 
 impl Settings for OutlinePanelSettings {
     const KEY: Option<&'static str> = Some("outline_panel");
 
-    type FileContent = OutlinePanelSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,

crates/performance/src/performance.rs 🔗

@@ -0,0 +1,184 @@
+use std::time::Instant;
+
+use anyhow::Result;
+use gpui::{
+    div, AppContext, InteractiveElement as _, Render, StatefulInteractiveElement as _,
+    Subscription, ViewContext, VisualContext,
+};
+use schemars::JsonSchema;
+use serde::{Deserialize, Serialize};
+use settings::{Settings, SettingsSources, SettingsStore};
+use workspace::{
+    ui::{Label, LabelCommon, LabelSize, Tooltip},
+    ItemHandle, StatusItemView, Workspace,
+};
+
+const SHOW_STARTUP_TIME_DURATION: std::time::Duration = std::time::Duration::from_secs(5);
+
+pub fn init(cx: &mut AppContext) {
+    PerformanceSettings::register(cx);
+
+    let mut enabled = PerformanceSettings::get_global(cx).show_in_status_bar;
+    let start_time = Instant::now();
+    let mut _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx);
+
+    cx.observe_global::<SettingsStore>(move |cx| {
+        let new_value = PerformanceSettings::get_global(cx).show_in_status_bar;
+        if new_value != enabled {
+            enabled = new_value;
+            _observe_workspaces = toggle_status_bar_items(enabled, start_time, cx);
+        }
+    })
+    .detach();
+}
+
+fn toggle_status_bar_items(
+    enabled: bool,
+    start_time: Instant,
+    cx: &mut AppContext,
+) -> Option<Subscription> {
+    for window in cx.windows() {
+        if let Some(workspace) = window.downcast::<Workspace>() {
+            workspace
+                .update(cx, |workspace, cx| {
+                    toggle_status_bar_item(workspace, enabled, start_time, cx);
+                })
+                .ok();
+        }
+    }
+
+    if enabled {
+        log::info!("performance metrics display enabled");
+        Some(cx.observe_new_views::<Workspace>(move |workspace, cx| {
+            toggle_status_bar_item(workspace, true, start_time, cx);
+        }))
+    } else {
+        log::info!("performance metrics display disabled");
+        None
+    }
+}
+
+struct PerformanceStatusBarItem {
+    display_mode: DisplayMode,
+}
+
+#[derive(Copy, Clone, Debug)]
+enum DisplayMode {
+    StartupTime,
+    Fps,
+}
+
+impl PerformanceStatusBarItem {
+    fn new(start_time: Instant, cx: &mut ViewContext<Self>) -> Self {
+        let now = Instant::now();
+        let display_mode = if now < start_time + SHOW_STARTUP_TIME_DURATION {
+            DisplayMode::StartupTime
+        } else {
+            DisplayMode::Fps
+        };
+
+        let this = Self { display_mode };
+
+        if let DisplayMode::StartupTime = display_mode {
+            cx.spawn(|this, mut cx| async move {
+                let now = Instant::now();
+                let remaining_duration =
+                    (start_time + SHOW_STARTUP_TIME_DURATION).saturating_duration_since(now);
+                cx.background_executor().timer(remaining_duration).await;
+                this.update(&mut cx, |this, cx| {
+                    this.display_mode = DisplayMode::Fps;
+                    cx.notify();
+                })
+                .ok();
+            })
+            .detach();
+        }
+
+        this
+    }
+}
+
+impl Render for PerformanceStatusBarItem {
+    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl gpui::IntoElement {
+        let text = match self.display_mode {
+            DisplayMode::StartupTime => cx
+                .time_to_first_window_draw()
+                .map_or("Pending".to_string(), |duration| {
+                    format!("{}ms", duration.as_millis())
+                }),
+            DisplayMode::Fps => cx.fps().map_or("".to_string(), |fps| {
+                format!("{:3} FPS", fps.round() as u32)
+            }),
+        };
+
+        use gpui::ParentElement;
+        let display_mode = self.display_mode;
+        div()
+            .id("performance status")
+            .child(Label::new(text).size(LabelSize::Small))
+            .tooltip(move |cx| match display_mode {
+                DisplayMode::StartupTime => Tooltip::text("Time to first window draw", cx),
+                DisplayMode::Fps => cx
+                    .new_view(|cx| {
+                        let tooltip = Tooltip::new("Current FPS");
+                        if let Some(time_to_first) = cx.time_to_first_window_draw() {
+                            tooltip.meta(format!(
+                                "Time to first window draw: {}ms",
+                                time_to_first.as_millis()
+                            ))
+                        } else {
+                            tooltip
+                        }
+                    })
+                    .into(),
+            })
+    }
+}
+
+impl StatusItemView for PerformanceStatusBarItem {
+    fn set_active_pane_item(
+        &mut self,
+        _active_pane_item: Option<&dyn ItemHandle>,
+        _cx: &mut gpui::ViewContext<Self>,
+    ) {
+        // This is not currently used.
+    }
+}
+
+fn toggle_status_bar_item(
+    workspace: &mut Workspace,
+    enabled: bool,
+    start_time: Instant,
+    cx: &mut ViewContext<Workspace>,
+) {
+    if enabled {
+        workspace.status_bar().update(cx, |bar, cx| {
+            bar.add_right_item(
+                cx.new_view(|cx| PerformanceStatusBarItem::new(start_time, cx)),
+                cx,
+            )
+        });
+    } else {
+        workspace.status_bar().update(cx, |bar, cx| {
+            bar.remove_items_of_type::<PerformanceStatusBarItem>(cx);
+        });
+    }
+}
+
+/// Configuration of the display of performance details.
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
+#[serde(default)]
+pub struct PerformanceSettings {
+    /// Display the time to first window draw and frame rate in the status bar.
+    pub show_in_status_bar: bool,
+}
+
+impl Settings for PerformanceSettings {
+    const KEY: Option<&'static str> = Some("performance");
+
+    type FileContent = Self;
+
+    fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
+        sources.json_merge()
+    }
+}

crates/project/src/project_settings.rs 🔗

@@ -20,6 +20,7 @@ use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId};
 use crate::worktree_store::{WorktreeStore, WorktreeStoreEvent};
 
 #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct ProjectSettings {
     /// Configuration for language servers.
     ///
@@ -41,7 +42,6 @@ pub struct ProjectSettings {
     pub load_direnv: DirenvSettings,
 
     /// Configuration for session-related features
-    #[serde(default)]
     pub session: SessionSettings,
 }
 
@@ -59,36 +59,31 @@ pub enum DirenvSettings {
 }
 
 #[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct GitSettings {
     /// Whether or not to show the git gutter.
     ///
     /// Default: tracked_files
-    pub git_gutter: Option<GitGutterSetting>,
+    pub git_gutter: GitGutterSetting,
     pub gutter_debounce: Option<u64>,
     /// Whether or not to show git blame data inline in
     /// the currently focused line.
     ///
     /// Default: on
-    pub inline_blame: Option<InlineBlameSettings>,
+    pub inline_blame: InlineBlameSettings,
 }
 
 impl GitSettings {
     pub fn inline_blame_enabled(&self) -> bool {
         #[allow(unknown_lints, clippy::manual_unwrap_or_default)]
-        match self.inline_blame {
-            Some(InlineBlameSettings { enabled, .. }) => enabled,
-            _ => false,
-        }
+        self.inline_blame.enabled
     }
 
     pub fn inline_blame_delay(&self) -> Option<Duration> {
-        match self.inline_blame {
-            Some(InlineBlameSettings {
-                delay_ms: Some(delay_ms),
-                ..
-            }) if delay_ms > 0 => Some(Duration::from_millis(delay_ms)),
-            _ => None,
-        }
+        self.inline_blame
+            .delay_ms
+            .gt(&0)
+            .then(|| Duration::from_millis(self.inline_blame.delay_ms))
     }
 }
 
@@ -102,28 +97,34 @@ pub enum GitGutterSetting {
     Hide,
 }
 
-#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "snake_case")]
+#[serde(default)]
 pub struct InlineBlameSettings {
     /// Whether or not to show git blame data inline in
     /// the currently focused line.
     ///
     /// Default: true
-    #[serde(default = "true_value")]
     pub enabled: bool,
     /// Whether to only show the inline blame information
     /// after a delay once the cursor stops moving.
     ///
     /// Default: 0
-    pub delay_ms: Option<u64>,
+    pub delay_ms: u64,
     /// The minimum column number to show the inline blame information at
     ///
     /// Default: 0
-    pub min_column: Option<u32>,
+    pub min_column: u32,
 }
 
-const fn true_value() -> bool {
-    true
+impl Default for InlineBlameSettings {
+    fn default() -> Self {
+        Self {
+            enabled: true,
+            delay_ms: 0,
+            min_column: 0,
+        }
+    }
 }
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]

crates/project_panel/src/project_panel.rs 🔗

@@ -2289,7 +2289,7 @@ impl ProjectPanel {
             .child(
                 ListItem::new(entry_id.to_proto() as usize)
                     .indent_level(depth)
-                    .indent_step_size(px(settings.indent_size))
+                    .indent_step_size(settings.indent_size)
                     .selected(is_marked || is_active)
                     .when_some(canonical_path, |this, path| {
                         this.end_slot::<AnyElement>(
@@ -2817,7 +2817,7 @@ impl Render for DraggedProjectEntryView {
                 this.bg(cx.theme().colors().background).w(self.width).child(
                     ListItem::new(self.selection.entry_id.to_proto() as usize)
                         .indent_level(self.details.depth)
-                        .indent_step_size(px(settings.indent_size))
+                        .indent_step_size(settings.indent_size)
                         .child(if let Some(icon) = &self.details.icon {
                             div().child(Icon::from_path(icon.clone()))
                         } else {
@@ -2855,7 +2855,7 @@ impl Panel for ProjectPanel {
                     DockPosition::Left | DockPosition::Bottom => ProjectPanelDockPosition::Left,
                     DockPosition::Right => ProjectPanelDockPosition::Right,
                 };
-                settings.dock = Some(dock);
+                settings.dock = dock;
             },
         );
     }
@@ -3029,7 +3029,7 @@ mod tests {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
                     worktree_settings.file_scan_exclusions =
-                        Some(vec!["**/.git".to_string(), "**/4/**".to_string()]);
+                        vec!["**/.git".to_string(), "**/4/**".to_string()];
                 });
             });
         });
@@ -4818,10 +4818,10 @@ mod tests {
         cx.update(|cx| {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
-                    worktree_settings.file_scan_exclusions = Some(Vec::new());
+                    worktree_settings.file_scan_exclusions = Vec::new();
                 });
                 store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_reveal_entries = Some(false)
+                    project_panel_settings.auto_reveal_entries = false
                 });
             })
         });
@@ -4940,7 +4940,7 @@ mod tests {
         cx.update(|cx| {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_reveal_entries = Some(true)
+                    project_panel_settings.auto_reveal_entries = true
                 });
             })
         });
@@ -5054,10 +5054,10 @@ mod tests {
         cx.update(|cx| {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
-                    worktree_settings.file_scan_exclusions = Some(Vec::new());
+                    worktree_settings.file_scan_exclusions = Vec::new();
                 });
                 store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_reveal_entries = Some(false)
+                    project_panel_settings.auto_reveal_entries = false
                 });
             })
         });
@@ -5256,7 +5256,7 @@ mod tests {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
                     project_settings.file_scan_exclusions =
-                        Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
+                        vec!["excluded_dir".to_string(), "**/.git".to_string()];
                 });
             });
         });
@@ -5569,10 +5569,10 @@ mod tests {
 
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_fold_dirs = Some(false);
+                    project_panel_settings.auto_fold_dirs = false;
                 });
                 store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
-                    worktree_settings.file_scan_exclusions = Some(Vec::new());
+                    worktree_settings.file_scan_exclusions = Vec::new();
                 });
             });
         });
@@ -5591,10 +5591,10 @@ mod tests {
 
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<ProjectPanelSettings>(cx, |project_panel_settings| {
-                    project_panel_settings.auto_fold_dirs = Some(false);
+                    project_panel_settings.auto_fold_dirs = false;
                 });
                 store.update_user_settings::<WorktreeSettings>(cx, |worktree_settings| {
-                    worktree_settings.file_scan_exclusions = Some(Vec::new());
+                    worktree_settings.file_scan_exclusions = Vec::new();
                 });
             });
         });

crates/project_panel/src/project_panel_settings.rs 🔗

@@ -2,6 +2,7 @@ use gpui::Pixels;
 use schemars::JsonSchema;
 use serde_derive::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
+use ui::px;
 
 #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
 #[serde(rename_all = "snake_case")]
@@ -10,20 +11,50 @@ pub enum ProjectPanelDockPosition {
     Right,
 }
 
-#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
+#[derive(Deserialize, Serialize, Debug, Clone, Copy, PartialEq, JsonSchema)]
+#[serde(default)]
 pub struct ProjectPanelSettings {
+    /// Whether to show the project panel button in the status bar.
     pub button: bool,
+    /// Customize default width (in pixels) taken by project panel
     pub default_width: Pixels,
+    /// The position of project panel
     pub dock: ProjectPanelDockPosition,
+    /// Whether to show file icons in the project panel.
     pub file_icons: bool,
+    /// Whether to show folder icons or chevrons for directories in the project panel.
     pub folder_icons: bool,
+    /// Whether to show the git status in the project panel.
     pub git_status: bool,
-    pub indent_size: f32,
+    /// Amount of indentation (in pixels) for nested items.
+    pub indent_size: Pixels,
+    /// Whether to reveal it in the project panel automatically,
+    /// when a corresponding project entry becomes active.
+    /// Gitignored entries are never auto revealed.
     pub auto_reveal_entries: bool,
+    /// Whether to fold directories automatically
+    /// when directory has only one directory inside.
     pub auto_fold_dirs: bool,
+    /// Scrollbar-related settings
     pub scrollbar: ScrollbarSettings,
 }
 
+impl Default for ProjectPanelSettings {
+    fn default() -> Self {
+        Self {
+            button: true,
+            default_width: px(240.),
+            dock: ProjectPanelDockPosition::Left,
+            file_icons: true,
+            folder_icons: true,
+            git_status: true,
+            indent_size: px(20.),
+            auto_reveal_entries: true,
+            auto_fold_dirs: true,
+            scrollbar: Default::default(),
+        }
+    }
+}
 /// When to show the scrollbar in the project panel.
 ///
 /// Default: always
@@ -37,7 +68,7 @@ pub enum ShowScrollbar {
     Never,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
 pub struct ScrollbarSettings {
     /// When to show the scrollbar in the project panel.
     ///
@@ -45,63 +76,10 @@ pub struct ScrollbarSettings {
     pub show: ShowScrollbar,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
-pub struct ScrollbarSettingsContent {
-    /// When to show the scrollbar in the project panel.
-    ///
-    /// Default: always
-    pub show: Option<ShowScrollbar>,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct ProjectPanelSettingsContent {
-    /// Whether to show the project panel button in the status bar.
-    ///
-    /// Default: true
-    pub button: Option<bool>,
-    /// Customize default width (in pixels) taken by project panel
-    ///
-    /// Default: 240
-    pub default_width: Option<f32>,
-    /// The position of project panel
-    ///
-    /// Default: left
-    pub dock: Option<ProjectPanelDockPosition>,
-    /// Whether to show file icons in the project panel.
-    ///
-    /// Default: true
-    pub file_icons: Option<bool>,
-    /// Whether to show folder icons or chevrons for directories in the project panel.
-    ///
-    /// Default: true
-    pub folder_icons: Option<bool>,
-    /// Whether to show the git status in the project panel.
-    ///
-    /// Default: true
-    pub git_status: Option<bool>,
-    /// Amount of indentation (in pixels) for nested items.
-    ///
-    /// Default: 20
-    pub indent_size: Option<f32>,
-    /// Whether to reveal it in the project panel automatically,
-    /// when a corresponding project entry becomes active.
-    /// Gitignored entries are never auto revealed.
-    ///
-    /// Default: true
-    pub auto_reveal_entries: Option<bool>,
-    /// Whether to fold directories automatically
-    /// when directory has only one directory inside.
-    ///
-    /// Default: false
-    pub auto_fold_dirs: Option<bool>,
-    /// Scrollbar-related settings
-    pub scrollbar: Option<ScrollbarSettingsContent>,
-}
-
 impl Settings for ProjectPanelSettings {
     const KEY: Option<&'static str> = Some("project_panel");
 
-    type FileContent = ProjectPanelSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,

crates/recent_projects/src/dev_servers.rs 🔗

@@ -48,7 +48,6 @@ use workspace::{notifications::DetachAndPromptErr, AppState, ModalView, Workspac
 use crate::open_dev_server_project;
 use crate::ssh_connections::connect_over_ssh;
 use crate::ssh_connections::open_ssh_project;
-use crate::ssh_connections::RemoteSettingsContent;
 use crate::ssh_connections::SshConnection;
 use crate::ssh_connections::SshConnectionModal;
 use crate::ssh_connections::SshProject;
@@ -1024,7 +1023,7 @@ impl DevServerProjects {
     fn update_settings_file(
         &mut self,
         cx: &mut ViewContext<Self>,
-        f: impl FnOnce(&mut RemoteSettingsContent) + Send + Sync + 'static,
+        f: impl FnOnce(&mut SshSettings) + Send + Sync + 'static,
     ) {
         let Some(fs) = self
             .workspace

crates/recent_projects/src/ssh_connections.rs 🔗

@@ -22,8 +22,24 @@ use ui::{
 use util::paths::PathWithPosition;
 use workspace::{AppState, ModalView, Workspace};
 
-#[derive(Deserialize)]
+#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct SshSettings {
+    /// ssh_connections is an array of ssh connections.
+    /// By default this setting is null, which disables the direct ssh connection support.
+    /// You can configure these from `project: Open Remote` in the command palette.
+    /// Zed's ssh support will pull configuration from your ~/.ssh too.
+    /// Examples:
+    /// [
+    ///   {
+    ///     "host": "example-box",
+    ///     "projects": [
+    ///       {
+    ///         "paths": ["/home/user/code/zed"]
+    ///       }
+    ///     ]
+    ///   }
+    /// ]
     pub ssh_connections: Option<Vec<SshConnection>>,
 }
 
@@ -62,15 +78,10 @@ pub struct SshProject {
     pub paths: Vec<String>,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct RemoteSettingsContent {
-    pub ssh_connections: Option<Vec<SshConnection>>,
-}
-
 impl Settings for SshSettings {
     const KEY: Option<&'static str> = None;
 
-    type FileContent = RemoteSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()

crates/repl/src/jupyter_settings.rs 🔗

@@ -6,8 +6,10 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 
-#[derive(Debug, Default)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct JupyterSettings {
+    /// Default kernels to select for each language.
     pub kernel_selections: HashMap<String, String>,
 }
 
@@ -20,26 +22,10 @@ impl JupyterSettings {
     }
 }
 
-#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)]
-pub struct JupyterSettingsContent {
-    /// Default kernels to select for each language.
-    ///
-    /// Default: `{}`
-    pub kernel_selections: Option<HashMap<String, String>>,
-}
-
-impl Default for JupyterSettingsContent {
-    fn default() -> Self {
-        JupyterSettingsContent {
-            kernel_selections: Some(HashMap::new()),
-        }
-    }
-}
-
 impl Settings for JupyterSettings {
     const KEY: Option<&'static str> = Some("jupyter");
 
-    type FileContent = JupyterSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,
@@ -51,10 +37,8 @@ impl Settings for JupyterSettings {
         let mut settings = JupyterSettings::default();
 
         for value in sources.defaults_and_customizations() {
-            if let Some(source) = &value.kernel_selections {
-                for (k, v) in source {
-                    settings.kernel_selections.insert(k.clone(), v.clone());
-                }
+            for (k, v) in &value.kernel_selections {
+                settings.kernel_selections.insert(k.clone(), v.clone());
             }
         }
 

crates/tasks_ui/src/settings.rs 🔗

@@ -2,22 +2,26 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 
-#[derive(Serialize, Deserialize, PartialEq, Default)]
+#[derive(Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
+#[serde(default)]
+/// Task-related settings.
 pub(crate) struct TaskSettings {
+    /// Whether to show task status indicator in the status bar. Default: true
     pub(crate) show_status_indicator: bool,
 }
 
-/// Task-related settings.
-#[derive(Serialize, Deserialize, PartialEq, Default, Clone, JsonSchema)]
-pub(crate) struct TaskSettingsContent {
-    /// Whether to show task status indicator in the status bar. Default: true
-    show_status_indicator: Option<bool>,
+impl Default for TaskSettings {
+    fn default() -> Self {
+        Self {
+            show_status_indicator: true,
+        }
+    }
 }
 
 impl Settings for TaskSettings {
     const KEY: Option<&'static str> = Some("task");
 
-    type FileContent = TaskSettingsContent;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,

crates/vim/src/digraph.rs 🔗

@@ -132,7 +132,7 @@ mod test {
                 let mut custom_digraphs = HashMap::default();
                 custom_digraphs.insert("|-".into(), "⊢".into());
                 custom_digraphs.insert(":)".into(), "👨‍💻".into());
-                s.custom_digraphs = Some(custom_digraphs);
+                s.custom_digraphs = custom_digraphs;
             });
         });
 

crates/vim/src/normal.rs 🔗

@@ -1184,7 +1184,7 @@ mod test {
         let mut cx = VimTestContext::new(cx, true).await;
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_multiline_find = Some(true);
+                s.use_multiline_find = true;
             });
         });
 
@@ -1226,7 +1226,7 @@ mod test {
         let mut cx = VimTestContext::new(cx, true).await;
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_multiline_find = Some(true);
+                s.use_multiline_find = true;
             });
         });
 
@@ -1268,7 +1268,7 @@ mod test {
         let mut cx = VimTestContext::new(cx, true).await;
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_smartcase_find = Some(true);
+                s.use_smartcase_find = true;
             });
         });
 

crates/vim/src/normal/paste.rs 🔗

@@ -291,7 +291,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_system_clipboard = Some(UseSystemClipboard::Never)
+                s.use_system_clipboard = UseSystemClipboard::Never
             });
         });
 
@@ -327,7 +327,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_system_clipboard = Some(UseSystemClipboard::OnYank)
+                s.use_system_clipboard = UseSystemClipboard::OnYank
             });
         });
 
@@ -584,7 +584,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_system_clipboard = Some(UseSystemClipboard::Never)
+                s.use_system_clipboard = UseSystemClipboard::Never
             });
         });
 
@@ -630,7 +630,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_system_clipboard = Some(UseSystemClipboard::Never)
+                s.use_system_clipboard = UseSystemClipboard::Never
             });
         });
 
@@ -659,7 +659,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_system_clipboard = Some(UseSystemClipboard::Never)
+                s.use_system_clipboard = UseSystemClipboard::Never
             });
         });
 
@@ -707,7 +707,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<VimSettings>(cx, |s| {
-                s.use_system_clipboard = Some(UseSystemClipboard::Never)
+                s.use_system_clipboard = UseSystemClipboard::Never
             });
         });
 

crates/vim/src/normal/scroll.rs 🔗

@@ -294,7 +294,7 @@ mod test {
 
         cx.update_global(|store: &mut SettingsStore, cx| {
             store.update_user_settings::<EditorSettings>(cx, |s| {
-                s.scroll_beyond_last_line = Some(ScrollBeyondLastLine::Off)
+                s.scroll_beyond_last_line = ScrollBeyondLastLine::Off
             });
         });
 

crates/vim/src/normal/search.rs 🔗

@@ -542,7 +542,7 @@ mod test {
         let mut cx = VimTestContext::new(cx, true).await;
 
         cx.update_global(|store: &mut SettingsStore, cx| {
-            store.update_user_settings::<EditorSettings>(cx, |s| s.search_wrap = Some(false));
+            store.update_user_settings::<EditorSettings>(cx, |s| s.search_wrap = false);
         });
 
         cx.set_state("ˇhi\nhigh\nhi\n", Mode::Normal);
@@ -655,7 +655,7 @@ mod test {
 
         // check that searching with unable search wrap
         cx.update_global(|store: &mut SettingsStore, cx| {
-            store.update_user_settings::<EditorSettings>(cx, |s| s.search_wrap = Some(false));
+            store.update_user_settings::<EditorSettings>(cx, |s| s.search_wrap = false);
         });
         cx.set_state("aa\nbˇb\ncc\ncc\ncc\n", Mode::Normal);
         cx.simulate_keystrokes("/ c c enter");

crates/vim/src/test.rs 🔗

@@ -1300,7 +1300,7 @@ async fn test_command_alias(cx: &mut gpui::TestAppContext) {
         store.update_user_settings::<WorkspaceSettings>(cx, |s| {
             let mut aliases = HashMap::default();
             aliases.insert("Q".to_string(), "upper".to_string());
-            s.command_aliases = Some(aliases)
+            s.command_aliases = aliases
         });
     });
 

crates/vim/src/test/vim_test_context.rs 🔗

@@ -57,7 +57,7 @@ impl VimTestContext {
     pub fn new_with_lsp(mut cx: EditorLspTestContext, enabled: bool) -> VimTestContext {
         cx.update(|cx| {
             SettingsStore::update_global(cx, |store, cx| {
-                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
+                store.update_user_settings::<VimModeSetting>(cx, |s| *s = VimModeSetting(enabled));
             });
             settings::KeymapFile::load_asset("keymaps/default-macos.json", cx).unwrap();
             if enabled {
@@ -105,7 +105,7 @@ impl VimTestContext {
     pub fn enable_vim(&mut self) {
         self.cx.update(|cx| {
             SettingsStore::update_global(cx, |store, cx| {
-                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true));
+                store.update_user_settings::<VimModeSetting>(cx, |s| *s = VimModeSetting(true));
             });
         })
     }
@@ -113,7 +113,7 @@ impl VimTestContext {
     pub fn disable_vim(&mut self) {
         self.cx.update(|cx| {
             SettingsStore::update_global(cx, |store, cx| {
-                store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false));
+                store.update_user_settings::<VimModeSetting>(cx, |s| *s = VimModeSetting(false));
             });
         })
     }

crates/vim/src/vim.rs 🔗

@@ -46,6 +46,8 @@ use crate::state::ReplayableAction;
 /// Whether or not to enable Vim mode.
 ///
 /// Default: false
+#[derive(Copy, Clone, Default, Deserialize, Serialize, JsonSchema)]
+#[serde(default, transparent)]
 pub struct VimModeSetting(pub bool);
 
 /// An Action to Switch between modes
@@ -99,7 +101,7 @@ pub fn init(cx: &mut AppContext) {
             let fs = workspace.app_state().fs.clone();
             let currently_enabled = Vim::enabled(cx);
             update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
-                *setting = Some(!currently_enabled)
+                *setting = VimModeSetting(!currently_enabled);
             })
         });
 
@@ -1068,12 +1070,10 @@ impl Vim {
 impl Settings for VimModeSetting {
     const KEY: Option<&'static str> = Some("vim_mode");
 
-    type FileContent = Option<bool>;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
-        Ok(Self(sources.user.copied().flatten().unwrap_or(
-            sources.default.ok_or_else(Self::missing_default)?,
-        )))
+        Ok(sources.user.copied().unwrap_or(*sources.default))
     }
 }
 
@@ -1089,7 +1089,8 @@ pub enum UseSystemClipboard {
     OnYank,
 }
 
-#[derive(Deserialize)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 struct VimSettings {
     pub toggle_relative_line_numbers: bool,
     pub use_system_clipboard: UseSystemClipboard,
@@ -1098,19 +1099,22 @@ struct VimSettings {
     pub custom_digraphs: HashMap<String, Arc<str>>,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-struct VimSettingsContent {
-    pub toggle_relative_line_numbers: Option<bool>,
-    pub use_system_clipboard: Option<UseSystemClipboard>,
-    pub use_multiline_find: Option<bool>,
-    pub use_smartcase_find: Option<bool>,
-    pub custom_digraphs: Option<HashMap<String, Arc<str>>>,
+impl Default for VimSettings {
+    fn default() -> Self {
+        Self {
+            toggle_relative_line_numbers: false,
+            use_system_clipboard: UseSystemClipboard::Always,
+            use_multiline_find: false,
+            use_smartcase_find: false,
+            custom_digraphs: Default::default(),
+        }
+    }
 }
 
 impl Settings for VimSettings {
     const KEY: Option<&'static str> = Some("vim");
 
-    type FileContent = VimSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -177,7 +177,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
                 .report_setting_event("keymap", base_keymap.to_string());
 
             update_settings_file::<BaseKeymap>(self.fs.clone(), cx, move |setting, _| {
-                *setting = Some(base_keymap)
+                *setting = base_keymap;
             });
         }
 

crates/welcome/src/base_keymap_setting.rs 🔗

@@ -87,15 +87,15 @@ impl BaseKeymap {
 impl Settings for BaseKeymap {
     const KEY: Option<&'static str> = Some("base_keymap");
 
-    type FileContent = Option<Self>;
+    type FileContent = Self;
 
     fn load(
         sources: SettingsSources<Self::FileContent>,
         _: &mut gpui::AppContext,
     ) -> anyhow::Result<Self> {
-        if let Some(Some(user_value)) = sources.user.copied() {
+        if let Some(user_value) = sources.user.copied() {
             return Ok(user_value);
         }
-        sources.default.ok_or_else(Self::missing_default)
+        Ok(*sources.default)
     }
 }

crates/welcome/src/welcome.rs 🔗

@@ -188,7 +188,7 @@ impl Render for WelcomePage {
                                     this.update_settings::<VimModeSetting>(
                                         selection,
                                         cx,
-                                        |setting, value| *setting = Some(value),
+                                        |setting, value| *setting = VimModeSetting(value),
                                     );
                                 }),
                             ))

crates/workspace/src/item.rs 🔗

@@ -36,20 +36,49 @@ use util::ResultExt;
 
 pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200);
 
-#[derive(Deserialize)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct ItemSettings {
+    /// Whether to show the Git file status on a tab item.
     pub git_status: bool,
+    /// Position of the close button in a tab.
     pub close_position: ClosePosition,
+    /// Whether to show the file icon for a tab.
     pub file_icons: bool,
 }
 
-#[derive(Deserialize)]
+impl Default for ItemSettings {
+    fn default() -> Self {
+        Self {
+            git_status: false,
+            close_position: ClosePosition::Right,
+            file_icons: false,
+        }
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct PreviewTabsSettings {
+    /// Whether to show opened editors as preview tabs.
+    /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
     pub enabled: bool,
+    /// Whether to open tabs in preview mode when selected from the file finder.
     pub enable_preview_from_file_finder: bool,
+    /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
     pub enable_preview_from_code_navigation: bool,
 }
 
+impl Default for PreviewTabsSettings {
+    fn default() -> Self {
+        Self {
+            enabled: true,
+            enable_preview_from_file_finder: false,
+            enable_preview_from_code_navigation: false,
+        }
+    }
+}
+
 #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "lowercase")]
 pub enum ClosePosition {
@@ -67,43 +96,10 @@ impl ClosePosition {
     }
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct ItemSettingsContent {
-    /// Whether to show the Git file status on a tab item.
-    ///
-    /// Default: false
-    git_status: Option<bool>,
-    /// Position of the close button in a tab.
-    ///
-    /// Default: right
-    close_position: Option<ClosePosition>,
-    /// Whether to show the file icon for a tab.
-    ///
-    /// Default: false
-    file_icons: Option<bool>,
-}
-
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct PreviewTabsSettingsContent {
-    /// Whether to show opened editors as preview tabs.
-    /// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
-    ///
-    /// Default: true
-    enabled: Option<bool>,
-    /// Whether to open tabs in preview mode when selected from the file finder.
-    ///
-    /// Default: false
-    enable_preview_from_file_finder: Option<bool>,
-    /// Whether a preview tab gets replaced when code navigation is used to navigate away from the tab.
-    ///
-    /// Default: false
-    enable_preview_from_code_navigation: Option<bool>,
-}
-
 impl Settings for ItemSettings {
     const KEY: Option<&'static str> = Some("tabs");
 
-    type FileContent = ItemSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()
@@ -113,7 +109,7 @@ impl Settings for ItemSettings {
 impl Settings for PreviewTabsSettings {
     const KEY: Option<&'static str> = Some("preview_tabs");
 
-    type FileContent = PreviewTabsSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()

crates/workspace/src/workspace.rs 🔗

@@ -6418,7 +6418,7 @@ mod tests {
         item.update(cx, |item, cx| {
             SettingsStore::update_global(cx, |settings, cx| {
                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-                    settings.autosave = Some(AutosaveSetting::OnWindowChange);
+                    settings.autosave = AutosaveSetting::OnWindowChange;
                 })
             });
             item.is_dirty = true;
@@ -6438,7 +6438,7 @@ mod tests {
             cx.focus_self();
             SettingsStore::update_global(cx, |settings, cx| {
                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                    settings.autosave = AutosaveSetting::OnFocusChange;
                 })
             });
             item.is_dirty = true;
@@ -6461,7 +6461,7 @@ mod tests {
         item.update(cx, |item, cx| {
             SettingsStore::update_global(cx, |settings, cx| {
                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-                    settings.autosave = Some(AutosaveSetting::AfterDelay { milliseconds: 500 });
+                    settings.autosave = AutosaveSetting::AfterDelay { milliseconds: 500 };
                 })
             });
             item.is_dirty = true;
@@ -6480,7 +6480,7 @@ mod tests {
         item.update(cx, |item, cx| {
             SettingsStore::update_global(cx, |settings, cx| {
                 settings.update_user_settings::<WorkspaceSettings>(cx, |settings| {
-                    settings.autosave = Some(AutosaveSetting::OnFocusChange);
+                    settings.autosave = AutosaveSetting::OnFocusChange;
                 })
             });
             item.is_dirty = true;

crates/workspace/src/workspace_settings.rs 🔗

@@ -5,22 +5,58 @@ use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
 use settings::{Settings, SettingsSources};
 
-#[derive(Deserialize)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct WorkspaceSettings {
+    /// Scale by which to zoom the active pane.
+    /// When set to 1.0, the active pane has the same size as others,
+    /// but when set to a larger value, the active pane takes up more space.
     pub active_pane_magnification: f32,
+    /// Direction to split horizontally.
     pub pane_split_direction_horizontal: PaneSplitDirectionHorizontal,
+    /// Direction to split vertically.
     pub pane_split_direction_vertical: PaneSplitDirectionVertical,
+    /// Centered layout related settings.
     pub centered_layout: CenteredLayoutSettings,
+    /// Whether or not to prompt the user to confirm before closing the application.
     pub confirm_quit: bool,
+    /// Whether or not to show the call status icon in the status bar.
     pub show_call_status_icon: bool,
+    /// When to automatically save edited buffers.
     pub autosave: AutosaveSetting,
+    /// Controls previous session restoration in freshly launched Zed instance.
     pub restore_on_startup: RestoreOnStartupBehavior,
+    /// The size of the workspace split drop targets on the outer edges.
+    /// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
     pub drop_target_size: f32,
+    /// Whether to close the window when using 'close active item' on a workspace with no tabs
     pub when_closing_with_no_tabs: CloseWindowWhenNoItems,
+    /// Whether to use the system provided dialogs for Open and Save As.
+    /// When set to false, Zed will use the built-in keyboard-first pickers.
     pub use_system_path_prompts: bool,
+    /// Aliases for the command palette. When you type a key in this map,
+    /// it will be assumed to equal the value.
     pub command_aliases: HashMap<String, String>,
 }
 
+impl Default for WorkspaceSettings {
+    fn default() -> Self {
+        Self {
+            active_pane_magnification: 1.0,
+            pane_split_direction_horizontal: PaneSplitDirectionHorizontal::Up,
+            pane_split_direction_vertical: PaneSplitDirectionVertical::Left,
+            centered_layout: CenteredLayoutSettings::default(),
+            confirm_quit: false,
+            show_call_status_icon: true,
+            autosave: AutosaveSetting::Off,
+            restore_on_startup: RestoreOnStartupBehavior::default(),
+            drop_target_size: 0.2,
+            when_closing_with_no_tabs: CloseWindowWhenNoItems::default(),
+            use_system_path_prompts: true,
+            command_aliases: HashMap::default(),
+        }
+    }
+}
 #[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema)]
 #[serde(rename_all = "snake_case")]
 pub enum CloseWindowWhenNoItems {
@@ -55,77 +91,22 @@ pub enum RestoreOnStartupBehavior {
     LastSession,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct WorkspaceSettingsContent {
-    /// Scale by which to zoom the active pane.
-    /// When set to 1.0, the active pane has the same size as others,
-    /// but when set to a larger value, the active pane takes up more space.
-    ///
-    /// Default: `1.0`
-    pub active_pane_magnification: Option<f32>,
-    // Direction to split horizontally.
-    //
-    // Default: "up"
-    pub pane_split_direction_horizontal: Option<PaneSplitDirectionHorizontal>,
-    // Direction to split vertically.
-    //
-    // Default: "left"
-    pub pane_split_direction_vertical: Option<PaneSplitDirectionVertical>,
-    // Centered layout related settings.
-    pub centered_layout: Option<CenteredLayoutSettings>,
-    /// Whether or not to prompt the user to confirm before closing the application.
-    ///
-    /// Default: false
-    pub confirm_quit: Option<bool>,
-    /// Whether or not to show the call status icon in the status bar.
-    ///
-    /// Default: true
-    pub show_call_status_icon: Option<bool>,
-    /// When to automatically save edited buffers.
-    ///
-    /// Default: off
-    pub autosave: Option<AutosaveSetting>,
-    /// Controls previous session restoration in freshly launched Zed instance.
-    /// Values: none, last_workspace, last_session
-    /// Default: last_session
-    pub restore_on_startup: Option<RestoreOnStartupBehavior>,
-    /// The size of the workspace split drop targets on the outer edges.
-    /// Given as a fraction that will be multiplied by the smaller dimension of the workspace.
-    ///
-    /// Default: `0.2` (20% of the smaller dimension of the workspace)
-    pub drop_target_size: Option<f32>,
-    /// Whether to close the window when using 'close active item' on a workspace with no tabs
-    ///
-    /// Default: auto ("on" on macOS, "off" otherwise)
-    pub when_closing_with_no_tabs: Option<CloseWindowWhenNoItems>,
-    /// Whether to use the system provided dialogs for Open and Save As.
-    /// When set to false, Zed will use the built-in keyboard-first pickers.
-    ///
-    /// Default: true
-    pub use_system_path_prompts: Option<bool>,
-    /// Aliases for the command palette. When you type a key in this map,
-    /// it will be assumed to equal the value.
-    ///
-    /// Default: true
-    pub command_aliases: Option<HashMap<String, String>>,
-}
-
-#[derive(Deserialize)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct TabBarSettings {
+    /// Whether or not to show the tab bar in the editor.
     pub show: bool,
+    /// Whether or not to show the navigation history buttons in the tab bar.
     pub show_nav_history_buttons: bool,
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
-pub struct TabBarSettingsContent {
-    /// Whether or not to show the tab bar in the editor.
-    ///
-    /// Default: true
-    pub show: Option<bool>,
-    /// Whether or not to show the navigation history buttons in the tab bar.
-    ///
-    /// Default: true
-    pub show_nav_history_buttons: Option<bool>,
+impl Default for TabBarSettings {
+    fn default() -> Self {
+        Self {
+            show_nav_history_buttons: true,
+            show: true,
+        }
+    }
 }
 
 #[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
@@ -163,17 +144,26 @@ pub struct CenteredLayoutSettings {
     ///
     /// Default: 0.2
     pub left_padding: Option<f32>,
-    // The relative width of the right padding of the central pane from the
-    // workspace when the centered layout is used.
+    /// The relative width of the right padding of the central pane from the
+    /// workspace when the centered layout is used.
     ///
     /// Default: 0.2
     pub right_padding: Option<f32>,
 }
 
+impl Default for CenteredLayoutSettings {
+    fn default() -> Self {
+        Self {
+            left_padding: Some(0.2),
+            right_padding: Some(0.2),
+        }
+    }
+}
+
 impl Settings for WorkspaceSettings {
     const KEY: Option<&'static str> = None;
 
-    type FileContent = WorkspaceSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()
@@ -183,7 +173,7 @@ impl Settings for WorkspaceSettings {
 impl Settings for TabBarSettings {
     const KEY: Option<&'static str> = Some("tab_bar");
 
-    type FileContent = TabBarSettingsContent;
+    type FileContent = Self;
 
     fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
         sources.json_merge()

crates/worktree/src/worktree_settings.rs 🔗

@@ -25,7 +25,8 @@ impl WorktreeSettings {
     }
 }
 
-#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)]
+#[derive(Clone, Serialize, Deserialize, JsonSchema)]
+#[serde(default)]
 pub struct WorktreeSettingsContent {
     /// Completely ignore files matching globs from `file_scan_exclusions`
     ///
@@ -39,12 +40,42 @@ pub struct WorktreeSettingsContent {
     ///   "**/.classpath",
     ///   "**/.settings"
     /// ]
-    #[serde(default)]
-    pub file_scan_exclusions: Option<Vec<String>>,
+    pub file_scan_exclusions: Vec<String>,
 
     /// Treat the files matching these globs as `.env` files.
     /// Default: [ "**/.env*" ]
-    pub private_files: Option<Vec<String>>,
+    pub private_files: Vec<String>,
+}
+
+impl Default for WorktreeSettingsContent {
+    fn default() -> Self {
+        Self {
+            private_files: [
+                "**/.env*",
+                "**/*.pem",
+                "**/*.key",
+                "**/*.cert",
+                "**/*.crt",
+                "**/secrets.yml",
+            ]
+            .into_iter()
+            .map(str::to_owned)
+            .collect(),
+            file_scan_exclusions: [
+                "**/.git",
+                "**/.svn",
+                "**/.hg",
+                "**/CVS",
+                "**/.DS_Store",
+                "**/Thumbs.db",
+                "**/.classpath",
+                "**/.settings",
+            ]
+            .into_iter()
+            .map(str::to_owned)
+            .collect(),
+        }
+    }
 }
 
 impl Settings for WorktreeSettings {
@@ -57,8 +88,8 @@ impl Settings for WorktreeSettings {
         _: &mut AppContext,
     ) -> anyhow::Result<Self> {
         let result: WorktreeSettingsContent = sources.json_merge()?;
-        let mut file_scan_exclusions = result.file_scan_exclusions.unwrap_or_default();
-        let mut private_files = result.private_files.unwrap_or_default();
+        let mut file_scan_exclusions = result.file_scan_exclusions;
+        let mut private_files = result.private_files;
         file_scan_exclusions.sort();
         private_files.sort();
         Ok(Self {

crates/worktree/src/worktree_tests.rs 🔗

@@ -673,7 +673,7 @@ async fn test_rescan_with_gitignore(cx: &mut TestAppContext) {
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(Vec::new());
+                project_settings.file_scan_exclusions = Vec::new();
             });
         });
     });
@@ -910,7 +910,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
         cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
                 project_settings.file_scan_exclusions =
-                    Some(vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()]);
+                    vec!["**/foo/**".to_string(), "**/.DS_Store".to_string()];
             });
         });
     });
@@ -945,8 +945,7 @@ async fn test_file_scan_exclusions(cx: &mut TestAppContext) {
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions =
-                    Some(vec!["**/node_modules/**".to_string()]);
+                project_settings.file_scan_exclusions = vec!["**/node_modules/**".to_string()];
             });
         });
     });
@@ -1009,11 +1008,11 @@ async fn test_fs_events_in_exclusions(cx: &mut TestAppContext) {
     cx.update(|cx| {
         cx.update_global::<SettingsStore, _>(|store, cx| {
             store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
-                project_settings.file_scan_exclusions = Some(vec![
+                project_settings.file_scan_exclusions = vec![
                     "**/.git".to_string(),
                     "node_modules/".to_string(),
                     "build_output".to_string(),
-                ]);
+                ];
             });
         });
     });

crates/zed/src/zed.rs 🔗

@@ -1996,7 +1996,7 @@ mod tests {
             cx.update_global::<SettingsStore, _>(|store, cx| {
                 store.update_user_settings::<WorktreeSettings>(cx, |project_settings| {
                     project_settings.file_scan_exclusions =
-                        Some(vec!["excluded_dir".to_string(), "**/.git".to_string()]);
+                        vec!["excluded_dir".to_string(), "**/.git".to_string()];
                 });
             });
         });