From 60d17cccd35b919d6226ae86559c4505d7c1e833 Mon Sep 17 00:00:00 2001 From: Ben Kunkle Date: Mon, 1 Sep 2025 17:42:33 -0500 Subject: [PATCH] settings_ui: Move settings UI trait to file content (#37337) Closes #ISSUE Initially, the `SettingsUi` trait was tied to `Settings`, however, given that the `Settings::FileContent` type (which may be the same as the type that implements `Settings`) will be the type that more directly maps to the JSON structure (and therefore have the documentation, correct field names (or `serde` rename attributes), etc) it makes more sense to have the deriving of `SettingsUi` occur on the `FileContent` type rather than the `Settings` type. In order for this to work a relatively important change had to be made to the derive macro, that being that it now "unwraps" options into their inner type, so a field with type `Option` where `Foo: SettingsUi` will treat the field as if it were just `Foo`, expecting there to be a default set in `default.json`. This imposes some restrictions on what `Settings::FileContent` can be as seen in 1e19398 where `FileContent` itself can't be optional without manually implementing `SettingsUi`, as well as introducing some risk that if the `FileContent` type has `serde(default)`, the default value will override the default value from `default.json` in the UI even though it may differ (but it should!). A future PR should probably replace the other settings with `FileContent = Option` (all of which currently have `T == bool`) with wrapper structs and have `KEY = None` so the further niceties `derive(SettingsUi)` will provide such as path renaming, custom UI, auto naming and doc comment extraction can be used. Release Notes: - N/A *or* Added/Fixed/Improved ... --- crates/agent_settings/src/agent_settings.rs | 4 +-- crates/audio/src/audio_settings.rs | 4 +-- crates/auto_update/src/auto_update.rs | 13 ++++----- crates/call/src/call_settings.rs | 4 +-- crates/client/src/client.rs | 12 ++++----- crates/collab_ui/src/panel_settings.rs | 10 +++---- crates/dap/src/debugger_settings.rs | 2 ++ crates/editor/src/editor_settings.rs | 4 +-- .../file_finder/src/file_finder_settings.rs | 4 +-- crates/git_ui/src/git_panel_settings.rs | 4 +-- crates/go_to_line/src/cursor_position.rs | 10 +++---- crates/language/src/language_settings.rs | 4 +-- crates/language_models/src/settings.rs | 4 +-- crates/onboarding/src/base_keymap_picker.rs | 2 +- crates/onboarding/src/basics_page.rs | 2 +- .../src/outline_panel_settings.rs | 4 +-- .../src/project_panel_settings.rs | 4 +-- .../recent_projects/src/remote_connections.rs | 4 +-- crates/repl/src/jupyter_settings.rs | 4 +-- crates/settings/src/base_keymap_setting.rs | 23 +++++++++++----- crates/settings/src/settings_store.rs | 18 ++++++------- crates/settings/src/settings_ui.rs | 23 ++++++++-------- .../src/settings_ui_macros.rs | 27 +++++++++++++++++++ crates/terminal/src/terminal_settings.rs | 4 +-- crates/theme/src/settings.rs | 4 +-- crates/title_bar/src/title_bar_settings.rs | 6 ++--- crates/vim/src/vim.rs | 4 +-- crates/workspace/src/item.rs | 8 +++--- crates/workspace/src/workspace_settings.rs | 10 +++---- crates/worktree/src/worktree_settings.rs | 4 +-- 30 files changed, 136 insertions(+), 94 deletions(-) diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 3e21e18a11ba68726f15d88bec93b95f01f89500..8aebdcd288c8451d9bc391f1fc1598d6098d55af 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -48,7 +48,7 @@ pub enum NotifyWhenAgentWaiting { Never, } -#[derive(Default, Clone, Debug, SettingsUi)] +#[derive(Default, Clone, Debug)] pub struct AgentSettings { pub enabled: bool, pub button: bool, @@ -223,7 +223,7 @@ impl AgentSettingsContent { } } -#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default)] +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi)] pub struct AgentSettingsContent { /// Whether the Agent is enabled. /// diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index e42918825cd3a25bb18d6f0b357801949520833f..d30d950273f2138f3bd54c573513373574f1ce43 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -4,7 +4,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] pub struct AudioSettings { /// Opt into the new audio system. #[serde(rename = "experimental.rodio_audio", default)] @@ -12,7 +12,7 @@ pub struct AudioSettings { } /// Configuration of audio in Zed. -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] #[serde(default)] pub struct AudioSettingsContent { /// Whether to use the experimental audio system diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 71dcf25aeea9d8ebd4feb01db9161dc177fcdd26..f0ae3fdb1cfef667a9f737aa6545a42046a9d322 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -113,20 +113,19 @@ impl Drop for MacOsUnmounter { } } -#[derive(SettingsUi)] struct AutoUpdateSetting(bool); /// Whether or not to automatically check for updates. /// /// Default: true -#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)] +#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi)] #[serde(transparent)] struct AutoUpdateSettingContent(bool); impl Settings for AutoUpdateSetting { const KEY: Option<&'static str> = Some("auto_update"); - type FileContent = Option; + type FileContent = AutoUpdateSettingContent; fn load(sources: SettingsSources, _: &mut App) -> Result { let auto_update = [ @@ -136,17 +135,19 @@ impl Settings for AutoUpdateSetting { 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)) } fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) { - vscode.enum_setting("update.mode", current, |s| match s { + let mut cur = &mut Some(*current); + vscode.enum_setting("update.mode", &mut cur, |s| match s { "none" | "manual" => Some(AutoUpdateSettingContent(false)), _ => Some(AutoUpdateSettingContent(true)), }); + *current = cur.unwrap(); } } diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index 64d11d0df64eedbbc29f06b8205f0318d999ea30..7b0838e3a96185c1e4c33b8116fbd6a41b35f3dc 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -4,14 +4,14 @@ use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; -#[derive(Deserialize, Debug, SettingsUi)] +#[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)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. /// diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index c5bb1af0d7605cfcfc28d86bc389189d653e28ae..1287b4563c99cbd387b3a18d98fbbc734e55e4db 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -96,12 +96,12 @@ actions!( ] ); -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct ClientSettingsContent { server_url: Option, } -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] pub struct ClientSettings { pub server_url: String, } @@ -122,12 +122,12 @@ impl Settings for ClientSettings { fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {} } -#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct ProxySettingsContent { proxy: Option, } -#[derive(Deserialize, Default, SettingsUi)] +#[derive(Deserialize, Default)] pub struct ProxySettings { pub proxy: Option, } @@ -520,14 +520,14 @@ impl Drop for PendingEntitySubscription { } } -#[derive(Copy, Clone, Deserialize, Debug, SettingsUi)] +#[derive(Copy, Clone, Deserialize, Debug)] pub struct TelemetrySettings { pub diagnostics: bool, pub metrics: bool, } /// Control what info is collected by Zed. -#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct TelemetrySettingsContent { /// Send debug info like crash reports. /// diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index 4e5c8ad8f005d00a8802ab0a1f79ff7fbb3d0861..64f0a9366df7cdef1f2c05809752fb1cf912111b 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; use workspace::dock::DockPosition; -#[derive(Deserialize, Debug, SettingsUi)] +#[derive(Deserialize, Debug)] pub struct CollaborationPanelSettings { pub button: bool, pub dock: DockPosition, @@ -20,14 +20,14 @@ pub enum ChatPanelButton { WhenInCall, } -#[derive(Deserialize, Debug, SettingsUi)] +#[derive(Deserialize, Debug)] pub struct ChatPanelSettings { pub button: ChatPanelButton, pub dock: DockPosition, pub default_width: Pixels, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct ChatPanelSettingsContent { /// When to show the panel button in the status bar. /// @@ -43,14 +43,14 @@ pub struct ChatPanelSettingsContent { pub default_width: Option, } -#[derive(Deserialize, Debug, SettingsUi)] +#[derive(Deserialize, Debug)] pub struct NotificationPanelSettings { pub button: bool, pub dock: DockPosition, pub default_width: Pixels, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct PanelSettingsContent { /// Whether to show the panel button in the status bar. /// diff --git a/crates/dap/src/debugger_settings.rs b/crates/dap/src/debugger_settings.rs index 6843f19e3811967084cc61a3874ec86451ab6faf..929bff747e8685ec9a4b36fa9db63d12a769faa2 100644 --- a/crates/dap/src/debugger_settings.rs +++ b/crates/dap/src/debugger_settings.rs @@ -14,6 +14,8 @@ pub enum DebugPanelDockPosition { #[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUi)] #[serde(default)] +// todo(settings_ui) @ben: I'm pretty sure not having the fields be optional here is a bug, +// it means the defaults will override previously set values if a single key is missing #[settings_ui(group = "Debugger", path = "debugger")] pub struct DebuggerSettings { /// Determines the stepping granularity. diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 084c4eb5c618cbf3d290b317b0035f1b8f307b3f..3e4e86f023530b89fa3e325474ecea801ff06ecb 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -11,7 +11,7 @@ use util::serde::default_true; /// Imports from the VSCode settings at /// https://code.visualstudio.com/docs/reference/default-settings -#[derive(Deserialize, Clone, SettingsUi)] +#[derive(Deserialize, Clone)] pub struct EditorSettings { pub cursor_blink: bool, pub cursor_shape: Option, @@ -415,7 +415,7 @@ pub enum SnippetSortOrder { None, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct EditorSettingsContent { /// Whether the cursor blinks in the editor. /// diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 20057417a2ddbce7acd7fd5a8e09e54aab779638..007af53b1144ed4caa7985d75cdf4707f13ed13e 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -3,7 +3,7 @@ use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; -#[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUi)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct FileFinderSettings { pub file_icons: bool, pub modal_max_width: Option, @@ -11,7 +11,7 @@ pub struct FileFinderSettings { pub include_ignored: Option, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct FileFinderSettingsContent { /// Whether to show file icons in the file finder. /// diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index 576949220405e408df1b23d189e661405c4c39e4..39d6540db52046845521a23c0290be4e6e595492 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -36,7 +36,7 @@ pub enum StatusStyle { LabelColor, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct GitPanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -77,7 +77,7 @@ pub struct GitPanelSettingsContent { pub collapse_untracked_diff: Option, } -#[derive(Deserialize, Debug, Clone, PartialEq, SettingsUi)] +#[derive(Deserialize, Debug, Clone, PartialEq)] pub struct GitPanelSettings { pub button: bool, pub dock: DockPosition, diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 345af8a867c6ff6c1790450d2b28cd275c04ebbb..5840993ece84b1e8098ee341395e7f77fb791ace 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -293,7 +293,7 @@ impl StatusItemView for CursorPosition { } } -#[derive(Clone, Copy, Default, PartialEq, JsonSchema, Deserialize, Serialize, SettingsUi)] +#[derive(Clone, Copy, Default, PartialEq, JsonSchema, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub(crate) enum LineIndicatorFormat { Short, @@ -301,14 +301,14 @@ pub(crate) enum LineIndicatorFormat { Long, } -#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize)] +#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi)] #[serde(transparent)] pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat); impl Settings for LineIndicatorFormat { const KEY: Option<&'static str> = Some("line_indicator_format"); - type FileContent = Option; + type FileContent = LineIndicatorFormatContent; fn load(sources: SettingsSources, _: &mut App) -> anyhow::Result { let format = [ @@ -317,8 +317,8 @@ impl Settings for LineIndicatorFormat { 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) } diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index a44df4993af5f29cbfce337d2c90dd8f840d97a6..f04b83bc7336143672647a07107fa27bc55f5823 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -55,7 +55,7 @@ pub fn all_language_settings<'a>( } /// The settings for all languages. -#[derive(Debug, Clone, SettingsUi)] +#[derive(Debug, Clone)] pub struct AllLanguageSettings { /// The edit prediction settings. pub edit_predictions: EditPredictionSettings, @@ -292,7 +292,7 @@ pub struct CopilotSettings { } /// The settings for all languages. -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct AllLanguageSettingsContent { /// The settings for enabling/disabling features. #[serde(default)] diff --git a/crates/language_models/src/settings.rs b/crates/language_models/src/settings.rs index 1d03ab48f7de3ab9a20c1a099803e6b759b8ea81..8b7ab5fc2547bd0b014238739f1b940dad831f66 100644 --- a/crates/language_models/src/settings.rs +++ b/crates/language_models/src/settings.rs @@ -29,7 +29,7 @@ pub fn init_settings(cx: &mut App) { AllLanguageModelSettings::register(cx); } -#[derive(Default, SettingsUi)] +#[derive(Default)] pub struct AllLanguageModelSettings { pub anthropic: AnthropicSettings, pub bedrock: AmazonBedrockSettings, @@ -46,7 +46,7 @@ pub struct AllLanguageModelSettings { pub zed_dot_dev: ZedDotDevSettings, } -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, SettingsUi)] pub struct AllLanguageModelSettingsContent { pub anthropic: Option, pub bedrock: Option, diff --git a/crates/onboarding/src/base_keymap_picker.rs b/crates/onboarding/src/base_keymap_picker.rs index 0ac07d9a9d3b17921112d6accf6f4c9c9dd65ef6..79a716cc9eaf36a77668900f63ba42a896a334eb 100644 --- a/crates/onboarding/src/base_keymap_picker.rs +++ b/crates/onboarding/src/base_keymap_picker.rs @@ -187,7 +187,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate { ); update_settings_file::(self.fs.clone(), cx, move |setting, _| { - *setting = Some(base_keymap) + setting.base_keymap = Some(base_keymap) }); } diff --git a/crates/onboarding/src/basics_page.rs b/crates/onboarding/src/basics_page.rs index 441d2ca4b748e43c8c5db41a113c54e185b2f1de..991386cb389b1ac5bdeb2e76bae4a210fe3b2cce 100644 --- a/crates/onboarding/src/basics_page.rs +++ b/crates/onboarding/src/basics_page.rs @@ -325,7 +325,7 @@ fn render_base_keymap_section(tab_index: &mut isize, cx: &mut App) -> impl IntoE let fs = ::global(cx); update_settings_file::(fs, cx, move |setting, _| { - *setting = Some(keymap_base); + setting.base_keymap = Some(keymap_base); }); } } diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index c33125654f043022bfaa7a31200d43d1d6326607..48c6621e3509c1eda69a6a5e92602ba2ab12a484 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -18,7 +18,7 @@ pub enum ShowIndentGuides { Never, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUi)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct OutlinePanelSettings { pub button: bool, pub default_width: Pixels, @@ -61,7 +61,7 @@ pub struct IndentGuidesSettingsContent { pub show: Option, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct OutlinePanelSettingsContent { /// Whether to show the outline panel button in the status bar. /// diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 9c7bd4fd66e9e5b884867bf13f88856c126974b6..db9b2b85d545e85a0cff3ec13a8f75e28dac88fa 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -28,7 +28,7 @@ pub enum EntrySpacing { Standard, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUi)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct ProjectPanelSettings { pub button: bool, pub hide_gitignore: bool, @@ -92,7 +92,7 @@ pub enum ShowDiagnostics { All, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct ProjectPanelSettingsContent { /// Whether to show the project panel button in the status bar. /// diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index 47607813b547e28b9b4a37449f8daaa6ec022764..e543bf219ff0bc8226e819798a9ea74a098d0f98 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -30,7 +30,7 @@ use ui::{ use util::serde::default_true; use workspace::{AppState, ModalView, Workspace}; -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] pub struct SshSettings { pub ssh_connections: Option>, /// Whether to read ~/.ssh/config for ssh connection sources. @@ -121,7 +121,7 @@ pub struct SshProject { pub paths: Vec, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct RemoteSettingsContent { pub ssh_connections: Option>, pub read_ssh_config: Option, diff --git a/crates/repl/src/jupyter_settings.rs b/crates/repl/src/jupyter_settings.rs index c3bfd2079dfae21c9b990b15faec4cf7d4ffaa68..6f3d6b1db631267e9b41ae7598d6e573387f2ac6 100644 --- a/crates/repl/src/jupyter_settings.rs +++ b/crates/repl/src/jupyter_settings.rs @@ -6,7 +6,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; -#[derive(Debug, Default, SettingsUi)] +#[derive(Debug, Default)] pub struct JupyterSettings { pub kernel_selections: HashMap, } @@ -20,7 +20,7 @@ impl JupyterSettings { } } -#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] pub struct JupyterSettingsContent { /// Default kernels to select for each language. /// diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index 087f25185a99cb927892e3ada22d92c1c319a390..fb5b445b49a1fdbfac34ce8bc1a3d17d8241e009 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -100,25 +100,36 @@ impl BaseKeymap { } } +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default, SettingsUi, +)] +// extracted so that it can be an option, and still work with derive(SettingsUi) +pub struct BaseKeymapSetting { + pub base_keymap: Option, +} + impl Settings for BaseKeymap { - const KEY: Option<&'static str> = Some("base_keymap"); + const KEY: Option<&'static str> = None; - type FileContent = Option; + type FileContent = BaseKeymapSetting; fn load( sources: SettingsSources, _: &mut gpui::App, ) -> anyhow::Result { - if let Some(Some(user_value)) = sources.user.copied() { + if let Some(Some(user_value)) = sources.user.map(|setting| setting.base_keymap) { return Ok(user_value); } - if let Some(Some(server_value)) = sources.server.copied() { + if let Some(Some(server_value)) = sources.server.map(|setting| setting.base_keymap) { return Ok(server_value); } - sources.default.ok_or_else(Self::missing_default) + sources + .default + .base_keymap + .ok_or_else(Self::missing_default) } fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut Self::FileContent) { - *current = Some(BaseKeymap::VSCode); + current.base_keymap = Some(BaseKeymap::VSCode); } } diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 023f8cbfba3d96b0a6cad2e1c6ebb930f0bcdf9e..c1a7fd9e3c4812b78627ae6899ce068d499cbdf7 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -39,7 +39,7 @@ use crate::{ /// A value that can be defined as a user setting. /// /// Settings can be loaded from a combination of multiple JSON files. -pub trait Settings: SettingsUi + 'static + Send + Sync { +pub trait Settings: 'static + Send + Sync { /// The name of a key within the JSON file from which this setting should /// be deserialized. If this is `None`, then the setting will be deserialized /// from the root object. @@ -57,7 +57,7 @@ pub trait Settings: SettingsUi + 'static + Send + Sync { const PRESERVED_KEYS: Option<&'static [&'static str]> = None; /// The type that is stored in an individual JSON file. - type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema; + type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema + SettingsUi; /// The logic for combining together values from one or more JSON files into the /// final value for this setting. @@ -1565,7 +1565,7 @@ impl AnySettingValue for SettingValue { } fn settings_ui_item(&self) -> SettingsUiEntry { - ::settings_ui_entry() + <::FileContent as SettingsUi>::settings_ui_entry() } } @@ -2147,12 +2147,12 @@ mod tests { } } - #[derive(Debug, Deserialize, PartialEq, SettingsUi)] + #[derive(Debug, Deserialize, PartialEq)] struct TurboSetting(bool); impl Settings for TurboSetting { const KEY: Option<&'static str> = Some("turbo"); - type FileContent = Option; + type FileContent = bool; fn load(sources: SettingsSources, _: &mut App) -> Result { sources.json_merge() @@ -2161,7 +2161,7 @@ mod tests { fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {} } - #[derive(Clone, Debug, PartialEq, Deserialize, SettingsUi)] + #[derive(Clone, Debug, PartialEq, Deserialize)] struct MultiKeySettings { #[serde(default)] key1: String, @@ -2169,7 +2169,7 @@ mod tests { key2: String, } - #[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] struct MultiKeySettingsJson { key1: Option, key2: Option, @@ -2194,7 +2194,7 @@ mod tests { } } - #[derive(Debug, Deserialize, SettingsUi)] + #[derive(Debug, Deserialize)] struct JournalSettings { pub path: String, pub hour_format: HourFormat, @@ -2207,7 +2207,7 @@ mod tests { Hour24, } - #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)] struct JournalSettingsJson { pub path: Option, pub hour_format: Option, diff --git a/crates/settings/src/settings_ui.rs b/crates/settings/src/settings_ui.rs index 40ac3d9db9f82625f58007b182d6fb2ffb43a648..3a77627d5976f20c5732b47a1dfc164bc0a74b58 100644 --- a/crates/settings/src/settings_ui.rs +++ b/crates/settings/src/settings_ui.rs @@ -7,9 +7,16 @@ use crate::SettingsStore; pub trait SettingsUi { fn settings_ui_item() -> SettingsUiItem { + // todo(settings_ui): remove this default impl, only entry should have a default impl + // because it's expected that the macro or custom impl use the item and the known paths to create the entry SettingsUiItem::None } - fn settings_ui_entry() -> SettingsUiEntry; + + fn settings_ui_entry() -> SettingsUiEntry { + SettingsUiEntry { + item: SettingsUiEntryVariant::None, + } + } } pub struct SettingsUiEntry { @@ -106,11 +113,11 @@ impl SettingsUi for bool { fn settings_ui_item() -> SettingsUiItem { SettingsUiItem::Single(SettingsUiItemSingle::SwitchField) } +} - fn settings_ui_entry() -> SettingsUiEntry { - SettingsUiEntry { - item: SettingsUiEntryVariant::None, - } +impl SettingsUi for Option { + fn settings_ui_item() -> SettingsUiItem { + SettingsUiItem::Single(SettingsUiItemSingle::SwitchField) } } @@ -118,10 +125,4 @@ impl SettingsUi for u64 { fn settings_ui_item() -> SettingsUiItem { SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper) } - - fn settings_ui_entry() -> SettingsUiEntry { - SettingsUiEntry { - item: SettingsUiEntryVariant::None, - } - } } diff --git a/crates/settings_ui_macros/src/settings_ui_macros.rs b/crates/settings_ui_macros/src/settings_ui_macros.rs index 5250febe98cb17c74cf03d909e430b1415e29569..3840bc38da88a37fdbf17b8b06795f81c58ad132 100644 --- a/crates/settings_ui_macros/src/settings_ui_macros.rs +++ b/crates/settings_ui_macros/src/settings_ui_macros.rs @@ -88,7 +88,34 @@ pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenSt proc_macro::TokenStream::from(expanded) } +fn extract_type_from_option(ty: TokenStream) -> TokenStream { + match option_inner_type(ty.clone()) { + Some(inner_type) => inner_type, + None => ty, + } +} + +fn option_inner_type(ty: TokenStream) -> Option { + let ty = syn::parse2::(ty).ok()?; + let syn::Type::Path(path) = ty else { + return None; + }; + let segment = path.path.segments.last()?; + if segment.ident != "Option" { + return None; + } + let syn::PathArguments::AngleBracketed(args) = &segment.arguments else { + return None; + }; + let arg = args.args.first()?; + let syn::GenericArgument::Type(ty) = arg else { + return None; + }; + return Some(ty.to_token_stream()); +} + fn map_ui_item_to_render(path: &str, ty: TokenStream) -> TokenStream { + let ty = extract_type_from_option(ty); quote! { settings::SettingsUiEntry { item: match #ty::settings_ui_item() { diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 01f2d85f09e416b6c8ac40d7fa283d1f1e296cd5..c3051e089c68e3df0733c9e6cf7c8a42f56e742d 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -24,7 +24,7 @@ pub struct Toolbar { pub breadcrumbs: bool, } -#[derive(Clone, Debug, Deserialize, SettingsUi)] +#[derive(Clone, Debug, Deserialize)] pub struct TerminalSettings { pub shell: Shell, pub working_directory: WorkingDirectory, @@ -135,7 +135,7 @@ pub enum ActivateScript { Pyenv, } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct TerminalSettingsContent { /// What shell to use when opening a terminal. /// diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 61b41eba0642f10312a4c78df447ac7344f7e2dc..11db22d97485f5d400abdd8638da501abd55a192 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -87,7 +87,7 @@ impl From for String { } /// Customizable settings for the UI and theme system. -#[derive(Clone, PartialEq, SettingsUi)] +#[derive(Clone, PartialEq)] pub struct ThemeSettings { /// The UI font size. Determines the size of text in the UI, /// as well as the size of a [gpui::Rems] unit. @@ -365,7 +365,7 @@ impl IconThemeSelection { } /// Settings for rendering text in UI and text buffers. -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct ThemeSettingsContent { /// The default font size for text in the UI. #[serde(default)] diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 29d74c8590a63cd8aa75bdaa3655111d76fcf757..0dc301f7eef6789bf1c0a2ad51cb63dff77d0337 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -3,8 +3,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; -#[derive(Copy, Clone, Deserialize, Debug, SettingsUi)] -#[settings_ui(group = "Title Bar", path = "title_bar")] +#[derive(Copy, Clone, Deserialize, Debug)] pub struct TitleBarSettings { pub show_branch_icon: bool, pub show_onboarding_banner: bool, @@ -15,7 +14,8 @@ pub struct TitleBarSettings { pub show_menus: bool, } -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)] +#[settings_ui(group = "Title Bar", path = "title_bar")] pub struct TitleBarSettingsContent { /// Whether to show the branch icon beside branch switcher in the title bar. /// diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index a5cd909d5b53079d1da49591a5eca21416ba415a..5a4ac425183e1843db7075c0f5054a16f82948f9 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -1774,7 +1774,7 @@ struct CursorShapeSettings { pub insert: Option, } -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] struct VimSettings { pub default_mode: Mode, pub toggle_relative_line_numbers: bool, @@ -1785,7 +1785,7 @@ struct VimSettings { pub cursor_shape: CursorShapeSettings, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] struct VimSettingsContent { pub default_mode: Option, pub toggle_relative_line_numbers: Option, diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index a513f8c9317645469e5d5ca54c3b5351383c1ca3..731e1691479ad7eec1388c174d85081a177127cc 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -49,7 +49,7 @@ impl Default for SaveOptions { } } -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] pub struct ItemSettings { pub git_status: bool, pub close_position: ClosePosition, @@ -59,7 +59,7 @@ pub struct ItemSettings { pub show_close_button: ShowCloseButton, } -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] pub struct PreviewTabsSettings { pub enabled: bool, pub enable_preview_from_file_finder: bool, @@ -101,7 +101,7 @@ pub enum ActivateOnClose { LeftNeighbour, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct ItemSettingsContent { /// Whether to show the Git file status on a tab item. /// @@ -130,7 +130,7 @@ pub struct ItemSettingsContent { show_close_button: Option, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] 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. diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 419e33e54435779012207a024ea49e44a8acb1c2..1a7e548e4eda1f41e36c6ad0883cdd57be8828d7 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -8,7 +8,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] pub struct WorkspaceSettings { pub active_pane_modifiers: ActivePanelModifiers, pub bottom_dock_layout: BottomDockLayout, @@ -118,7 +118,7 @@ pub enum RestoreOnStartupBehavior { LastSession, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct WorkspaceSettingsContent { /// Active pane styling settings. pub active_pane_modifiers: Option, @@ -216,14 +216,14 @@ pub struct WorkspaceSettingsContent { pub zoomed_padding: Option, } -#[derive(Deserialize, SettingsUi)] +#[derive(Deserialize)] pub struct TabBarSettings { pub show: bool, pub show_nav_history_buttons: bool, pub show_tab_bar_buttons: bool, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct TabBarSettingsContent { /// Whether or not to show the tab bar in the editor. /// @@ -266,7 +266,7 @@ pub enum PaneSplitDirectionVertical { Right, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)] #[serde(rename_all = "snake_case")] pub struct CenteredLayoutSettings { /// The relative width of the left padding of the central pane from the diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index df3a4d35570ad21b80f968539afbe681c58e2a06..6a8e2b5d89b0201b81f45817adb439fe85e24d91 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources, SettingsUi}; use util::paths::PathMatcher; -#[derive(Clone, PartialEq, Eq, SettingsUi)] +#[derive(Clone, PartialEq, Eq)] pub struct WorktreeSettings { pub file_scan_inclusions: PathMatcher, pub file_scan_exclusions: PathMatcher, @@ -31,7 +31,7 @@ impl WorktreeSettings { } } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)] pub struct WorktreeSettingsContent { /// Completely ignore files matching globs from `file_scan_exclusions`. Overrides /// `file_scan_inclusions`.