diff --git a/Cargo.lock b/Cargo.lock index c835b503ad..ffbc1d7ee1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8953,6 +8953,46 @@ dependencies = [ "uuid", ] +[[package]] +name = "keymap_editor" +version = "0.1.0" +dependencies = [ + "anyhow", + "collections", + "command_palette", + "command_palette_hooks", + "component", + "db", + "editor", + "feature_flags", + "fs", + "fuzzy", + "gpui", + "itertools 0.14.0", + "language", + "log", + "menu", + "notifications", + "paths", + "project", + "search", + "serde", + "serde_json", + "settings", + "telemetry", + "tempfile", + "theme", + "tree-sitter-json", + "tree-sitter-rust", + "ui", + "ui_input", + "util", + "vim", + "workspace", + "workspace-hack", + "zed_actions", +] + [[package]] name = "khronos-egl" version = "6.0.0" @@ -14856,6 +14896,7 @@ dependencies = [ "serde_derive", "serde_json", "serde_json_lenient", + "settings_ui_macros", "smallvec", "tree-sitter", "tree-sitter-json", @@ -14891,39 +14932,25 @@ name = "settings_ui" version = "0.1.0" dependencies = [ "anyhow", - "collections", "command_palette", "command_palette_hooks", - "component", - "db", "editor", "feature_flags", - "fs", - "fuzzy", "gpui", - "itertools 0.14.0", - "language", - "log", - "menu", - "notifications", - "paths", - "project", - "search", - "serde", - "serde_json", "settings", - "telemetry", - "tempfile", "theme", - "tree-sitter-json", - "tree-sitter-rust", "ui", - "ui_input", "util", - "vim", "workspace", - "workspace-hack", - "zed_actions", +] + +[[package]] +name = "settings_ui_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", ] [[package]] @@ -16739,6 +16766,7 @@ dependencies = [ "db", "gpui", "http_client", + "keymap_editor", "notifications", "pretty_assertions", "project", @@ -16747,7 +16775,6 @@ dependencies = [ "schemars", "serde", "settings", - "settings_ui", "smallvec", "story", "telemetry", @@ -20459,6 +20486,7 @@ dependencies = [ "itertools 0.14.0", "jj_ui", "journal", + "keymap_editor", "language", "language_extension", "language_model", diff --git a/Cargo.toml b/Cargo.toml index 6ec243a9b9..7be525cd6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,6 +54,8 @@ members = [ "crates/deepseek", "crates/diagnostics", "crates/docs_preprocessor", + "crates/edit_prediction", + "crates/edit_prediction_button", "crates/editor", "crates/eval", "crates/explorer_command_injector", @@ -82,13 +84,12 @@ members = [ "crates/http_client_tls", "crates/icons", "crates/image_viewer", - "crates/edit_prediction", - "crates/edit_prediction_button", "crates/inspector_ui", "crates/install_cli", "crates/jj", "crates/jj_ui", "crates/journal", + "crates/keymap_editor", "crates/language", "crates/language_extension", "crates/language_model", @@ -146,6 +147,7 @@ members = [ "crates/settings", "crates/settings_profile_selector", "crates/settings_ui", + "crates/settings_ui_macros", "crates/snippet", "crates/snippet_provider", "crates/snippets_ui", @@ -156,9 +158,9 @@ members = [ "crates/streaming_diff", "crates/sum_tree", "crates/supermaven", - "crates/system_specs", "crates/supermaven_api", "crates/svg_preview", + "crates/system_specs", "crates/tab_switcher", "crates/task", "crates/tasks_ui", @@ -314,6 +316,7 @@ install_cli = { path = "crates/install_cli" } jj = { path = "crates/jj" } jj_ui = { path = "crates/jj_ui" } journal = { path = "crates/journal" } +keymap_editor = { path = "crates/keymap_editor" } language = { path = "crates/language" } language_extension = { path = "crates/language_extension" } language_model = { path = "crates/language_model" } @@ -373,6 +376,7 @@ semantic_version = { path = "crates/semantic_version" } session = { path = "crates/session" } settings = { path = "crates/settings" } settings_ui = { path = "crates/settings_ui" } +settings_ui_macros = { path = "crates/settings_ui_macros" } snippet = { path = "crates/snippet" } snippet_provider = { path = "crates/snippet_provider" } snippets_ui = { path = "crates/snippets_ui" } diff --git a/crates/agent_servers/src/settings.rs b/crates/agent_servers/src/settings.rs index 96ac6e3cbe..916ff72baf 100644 --- a/crates/agent_servers/src/settings.rs +++ b/crates/agent_servers/src/settings.rs @@ -4,13 +4,13 @@ use collections::HashMap; use gpui::{App, SharedString}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; pub fn init(cx: &mut App) { AllAgentServersSettings::register(cx); } -#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)] +#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUI)] pub struct AllAgentServersSettings { pub gemini: Option, pub claude: Option, diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index ed1ed2b898..82be85be07 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -8,7 +8,7 @@ use gpui::{App, Pixels, SharedString}; use language_model::LanguageModel; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use std::borrow::Cow; pub use crate::agent_profile::*; @@ -48,7 +48,7 @@ pub enum NotifyWhenAgentWaiting { Never, } -#[derive(Default, Clone, Debug)] +#[derive(Default, Clone, Debug, SettingsUI)] pub struct AgentSettings { pub enabled: bool, pub button: bool, diff --git a/crates/agent_ui/src/slash_command_settings.rs b/crates/agent_ui/src/slash_command_settings.rs index 73e5622aa9..dbc4c9c27c 100644 --- a/crates/agent_ui/src/slash_command_settings.rs +++ b/crates/agent_ui/src/slash_command_settings.rs @@ -2,10 +2,10 @@ use anyhow::Result; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; /// Settings for slash commands. -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUI)] pub struct SlashCommandSettings { /// Settings for the `/cargo-workspace` slash command. #[serde(default)] diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index 807179881c..bb5f975c3c 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -2,9 +2,9 @@ use anyhow::Result; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; -#[derive(Deserialize, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUI)] pub struct AudioSettings { /// Opt into the new audio system. #[serde(rename = "experimental.rodio_audio", default)] diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 2150873cad..58522d3941 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -10,7 +10,7 @@ use paths::remote_servers_dir; use release_channel::{AppCommitSha, ReleaseChannel}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources, SettingsStore}; +use settings::{SettingsUI, Settings, SettingsSources, SettingsStore}; use smol::{fs, io::AsyncReadExt}; use smol::{fs::File, process::Command}; use std::{ @@ -113,6 +113,7 @@ impl Drop for MacOsUnmounter { } } +#[derive(SettingsUI)] struct AutoUpdateSetting(bool); /// Whether or not to automatically check for updates. diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index c8f51e0c1a..d3c867b199 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -2,9 +2,9 @@ use anyhow::Result; use gpui::App; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, SettingsUI)] pub struct CallSettings { pub mute_on_join: bool, pub share_on_join: bool, diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 2bbe7dd1b5..dc106382a0 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -31,7 +31,7 @@ use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use std::{ any::TypeId, convert::TryFrom, @@ -101,7 +101,7 @@ pub struct ClientSettingsContent { server_url: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, SettingsUI)] pub struct ClientSettings { pub server_url: String, } @@ -127,7 +127,7 @@ pub struct ProxySettingsContent { proxy: Option, } -#[derive(Deserialize, Default)] +#[derive(Deserialize, Default, SettingsUI)] pub struct ProxySettings { pub proxy: Option, } @@ -504,7 +504,7 @@ impl Drop for PendingEntitySubscription { } } -#[derive(Copy, Clone, Deserialize, Debug)] +#[derive(Copy, Clone, Deserialize, Debug, SettingsUI)] pub struct TelemetrySettings { pub diagnostics: bool, pub metrics: bool, diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index 652d9eb67f..92815dcf9f 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -1,10 +1,10 @@ use gpui::Pixels; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use workspace::dock::DockPosition; -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, SettingsUI)] pub struct CollaborationPanelSettings { pub button: bool, pub dock: DockPosition, @@ -20,7 +20,7 @@ pub enum ChatPanelButton { WhenInCall, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, SettingsUI)] pub struct ChatPanelSettings { pub button: ChatPanelButton, pub dock: DockPosition, @@ -43,7 +43,7 @@ pub struct ChatPanelSettingsContent { pub default_width: Option, } -#[derive(Deserialize, Debug)] +#[derive(Deserialize, Debug, SettingsUI)] pub struct NotificationPanelSettings { pub button: bool, pub dock: DockPosition, @@ -66,7 +66,7 @@ pub struct PanelSettingsContent { pub default_width: Option, } -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUI)] pub struct MessageEditorSettings { /// Whether to automatically replace emoji shortcodes with emoji characters. /// For example: typing `:wave:` gets replaced with `👋`. diff --git a/crates/dap/src/debugger_settings.rs b/crates/dap/src/debugger_settings.rs index e1176633e5..bef7157eba 100644 --- a/crates/dap/src/debugger_settings.rs +++ b/crates/dap/src/debugger_settings.rs @@ -2,7 +2,7 @@ use dap_types::SteppingGranularity; use gpui::{App, Global}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(rename_all = "snake_case")] @@ -12,7 +12,7 @@ pub enum DebugPanelDockPosition { Right, } -#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)] +#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUI)] #[serde(default)] pub struct DebuggerSettings { /// Determines the stepping granularity. diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index 1d7e04cae0..5e1675d23e 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -6,12 +6,12 @@ use language::CursorShape; use project::project_settings::DiagnosticSeverity; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources, VsCodeSettings}; +use settings::{SettingsUI, Settings, SettingsSources, VsCodeSettings}; use util::serde::default_true; /// Imports from the VSCode settings at /// https://code.visualstudio.com/docs/reference/default-settings -#[derive(Deserialize, Clone)] +#[derive(Deserialize, Clone, SettingsUI)] pub struct EditorSettings { pub cursor_blink: bool, pub cursor_shape: Option, diff --git a/crates/extension_host/src/extension_settings.rs b/crates/extension_host/src/extension_settings.rs index cfa67990b0..51ac489ef7 100644 --- a/crates/extension_host/src/extension_settings.rs +++ b/crates/extension_host/src/extension_settings.rs @@ -3,10 +3,10 @@ use collections::HashMap; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use std::sync::Arc; -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUI)] pub struct ExtensionSettings { /// The extensions that should be automatically installed by Zed. /// diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 350e1de3b3..6066397e52 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -1,9 +1,9 @@ use anyhow::Result; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUI)] pub struct FileFinderSettings { pub file_icons: bool, pub modal_max_width: Option, diff --git a/crates/git_hosting_providers/src/settings.rs b/crates/git_hosting_providers/src/settings.rs index 91179fea39..07831ee390 100644 --- a/crates/git_hosting_providers/src/settings.rs +++ b/crates/git_hosting_providers/src/settings.rs @@ -5,7 +5,7 @@ use git::GitHostingProviderRegistry; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::{Settings, SettingsStore, SettingsUI}; use url::Url; use util::ResultExt as _; @@ -78,7 +78,7 @@ pub struct GitHostingProviderConfig { pub name: String, } -#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, SettingsUI)] pub struct GitHostingProviderSettings { /// The list of custom Git hosting providers. #[serde(default)] diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index b6891c7d25..21f40f4a34 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -2,7 +2,7 @@ use editor::ShowScrollbar; use gpui::Pixels; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use workspace::dock::DockPosition; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -77,7 +77,7 @@ pub struct GitPanelSettingsContent { pub collapse_untracked_diff: Option, } -#[derive(Deserialize, Debug, Clone, PartialEq)] +#[derive(Deserialize, Debug, Clone, PartialEq, SettingsUI)] 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 e60a3651aa..4913b4901f 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -2,7 +2,7 @@ use editor::{Editor, EditorSettings, MultiBufferSnapshot}; use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use std::{fmt::Write, num::NonZeroU32, time::Duration}; use text::{Point, Selection}; use ui::{ @@ -293,7 +293,7 @@ impl StatusItemView for CursorPosition { } } -#[derive(Clone, Copy, Default, PartialEq, JsonSchema, Deserialize, Serialize)] +#[derive(Clone, Copy, Default, PartialEq, JsonSchema, Deserialize, Serialize, SettingsUI)] #[serde(rename_all = "snake_case")] pub(crate) enum LineIndicatorFormat { Short, diff --git a/crates/gpui_macros/src/derive_action.rs b/crates/gpui_macros/src/derive_action.rs index 9c7f97371d..4e6c6277e4 100644 --- a/crates/gpui_macros/src/derive_action.rs +++ b/crates/gpui_macros/src/derive_action.rs @@ -16,6 +16,13 @@ pub(crate) fn derive_action(input: TokenStream) -> TokenStream { let mut deprecated = None; let mut doc_str: Option = None; + /* + * + * #[action()] + * Struct Foo { + * bar: bool // is bar considered an attribute + } + */ for attr in &input.attrs { if attr.path().is_ident("action") { attr.parse_nested_meta(|meta| { diff --git a/crates/image_viewer/src/image_viewer_settings.rs b/crates/image_viewer/src/image_viewer_settings.rs index 1dcf99c0af..2adeb5d616 100644 --- a/crates/image_viewer/src/image_viewer_settings.rs +++ b/crates/image_viewer/src/image_viewer_settings.rs @@ -1,10 +1,10 @@ use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; /// The settings for the image viewer. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, SettingsUI)] pub struct ImageViewerSettings { /// The unit to use for displaying image file sizes. /// diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index c09ab6f764..6d18b304b2 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -5,7 +5,7 @@ use editor::{Editor, SelectionEffects}; use gpui::{App, AppContext as _, Context, Window, actions}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use std::{ fs::OpenOptions, path::{Path, PathBuf}, @@ -22,7 +22,7 @@ actions!( ); /// Settings specific to journaling -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUI)] pub struct JournalSettings { /// The path of the directory where journal entries are stored. /// diff --git a/crates/keymap_editor/Cargo.toml b/crates/keymap_editor/Cargo.toml new file mode 100644 index 0000000000..03098d5506 --- /dev/null +++ b/crates/keymap_editor/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "keymap_editor" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/keymap_editor.rs" + +[dependencies] +anyhow.workspace = true +collections.workspace = true +command_palette.workspace = true +command_palette_hooks.workspace = true +component.workspace = true +db.workspace = true +editor.workspace = true +feature_flags.workspace = true +fs.workspace = true +fuzzy.workspace = true +gpui.workspace = true +itertools.workspace = true +language.workspace = true +log.workspace = true +menu.workspace = true +notifications.workspace = true +paths.workspace = true +project.workspace = true +search.workspace = true +serde.workspace = true +serde_json.workspace = true +settings.workspace = true +telemetry.workspace = true +tempfile.workspace = true +theme.workspace = true +tree-sitter-json.workspace = true +tree-sitter-rust.workspace = true +ui.workspace = true +ui_input.workspace = true +util.workspace = true +vim.workspace = true +workspace-hack.workspace = true +workspace.workspace = true +zed_actions.workspace = true + +[dev-dependencies] +db = {"workspace"= true, "features" = ["test-support"]} +fs = { workspace = true, features = ["test-support"] } +gpui = { workspace = true, features = ["test-support"] } +project = { workspace = true, features = ["test-support"] } +workspace = { workspace = true, features = ["test-support"] } diff --git a/crates/keymap_editor/LICENSE-GPL b/crates/keymap_editor/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/keymap_editor/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/settings_ui/src/keybindings.rs b/crates/keymap_editor/src/keymap_editor.rs similarity index 99% rename from crates/settings_ui/src/keybindings.rs rename to crates/keymap_editor/src/keymap_editor.rs index 9c76725972..3c67610aeb 100644 --- a/crates/settings_ui/src/keybindings.rs +++ b/crates/keymap_editor/src/keymap_editor.rs @@ -1,3 +1,5 @@ +mod ui_components; + use std::{ cmp::{self}, ops::{Not as _, Range}, @@ -35,7 +37,7 @@ use workspace::{ }; use crate::{ - keybindings::persistence::KEYBINDING_EDITORS, + persistence::KEYBINDING_EDITORS, ui_components::{ keystroke_input::{ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording}, table::{ColumnWidths, ResizeBehavior, Table, TableInteractionState}, diff --git a/crates/settings_ui/src/ui_components/keystroke_input.rs b/crates/keymap_editor/src/ui_components/keystroke_input.rs similarity index 100% rename from crates/settings_ui/src/ui_components/keystroke_input.rs rename to crates/keymap_editor/src/ui_components/keystroke_input.rs diff --git a/crates/settings_ui/src/ui_components/mod.rs b/crates/keymap_editor/src/ui_components/mod.rs similarity index 100% rename from crates/settings_ui/src/ui_components/mod.rs rename to crates/keymap_editor/src/ui_components/mod.rs diff --git a/crates/settings_ui/src/ui_components/table.rs b/crates/keymap_editor/src/ui_components/table.rs similarity index 100% rename from crates/settings_ui/src/ui_components/table.rs rename to crates/keymap_editor/src/ui_components/table.rs diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index 0f82d3997f..6e08ca49e1 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -17,7 +17,7 @@ use serde::{ }; use settings::{ - ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore, + ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore, SettingsUI, }; use shellexpand; use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc}; @@ -55,7 +55,7 @@ pub fn all_language_settings<'a>( } /// The settings for all languages. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, SettingsUI)] pub struct AllLanguageSettings { /// The edit prediction settings. pub edit_predictions: EditPredictionSettings, diff --git a/crates/language_models/src/settings.rs b/crates/language_models/src/settings.rs index b163585aa7..172fcf6446 100644 --- a/crates/language_models/src/settings.rs +++ b/crates/language_models/src/settings.rs @@ -5,7 +5,7 @@ use collections::HashMap; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use crate::provider::{ self, @@ -29,7 +29,7 @@ pub fn init_settings(cx: &mut App) { AllLanguageModelSettings::register(cx); } -#[derive(Default)] +#[derive(Default, SettingsUI)] pub struct AllLanguageModelSettings { pub anthropic: AnthropicSettings, pub bedrock: AmazonBedrockSettings, diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index 133d28b748..9d46fac0c4 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -2,7 +2,7 @@ use editor::ShowScrollbar; use gpui::Pixels; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] #[serde(rename_all = "snake_case")] @@ -18,7 +18,7 @@ pub enum ShowIndentGuides { Never, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUI)] pub struct OutlinePanelSettings { pub button: bool, pub default_width: Pixels, diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 9fd4eed641..48e75c8095 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -948,7 +948,7 @@ pub enum PulledDiagnostics { /// Whether to disable all AI features in Zed. /// /// Default: false -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, settings::SettingsUI)] pub struct DisableAiSettings { pub disable_ai: bool, } diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 4447c25129..132c3cc5ab 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -18,8 +18,8 @@ use rpc::{ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{ - InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources, - SettingsStore, parse_json_with_comments, watch_config_file, + SettingsUI, InvalidSettingsError, LocalSettingsKind, Settings, + SettingsLocation, SettingsSources, SettingsStore, parse_json_with_comments, watch_config_file, }; use std::{ collections::BTreeMap, @@ -36,7 +36,7 @@ use crate::{ worktree_store::{WorktreeStore, WorktreeStoreEvent}, }; -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUI)] pub struct ProjectSettings { /// Configuration for language servers. /// diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index 8a243589ed..531136e699 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -2,7 +2,7 @@ use editor::ShowScrollbar; use gpui::Pixels; use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] #[serde(rename_all = "snake_case")] @@ -28,7 +28,7 @@ pub enum EntrySpacing { Standard, } -#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] +#[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUI)] pub struct ProjectPanelSettings { pub button: bool, pub hide_gitignore: bool, diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index d07ea48c7e..9544d1aae1 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -19,7 +19,7 @@ use remote::ssh_session::{ConnectionIdentifier, SshPortForwardOption}; use remote::{SshConnectionOptions, SshPlatform, SshRemoteClient}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use theme::ThemeSettings; use ui::{ ActiveTheme, Color, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, @@ -28,7 +28,7 @@ use ui::{ use util::serde::default_true; use workspace::{AppState, ModalView, Workspace}; -#[derive(Deserialize)] +#[derive(Deserialize, SettingsUI)] pub struct SshSettings { pub ssh_connections: Option>, /// Whether to read ~/.ssh/config for ssh connection sources. diff --git a/crates/repl/src/jupyter_settings.rs b/crates/repl/src/jupyter_settings.rs index 8b00e0f757..4dcb3bb603 100644 --- a/crates/repl/src/jupyter_settings.rs +++ b/crates/repl/src/jupyter_settings.rs @@ -4,9 +4,9 @@ use editor::EditorSettings; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; -#[derive(Debug, Default)] +#[derive(Debug, Default, SettingsUI)] pub struct JupyterSettings { pub kernel_selections: HashMap, } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 892d4dea8b..8768b40736 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -31,6 +31,7 @@ schemars.workspace = true serde.workspace = true serde_derive.workspace = true serde_json.workspace = true +settings_ui_macros.workspace = true serde_json_lenient.workspace = true smallvec.workspace = true tree-sitter-json.workspace = true diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index 91dda03d00..4f4ba6e4a4 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -1,13 +1,17 @@ use std::fmt::{Display, Formatter}; -use crate::{Settings, SettingsSources, VsCodeSettings}; +use crate as settings; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use settings::{Settings, SettingsSources, VsCodeSettings}; +use settings_ui_macros::SettingsUI; /// Base key bindings scheme. Base keymaps can be overridden with user keymaps. /// /// Default: VSCode -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] +#[derive( + Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default, SettingsUI, +)] pub enum BaseKeymap { #[default] VSCode, diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index b73ab9ae95..aa2af3ac99 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -5,6 +5,7 @@ mod keymap_file; mod settings_file; mod settings_json; mod settings_store; +mod settings_ui; mod vscode_import; use gpui::{App, Global}; @@ -25,6 +26,9 @@ pub use settings_store::{ InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources, SettingsStore, }; +pub use settings_ui::*; +// Re-export the derive macro +pub use settings_ui_macros::SettingsUI; pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; #[derive(Clone, Debug, PartialEq)] diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index 3deaed8b9d..cc06e990d3 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -31,14 +31,15 @@ use util::{ pub type EditorconfigProperties = ec4rs::Properties; use crate::{ - ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, - WorktreeId, parse_json_with_comments, update_value_in_json_text, + ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUIItem, + SettingsUIRender, VsCodeSettings, WorktreeId, parse_json_with_comments, + settings_ui::SettingsUI, update_value_in_json_text, }; /// A value that can be defined as a user setting. /// /// Settings can be loaded from a combination of multiple JSON files. -pub trait Settings: 'static + Send + Sync { +pub trait Settings: SettingsUI + '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. @@ -272,6 +273,7 @@ trait AnySettingValue: 'static + Send + Sync { text: &mut String, edits: &mut Vec<(Range, String)>, ); + fn settings_ui_item(&self) -> SettingsUIItem; } struct DeserializedSetting(Box); @@ -604,6 +606,12 @@ impl SettingsStore { rx } + + pub fn settings_ui_items(&self) -> impl IntoIterator { + self.setting_values + .values() + .map(|item| item.settings_ui_item()) + } } impl SettingsStore { @@ -1498,6 +1506,10 @@ impl AnySettingValue for SettingValue { edits, ); } + + fn settings_ui_item(&self) -> SettingsUIItem { + ::settings_ui_item() + } } #[cfg(test)] @@ -1505,7 +1517,10 @@ mod tests { use crate::VsCodeSettingsSource; use super::*; + // This is so the SettingsUI macro can still work properly + use crate as settings; use serde_derive::Deserialize; + use settings_ui_macros::SettingsUI; use unindent::Unindent; #[gpui::test] @@ -2048,14 +2063,14 @@ mod tests { pretty_assertions::assert_eq!(new, expected); } - #[derive(Debug, PartialEq, Deserialize)] + #[derive(Debug, PartialEq, Deserialize, SettingsUI)] struct UserSettings { name: String, age: u32, staff: bool, } - #[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] + #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUI)] struct UserSettingsContent { name: Option, age: Option, @@ -2075,7 +2090,7 @@ mod tests { } } - #[derive(Debug, Deserialize, PartialEq)] + #[derive(Debug, Deserialize, PartialEq, SettingsUI)] struct TurboSetting(bool); impl Settings for TurboSetting { @@ -2089,7 +2104,7 @@ mod tests { fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {} } - #[derive(Clone, Debug, PartialEq, Deserialize)] + #[derive(Clone, Debug, PartialEq, Deserialize, SettingsUI)] struct MultiKeySettings { #[serde(default)] key1: String, @@ -2122,7 +2137,7 @@ mod tests { } } - #[derive(Debug, Deserialize)] + #[derive(Debug, Deserialize, SettingsUI)] struct JournalSettings { pub path: String, pub hour_format: HourFormat, @@ -2223,7 +2238,7 @@ mod tests { ); } - #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] + #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUI)] struct LanguageSettings { #[serde(default)] languages: HashMap, diff --git a/crates/settings/src/settings_ui.rs b/crates/settings/src/settings_ui.rs new file mode 100644 index 0000000000..3a1f3c2216 --- /dev/null +++ b/crates/settings/src/settings_ui.rs @@ -0,0 +1,152 @@ +use std::any::Any; + +use gpui::{AnyElement, App, Window}; + +pub trait SettingsUI { + fn settings_ui_render() -> SettingsUIRender { + SettingsUIRender::None + } + fn settings_ui_item() -> SettingsUIItem; +} + +pub struct SettingsUIItem { + // TODO: move this back here once there isn't a None variant + // pub path: &'static str, + // pub title: &'static str, + pub item: SettingsUIItemVariant, +} + +pub enum SettingsUIItemVariant { + Group { + path: &'static str, + title: &'static str, + group: SettingsUIItemGroup, + }, + Item { + path: &'static str, + item: SettingsUIItemSingle, + }, + // TODO: remove + None, +} + +pub struct SettingsUIItemGroup { + pub items: Vec, +} + +pub enum SettingsUIItemSingle { + // TODO: default/builtin variants + SwitchField, + Custom(Box AnyElement>), +} + +pub enum SettingsUIRender { + Group { + title: &'static str, + items: Vec, + }, + Item(SettingsUIItemSingle), + None, +} + +impl SettingsUI for bool { + fn settings_ui_render() -> SettingsUIRender { + SettingsUIRender::Item(SettingsUIItemSingle::SwitchField) + } + + fn settings_ui_item() -> SettingsUIItem { + SettingsUIItem { + item: SettingsUIItemVariant::None, + } + } +} + +/* +FOR DOC COMMENTS ON "Contents" TYPES: +define trait: SettingsUIDocProvider with derive +derive creates: +impl SettingsUIDocProvider for Foo { + fn settings_ui_doc() -> Hashmap<&'static str, &'static str> { + Hashmap::from(Foo.fields.map(|field| (field.name, field.doc_comment))) + } +} + +on derive settings_ui, have attr +#[settings_ui(doc_from = "Foo")] + +and have derive(SettingsUI) do + +if doc_from { +quote! { + doc_comments = doc_from.type::settings_ui_doc(); + for fields { + field.doc_comment = doc_comments.get(field.name).unwrap() + } + } +} else { + doc_comments = FileContent::settings_ui +} + +FOR PATH: +if derive attr also contains "Settings", then we can use ::KEY, +otherwise we need a #[settings_ui(path = ...)]. + +FOR BOTH OF ABOVE, we can check if derive() attr contains Settings, otherwise assert that both doc_from and path are present +like so: #[settings_ui(doc_from = "Foo", path = "foo")] + */ + +/* +#[derive(SettingsUI)] +#[settings_ui(group = "Foo")] +struct Foo { + // #[settings_ui(render = "my_render_function")] + pub toggle: bool, + pub font_size: u32, + + Group(vec![Item {path: "toggle", item: SwitchField}]) +} + +macro code: +settings_ui_item() { + group.items = struct.fields.map((field_name, field_type) => quote! { SettingsUIItem::Item {path: #field_type::settings_ui_path().unwrap_or_else(|| #field_name), item: if field.attrs.render { #render } else field::settings_ui_render()}}) + } + */ + +/* NOTES: + +# Root Group +some_setting: { + # First Item + # this shouldn't be a group + # it should just be item with path "some_bool.enabled" and title "Some Bool" + "some_bool": { + # this should + enabled: true | false + } + # Second Item + "some_other_thing": "foo" | "bar" | "baz" +} + +Structure: +Group { + path: "some_item", + items: [ + Item( + path: ["some_bool", "enabled"], + ), + Item( + path: ["some_other_thing"], + ) + ] +} + +is the following better than "foo.enabled"? +- for objects with single key "enabled", should just be a bool, with no "enabled" +for objects with enabled and other settings, enabled should be implicit, +so +"vim": false, # disabled +"vim": true, # enabled with default settings +"vim": { + "default_mode": "HelixNormal" +} # enabled with custom settings +*/ diff --git a/crates/settings_ui/Cargo.toml b/crates/settings_ui/Cargo.toml index 8a151359ec..9c3866e228 100644 --- a/crates/settings_ui/Cargo.toml +++ b/crates/settings_ui/Cargo.toml @@ -11,45 +11,24 @@ workspace = true [lib] path = "src/settings_ui.rs" +[features] +default = [] + [dependencies] anyhow.workspace = true -collections.workspace = true command_palette.workspace = true command_palette_hooks.workspace = true -component.workspace = true -db.workspace = true editor.workspace = true feature_flags.workspace = true -fs.workspace = true -fuzzy.workspace = true gpui.workspace = true -itertools.workspace = true -language.workspace = true -log.workspace = true -menu.workspace = true -notifications.workspace = true -paths.workspace = true -project.workspace = true -search.workspace = true -serde.workspace = true -serde_json.workspace = true -settings.workspace = true -telemetry.workspace = true -tempfile.workspace = true theme.workspace = true -tree-sitter-json.workspace = true -tree-sitter-rust.workspace = true +settings.workspace = true ui.workspace = true -ui_input.workspace = true util.workspace = true -vim.workspace = true -workspace-hack.workspace = true workspace.workspace = true -zed_actions.workspace = true -[dev-dependencies] -db = {"workspace"= true, "features" = ["test-support"]} -fs = { workspace = true, features = ["test-support"] } -gpui = { workspace = true, features = ["test-support"] } -project = { workspace = true, features = ["test-support"] } -workspace = { workspace = true, features = ["test-support"] } +# Uncomment other workspace dependencies as needed +# assistant.workspace = true +# client.workspace = true +# project.workspace = true +# settings.workspace = true diff --git a/crates/settings_ui/src/settings_ui.rs b/crates/settings_ui/src/settings_ui.rs index 3022cc7142..e97b405dbc 100644 --- a/crates/settings_ui/src/settings_ui.rs +++ b/crates/settings_ui/src/settings_ui.rs @@ -5,16 +5,14 @@ use std::any::TypeId; use command_palette_hooks::CommandPaletteFilter; use editor::EditorSettingsControls; use feature_flags::{FeatureFlag, FeatureFlagViewExt}; -use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, actions}; -use ui::prelude::*; +use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, ReadGlobal, actions}; +use settings::{SettingsStore, SettingsUIItemSingle, SettingsUIItemVariant}; +use ui::{SwitchField, prelude::*}; use workspace::item::{Item, ItemEvent}; use workspace::{Workspace, with_active_or_new_workspace}; use crate::appearance_settings_controls::AppearanceSettingsControls; -pub mod keybindings; -pub mod ui_components; - pub struct SettingsUiFeatureFlag; impl FeatureFlag for SettingsUiFeatureFlag { @@ -75,8 +73,6 @@ pub fn init(cx: &mut App) { .detach(); }) .detach(); - - keybindings::init(cx); } pub struct SettingsPage { @@ -125,6 +121,52 @@ impl Render for SettingsPage { .p_4() .size_full() .gap_4() + .children( + SettingsStore::global(cx) + .settings_ui_items() + .into_iter() + .flat_map(|item| match item.item { + settings::SettingsUIItemVariant::Group { title, path, group } => Some( + div() + .child(Label::new(title).size(LabelSize::Large)) + .children(group.items.iter().map(|item| { + match &item.item { + settings::SettingsUIItemVariant::Group { + path, + title, + group, + } => div() + .child(format!("Subgroup: {}", title)) + .into_any_element(), + settings::SettingsUIItemVariant::Item { path, item } => { + match item { + SettingsUIItemSingle::Custom(_) => div() + .child(format!("Item: {}", path)) + .into_any_element(), + SettingsUIItemSingle::SwitchField => div() + .child(SwitchField::new( + ElementId::Name(SharedString::new_static( + path, + )), + SharedString::new_static(path), + None, + ToggleState::Unselected, + |_, _, _| {}, + )) + .into_any_element(), + } + } + settings::SettingsUIItemVariant::None => { + div().child("None").into_any_element() + } + } + })), + ), + + settings::SettingsUIItemVariant::Item { path, item } => todo!(), + settings::SettingsUIItemVariant::None => None, + }), + ) .child(Label::new("Settings").size(LabelSize::Large)) .child( v_flex().gap_1().child(Label::new("Appearance")).child( diff --git a/crates/settings_ui_macros/Cargo.toml b/crates/settings_ui_macros/Cargo.toml new file mode 100644 index 0000000000..7df5074c6f --- /dev/null +++ b/crates/settings_ui_macros/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "settings_ui_macros" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lib] +path = "src/settings_ui_macros.rs" +proc-macro = true + +[lints] +workspace = true + +[features] +default = [] + +[dependencies] +proc-macro2.workspace = true +quote.workspace = true +syn.workspace = true diff --git a/crates/settings_ui_macros/LICENSE-GPL b/crates/settings_ui_macros/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/settings_ui_macros/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/settings_ui_macros/src/settings_ui_macros.rs b/crates/settings_ui_macros/src/settings_ui_macros.rs new file mode 100644 index 0000000000..d83056629d --- /dev/null +++ b/crates/settings_ui_macros/src/settings_ui_macros.rs @@ -0,0 +1,131 @@ +use proc_macro2::TokenStream; +use quote::{ToTokens, quote}; +use syn::{DeriveInput, LitStr, Token, parse_macro_input}; + +/// Derive macro for the `SettingsUI` marker trait. +/// +/// This macro automatically implements the `SettingsUI` trait for the annotated type. +/// The `SettingsUI` trait is a marker trait used to indicate that a type can be +/// displayed in the settings UI. +/// +/// # Example +/// +/// ``` +/// use settings::SettingsUI; +/// use settings_ui_macros::SettingsUI; +/// +/// #[derive(SettingsUI)] +/// #[settings_ui(group = "Standard")] +/// struct MySettings { +/// enabled: bool, +/// count: usize, +/// } +/// ``` +#[proc_macro_derive(SettingsUI, attributes(settings_ui))] +pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let name = &input.ident; + + // Handle generic parameters if present + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let mut group_name = Option::::None; + + for attr in &input.attrs { + if attr.path().is_ident("settings_ui") { + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("group") { + if group_name.is_some() { + return Err(meta.error("Only one 'group' path can be specified")); + } + meta.input.parse::()?; + let lit: LitStr = meta.input.parse()?; + group_name = Some(lit.value()); + } + Ok(()) + }) + .unwrap_or_else(|e| panic!("in #[settings_ui] attribute: {}", e)); + } + } + + // let mut root_item = vec![]; + // for field in struct { + // + // match field::settings_ui_render() + // Group(items) => { + // let item = items.map(|item| something); + // item + // items.push(item::settings_ui_render()); + // root_item.push(Group(items)); + // }, + // Leaf(item) => { + // root_item.push(item); + // } + // } + // } + // + // group.items = struct.fields.map((field_name, field_type) => quote! { SettingsUIItem::Item {path: #field_type::settings_ui_path().unwrap_or_else(|| #field_name), item: if field.attrs.render { #render } else field::settings_ui_render()}}) + // } + + fn map_ui_item_to_render(path: &str, ty: TokenStream) -> TokenStream { + quote! { + settings::SettingsUIItem { + item: match #ty::settings_ui_render() { + settings::SettingsUIRender::Group{title, items} => settings::SettingsUIItemVariant::Group { + title, + path: #path, + group: settings::SettingsUIItemGroup { items }, + }, + settings::SettingsUIRender::Item(item) => settings::SettingsUIItemVariant::Item { + path: #path, + item, + }, + settings::SettingsUIRender::None => settings::SettingsUIItemVariant::None, + } + } + } + } + + let ui_render_fn_body = if let Some(group_name) = group_name { + let fields = match input.data { + syn::Data::Struct(data_struct) => data_struct + .fields + .iter() + .map(|field| { + ( + field.ident.clone().expect("tuple fields").to_string(), + field.ty.to_token_stream(), + ) + }) + .collect(), + syn::Data::Enum(data_enum) => vec![], // todo! enums + syn::Data::Union(data_union) => unimplemented!("Derive SettingsUI for unions"), + }; + let items = fields + .into_iter() + .map(|(name, ty)| map_ui_item_to_render(&name, ty)); + quote! { + settings::SettingsUIRender::Group{ title: #group_name, items: vec![#(#items),*] } + } + } else { + quote! { + settings::SettingsUIRender::None + } + }; + + let settings_ui_item_fn_body = map_ui_item_to_render("todo! define path", quote! { Self }); + + let expanded = quote! { + impl #impl_generics settings::SettingsUI for #name #ty_generics #where_clause { + fn settings_ui_render() -> settings::SettingsUIRender { + #ui_render_fn_body + } + + fn settings_ui_item() -> settings::SettingsUIItem { + #settings_ui_item_fn_body + } + } + }; + + proc_macro::TokenStream::from(expanded) +} diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 635e3e2ca5..7993fdef49 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -6,7 +6,7 @@ use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels, use schemars::JsonSchema; use serde_derive::{Deserialize, Serialize}; -use settings::SettingsSources; +use settings::{SettingsSources, SettingsUI}; use std::path::PathBuf; use task::Shell; use theme::FontFamilyName; @@ -24,7 +24,7 @@ pub struct Toolbar { pub breadcrumbs: bool, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize, SettingsUI)] pub struct TerminalSettings { pub shell: Shell, pub working_directory: WorkingDirectory, diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index df147cfe92..339589f99e 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -13,7 +13,7 @@ use gpui::{ use refineable::Refineable; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; -use settings::{ParameterizedJsonSchema, Settings, SettingsSources}; +use settings::{ParameterizedJsonSchema, Settings, SettingsSources, SettingsUI}; use std::sync::Arc; use util::ResultExt as _; use util::schemars::replace_subschema; @@ -87,7 +87,7 @@ impl From for String { } /// Customizable settings for the UI and theme system. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, SettingsUI)] 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. diff --git a/crates/title_bar/Cargo.toml b/crates/title_bar/Cargo.toml index cf178e2850..f60ac7c301 100644 --- a/crates/title_bar/Cargo.toml +++ b/crates/title_bar/Cargo.toml @@ -42,7 +42,7 @@ rpc.workspace = true schemars.workspace = true serde.workspace = true settings.workspace = true -settings_ui.workspace = true +keymap_editor.workspace = true smallvec.workspace = true story = { workspace = true, optional = true } telemetry.workspace = true diff --git a/crates/title_bar/src/title_bar.rs b/crates/title_bar/src/title_bar.rs index b84a2800b6..35e5feabfb 100644 --- a/crates/title_bar/src/title_bar.rs +++ b/crates/title_bar/src/title_bar.rs @@ -27,10 +27,10 @@ use gpui::{ IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, actions, div, }; +use keymap_editor; use onboarding_banner::OnboardingBanner; use project::Project; use settings::Settings as _; -use settings_ui::keybindings; use std::sync::Arc; use theme::ActiveTheme; use title_bar_settings::TitleBarSettings; @@ -681,7 +681,7 @@ impl TitleBar { "Settings Profiles", zed_actions::settings_profile_selector::Toggle.boxed_clone(), ) - .action("Key Bindings", Box::new(keybindings::OpenKeymapEditor)) + .action("Key Bindings", Box::new(keymap_editor::OpenKeymapEditor)) .action( "Themes…", zed_actions::theme_selector::Toggle::default().boxed_clone(), @@ -729,7 +729,7 @@ impl TitleBar { "Settings Profiles", zed_actions::settings_profile_selector::Toggle.boxed_clone(), ) - .action("Key Bindings", Box::new(keybindings::OpenKeymapEditor)) + .action("Key Bindings", Box::new(keymap_editor::OpenKeymapEditor)) .action( "Themes…", zed_actions::theme_selector::Toggle::default().boxed_clone(), diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index a98e984d80..d674899370 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -1,9 +1,10 @@ use db::anyhow; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{Settings, SettingsSources, SettingsUI}; -#[derive(Copy, Clone, Deserialize, Debug)] +#[derive(Copy, Clone, Deserialize, Debug, SettingsUI)] +#[settings_ui(group = "Title Bar")] pub struct TitleBarSettings { pub show_branch_icon: bool, pub show_onboarding_banner: bool, diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 9da01e6f44..e3ec2d414c 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -39,7 +39,9 @@ use object::Object; use schemars::JsonSchema; use serde::Deserialize; use serde_derive::Serialize; -use settings::{Settings, SettingsSources, SettingsStore, update_settings_file}; +use settings::{ + SettingsUI, Settings, SettingsSources, SettingsStore, update_settings_file, +}; use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals}; use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; @@ -1774,7 +1776,7 @@ struct CursorShapeSettings { pub insert: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, SettingsUI)] struct VimSettings { pub default_mode: Mode, pub toggle_relative_line_numbers: bool, diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs index 6f60d3f21f..dd9f122161 100644 --- a/crates/vim_mode_setting/src/vim_mode_setting.rs +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -6,7 +6,7 @@ use anyhow::Result; use gpui::App; -use settings::{Settings, SettingsSources}; +use settings::{Settings, SettingsSources, SettingsUI}; /// Initializes the `vim_mode_setting` crate. pub fn init(cx: &mut App) { @@ -17,6 +17,7 @@ pub fn init(cx: &mut App) { /// Whether or not to enable Vim mode. /// /// Default: false +#[derive(SettingsUI)] pub struct VimModeSetting(pub bool); impl Settings for VimModeSetting { @@ -43,6 +44,7 @@ impl Settings for VimModeSetting { /// Whether or not to enable Helix mode. /// /// Default: false +#[derive(SettingsUI)] pub struct HelixModeSetting(pub bool); impl Settings for HelixModeSetting { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 3485fcca43..18af720a74 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -17,7 +17,7 @@ use gpui::{ use project::{Project, ProjectEntryId, ProjectPath}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsLocation, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsLocation, SettingsSources}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -49,7 +49,7 @@ impl Default for SaveOptions { } } -#[derive(Deserialize)] +#[derive(Deserialize, SettingsUI)] 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)] +#[derive(Deserialize, SettingsUI)] pub struct PreviewTabsSettings { pub enabled: bool, pub enable_preview_from_file_finder: bool, diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 5635347514..b564ac23ad 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -6,9 +6,9 @@ use collections::HashMap; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; -#[derive(Deserialize)] +#[derive(Deserialize, SettingsUI)] pub struct WorkspaceSettings { pub active_pane_modifiers: ActivePanelModifiers, pub bottom_dock_layout: BottomDockLayout, @@ -204,7 +204,7 @@ pub struct WorkspaceSettingsContent { pub close_on_file_delete: Option, } -#[derive(Deserialize)] +#[derive(Deserialize, SettingsUI)] pub struct TabBarSettings { pub show: bool, pub show_nav_history_buttons: bool, diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index b18d3509be..6110b13dc1 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -4,10 +4,10 @@ use anyhow::Context as _; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsSources}; +use settings::{SettingsUI, Settings, SettingsSources}; use util::paths::PathMatcher; -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, SettingsUI)] pub struct WorktreeSettings { pub file_scan_inclusions: PathMatcher, pub file_scan_exclusions: PathMatcher, diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 6f4ead9ebb..62910089f2 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -131,6 +131,7 @@ serde_json.workspace = true session.workspace = true settings.workspace = true settings_ui.workspace = true +keymap_editor.workspace = true shellexpand.workspace = true smol.workspace = true snippet_provider.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index e99c8b564b..6f40b975b6 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -632,6 +632,7 @@ pub fn main() { svg_preview::init(cx); onboarding::init(cx); settings_ui::init(cx); + keymap_editor::init(cx); extensions_ui::init(cx); zeta::init(cx); inspector_ui::init(app_state.clone(), cx); diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 1b9657dcc6..f237a17d97 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -1482,7 +1482,7 @@ fn reload_keymaps(cx: &mut App, mut user_key_bindings: Vec) { workspace::NewWindow, )]); // todo: nicer api here? - settings_ui::keybindings::KeymapEventChannel::trigger_keymap_changed(cx); + keymap_editor::KeymapEventChannel::trigger_keymap_changed(cx); } pub fn load_default_keymap(cx: &mut App) { diff --git a/crates/zed/src/zed/app_menus.rs b/crates/zed/src/zed/app_menus.rs index 6c7ab0b374..342fd26cb7 100644 --- a/crates/zed/src/zed/app_menus.rs +++ b/crates/zed/src/zed/app_menus.rs @@ -1,6 +1,5 @@ use collab_ui::collab_panel; use gpui::{Menu, MenuItem, OsAction}; -use settings_ui::keybindings; use terminal_view::terminal_panel; pub fn app_menus() -> Vec { @@ -17,7 +16,7 @@ pub fn app_menus() -> Vec { name: "Settings".into(), items: vec![ MenuItem::action("Open Settings", super::OpenSettings), - MenuItem::action("Open Key Bindings", keybindings::OpenKeymapEditor), + MenuItem::action("Open Key Bindings", keymap_editor::OpenKeymapEditor), MenuItem::action("Open Default Settings", super::OpenDefaultSettings), MenuItem::action( "Open Default Key Bindings", diff --git a/crates/zlog_settings/src/zlog_settings.rs b/crates/zlog_settings/src/zlog_settings.rs index b58cbcc143..b75ab29d8b 100644 --- a/crates/zlog_settings/src/zlog_settings.rs +++ b/crates/zlog_settings/src/zlog_settings.rs @@ -3,7 +3,7 @@ use anyhow::Result; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsStore}; +use settings::{SettingsUI, Settings, SettingsStore}; pub fn init(cx: &mut App) { ZlogSettings::register(cx); @@ -15,7 +15,7 @@ pub fn init(cx: &mut App) { .detach(); } -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUI)] pub struct ZlogSettings { #[serde(default, flatten)] pub scopes: std::collections::HashMap,