Compare commits

...
Sign in to create a new pull request.

7 commits

Author SHA1 Message Date
Anthony
e42d5a8264 Add more notes
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-26 15:53:46 -04:00
Anthony
8ff656c999 Update boolean in settings ui to show up as switch fields
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-26 15:43:20 -04:00
Anthony
1fd0f7a46b Improve macro to show items
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-26 15:36:53 -04:00
Anthony
c1631b6e8c Add group setting titles
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-26 13:55:25 -04:00
Anthony
ba9d109289 Add settings_ui(group) attribute to macro
Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-26 12:33:38 -04:00
Anthony
15f634f8cc Move keymap editor into it's own crate and create settings ui crate
We also change the structure of the settings ui macro. The trait is
still a requirement on the Settings trait implementation, but it returns
a SettingUIItemVariant now, which the settings ui crate will take
adventage of to generate UI

This allows us to get around circular dependency errors and still get
the type system to ensure all settings fulfill the settings UI crate

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-26 12:00:10 -04:00
Anthony
3c0ec5f612 Start work on creating the inital structure for the settings UI
We created a proc macro that derives the settings ui trait on types and
added that trait as a marker on Settings trait. Then we added the derive
macro on all settings

Co-authored-by: Ben Kunkle <ben@zed.dev>
2025-08-25 15:50:42 -04:00
59 changed files with 610 additions and 156 deletions

76
Cargo.lock generated
View file

@ -8953,6 +8953,46 @@ dependencies = [
"uuid", "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]] [[package]]
name = "khronos-egl" name = "khronos-egl"
version = "6.0.0" version = "6.0.0"
@ -14856,6 +14896,7 @@ dependencies = [
"serde_derive", "serde_derive",
"serde_json", "serde_json",
"serde_json_lenient", "serde_json_lenient",
"settings_ui_macros",
"smallvec", "smallvec",
"tree-sitter", "tree-sitter",
"tree-sitter-json", "tree-sitter-json",
@ -14891,39 +14932,25 @@ name = "settings_ui"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"collections",
"command_palette", "command_palette",
"command_palette_hooks", "command_palette_hooks",
"component",
"db",
"editor", "editor",
"feature_flags", "feature_flags",
"fs",
"fuzzy",
"gpui", "gpui",
"itertools 0.14.0",
"language",
"log",
"menu",
"notifications",
"paths",
"project",
"search",
"serde",
"serde_json",
"settings", "settings",
"telemetry",
"tempfile",
"theme", "theme",
"tree-sitter-json",
"tree-sitter-rust",
"ui", "ui",
"ui_input",
"util", "util",
"vim",
"workspace", "workspace",
"workspace-hack", ]
"zed_actions",
[[package]]
name = "settings_ui_macros"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
] ]
[[package]] [[package]]
@ -16739,6 +16766,7 @@ dependencies = [
"db", "db",
"gpui", "gpui",
"http_client", "http_client",
"keymap_editor",
"notifications", "notifications",
"pretty_assertions", "pretty_assertions",
"project", "project",
@ -16747,7 +16775,6 @@ dependencies = [
"schemars", "schemars",
"serde", "serde",
"settings", "settings",
"settings_ui",
"smallvec", "smallvec",
"story", "story",
"telemetry", "telemetry",
@ -20459,6 +20486,7 @@ dependencies = [
"itertools 0.14.0", "itertools 0.14.0",
"jj_ui", "jj_ui",
"journal", "journal",
"keymap_editor",
"language", "language",
"language_extension", "language_extension",
"language_model", "language_model",

View file

@ -54,6 +54,8 @@ members = [
"crates/deepseek", "crates/deepseek",
"crates/diagnostics", "crates/diagnostics",
"crates/docs_preprocessor", "crates/docs_preprocessor",
"crates/edit_prediction",
"crates/edit_prediction_button",
"crates/editor", "crates/editor",
"crates/eval", "crates/eval",
"crates/explorer_command_injector", "crates/explorer_command_injector",
@ -82,13 +84,12 @@ members = [
"crates/http_client_tls", "crates/http_client_tls",
"crates/icons", "crates/icons",
"crates/image_viewer", "crates/image_viewer",
"crates/edit_prediction",
"crates/edit_prediction_button",
"crates/inspector_ui", "crates/inspector_ui",
"crates/install_cli", "crates/install_cli",
"crates/jj", "crates/jj",
"crates/jj_ui", "crates/jj_ui",
"crates/journal", "crates/journal",
"crates/keymap_editor",
"crates/language", "crates/language",
"crates/language_extension", "crates/language_extension",
"crates/language_model", "crates/language_model",
@ -146,6 +147,7 @@ members = [
"crates/settings", "crates/settings",
"crates/settings_profile_selector", "crates/settings_profile_selector",
"crates/settings_ui", "crates/settings_ui",
"crates/settings_ui_macros",
"crates/snippet", "crates/snippet",
"crates/snippet_provider", "crates/snippet_provider",
"crates/snippets_ui", "crates/snippets_ui",
@ -156,9 +158,9 @@ members = [
"crates/streaming_diff", "crates/streaming_diff",
"crates/sum_tree", "crates/sum_tree",
"crates/supermaven", "crates/supermaven",
"crates/system_specs",
"crates/supermaven_api", "crates/supermaven_api",
"crates/svg_preview", "crates/svg_preview",
"crates/system_specs",
"crates/tab_switcher", "crates/tab_switcher",
"crates/task", "crates/task",
"crates/tasks_ui", "crates/tasks_ui",
@ -314,6 +316,7 @@ install_cli = { path = "crates/install_cli" }
jj = { path = "crates/jj" } jj = { path = "crates/jj" }
jj_ui = { path = "crates/jj_ui" } jj_ui = { path = "crates/jj_ui" }
journal = { path = "crates/journal" } journal = { path = "crates/journal" }
keymap_editor = { path = "crates/keymap_editor" }
language = { path = "crates/language" } language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" } language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" } language_model = { path = "crates/language_model" }
@ -373,6 +376,7 @@ semantic_version = { path = "crates/semantic_version" }
session = { path = "crates/session" } session = { path = "crates/session" }
settings = { path = "crates/settings" } settings = { path = "crates/settings" }
settings_ui = { path = "crates/settings_ui" } settings_ui = { path = "crates/settings_ui" }
settings_ui_macros = { path = "crates/settings_ui_macros" }
snippet = { path = "crates/snippet" } snippet = { path = "crates/snippet" }
snippet_provider = { path = "crates/snippet_provider" } snippet_provider = { path = "crates/snippet_provider" }
snippets_ui = { path = "crates/snippets_ui" } snippets_ui = { path = "crates/snippets_ui" }

View file

@ -4,13 +4,13 @@ use collections::HashMap;
use gpui::{App, SharedString}; use gpui::{App, SharedString};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
AllAgentServersSettings::register(cx); AllAgentServersSettings::register(cx);
} }
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug)] #[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUI)]
pub struct AllAgentServersSettings { pub struct AllAgentServersSettings {
pub gemini: Option<AgentServerSettings>, pub gemini: Option<AgentServerSettings>,
pub claude: Option<AgentServerSettings>, pub claude: Option<AgentServerSettings>,

View file

@ -8,7 +8,7 @@ use gpui::{App, Pixels, SharedString};
use language_model::LanguageModel; use language_model::LanguageModel;
use schemars::{JsonSchema, json_schema}; use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use std::borrow::Cow; use std::borrow::Cow;
pub use crate::agent_profile::*; pub use crate::agent_profile::*;
@ -48,7 +48,7 @@ pub enum NotifyWhenAgentWaiting {
Never, Never,
} }
#[derive(Default, Clone, Debug)] #[derive(Default, Clone, Debug, SettingsUI)]
pub struct AgentSettings { pub struct AgentSettings {
pub enabled: bool, pub enabled: bool,
pub button: bool, pub button: bool,

View file

@ -2,10 +2,10 @@ use anyhow::Result;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
/// Settings for slash commands. /// Settings for slash commands.
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] #[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUI)]
pub struct SlashCommandSettings { pub struct SlashCommandSettings {
/// Settings for the `/cargo-workspace` slash command. /// Settings for the `/cargo-workspace` slash command.
#[serde(default)] #[serde(default)]

View file

@ -2,9 +2,9 @@ use anyhow::Result;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; 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 { pub struct AudioSettings {
/// Opt into the new audio system. /// Opt into the new audio system.
#[serde(rename = "experimental.rodio_audio", default)] #[serde(rename = "experimental.rodio_audio", default)]

View file

@ -10,7 +10,7 @@ use paths::remote_servers_dir;
use release_channel::{AppCommitSha, ReleaseChannel}; use release_channel::{AppCommitSha, ReleaseChannel};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsStore}; use settings::{SettingsUI, Settings, SettingsSources, SettingsStore};
use smol::{fs, io::AsyncReadExt}; use smol::{fs, io::AsyncReadExt};
use smol::{fs::File, process::Command}; use smol::{fs::File, process::Command};
use std::{ use std::{
@ -113,6 +113,7 @@ impl Drop for MacOsUnmounter {
} }
} }
#[derive(SettingsUI)]
struct AutoUpdateSetting(bool); struct AutoUpdateSetting(bool);
/// Whether or not to automatically check for updates. /// Whether or not to automatically check for updates.

View file

@ -2,9 +2,9 @@ use anyhow::Result;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; 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 struct CallSettings {
pub mute_on_join: bool, pub mute_on_join: bool,
pub share_on_join: bool, pub share_on_join: bool,

View file

@ -31,7 +31,7 @@ use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage}; use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use std::{ use std::{
any::TypeId, any::TypeId,
convert::TryFrom, convert::TryFrom,
@ -101,7 +101,7 @@ pub struct ClientSettingsContent {
server_url: Option<String>, server_url: Option<String>,
} }
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
pub struct ClientSettings { pub struct ClientSettings {
pub server_url: String, pub server_url: String,
} }
@ -127,7 +127,7 @@ pub struct ProxySettingsContent {
proxy: Option<String>, proxy: Option<String>,
} }
#[derive(Deserialize, Default)] #[derive(Deserialize, Default, SettingsUI)]
pub struct ProxySettings { pub struct ProxySettings {
pub proxy: Option<String>, pub proxy: Option<String>,
} }
@ -504,7 +504,7 @@ impl<T: 'static> Drop for PendingEntitySubscription<T> {
} }
} }
#[derive(Copy, Clone, Deserialize, Debug)] #[derive(Copy, Clone, Deserialize, Debug, SettingsUI)]
pub struct TelemetrySettings { pub struct TelemetrySettings {
pub diagnostics: bool, pub diagnostics: bool,
pub metrics: bool, pub metrics: bool,

View file

@ -1,10 +1,10 @@
use gpui::Pixels; use gpui::Pixels;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use workspace::dock::DockPosition; use workspace::dock::DockPosition;
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug, SettingsUI)]
pub struct CollaborationPanelSettings { pub struct CollaborationPanelSettings {
pub button: bool, pub button: bool,
pub dock: DockPosition, pub dock: DockPosition,
@ -20,7 +20,7 @@ pub enum ChatPanelButton {
WhenInCall, WhenInCall,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug, SettingsUI)]
pub struct ChatPanelSettings { pub struct ChatPanelSettings {
pub button: ChatPanelButton, pub button: ChatPanelButton,
pub dock: DockPosition, pub dock: DockPosition,
@ -43,7 +43,7 @@ pub struct ChatPanelSettingsContent {
pub default_width: Option<f32>, pub default_width: Option<f32>,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug, SettingsUI)]
pub struct NotificationPanelSettings { pub struct NotificationPanelSettings {
pub button: bool, pub button: bool,
pub dock: DockPosition, pub dock: DockPosition,
@ -66,7 +66,7 @@ pub struct PanelSettingsContent {
pub default_width: Option<f32>, pub default_width: Option<f32>,
} }
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)] #[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUI)]
pub struct MessageEditorSettings { pub struct MessageEditorSettings {
/// Whether to automatically replace emoji shortcodes with emoji characters. /// Whether to automatically replace emoji shortcodes with emoji characters.
/// For example: typing `:wave:` gets replaced with `👋`. /// For example: typing `:wave:` gets replaced with `👋`.

View file

@ -2,7 +2,7 @@ use dap_types::SteppingGranularity;
use gpui::{App, Global}; use gpui::{App, Global};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -12,7 +12,7 @@ pub enum DebugPanelDockPosition {
Right, Right,
} }
#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)] #[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUI)]
#[serde(default)] #[serde(default)]
pub struct DebuggerSettings { pub struct DebuggerSettings {
/// Determines the stepping granularity. /// Determines the stepping granularity.

View file

@ -6,12 +6,12 @@ use language::CursorShape;
use project::project_settings::DiagnosticSeverity; use project::project_settings::DiagnosticSeverity;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, VsCodeSettings}; use settings::{SettingsUI, Settings, SettingsSources, VsCodeSettings};
use util::serde::default_true; use util::serde::default_true;
/// Imports from the VSCode settings at /// Imports from the VSCode settings at
/// https://code.visualstudio.com/docs/reference/default-settings /// https://code.visualstudio.com/docs/reference/default-settings
#[derive(Deserialize, Clone)] #[derive(Deserialize, Clone, SettingsUI)]
pub struct EditorSettings { pub struct EditorSettings {
pub cursor_blink: bool, pub cursor_blink: bool,
pub cursor_shape: Option<CursorShape>, pub cursor_shape: Option<CursorShape>,

View file

@ -3,10 +3,10 @@ use collections::HashMap;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use std::sync::Arc; use std::sync::Arc;
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] #[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUI)]
pub struct ExtensionSettings { pub struct ExtensionSettings {
/// The extensions that should be automatically installed by Zed. /// The extensions that should be automatically installed by Zed.
/// ///

View file

@ -1,9 +1,9 @@
use anyhow::Result; use anyhow::Result;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; 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 struct FileFinderSettings {
pub file_icons: bool, pub file_icons: bool,
pub modal_max_width: Option<FileFinderWidth>, pub modal_max_width: Option<FileFinderWidth>,

View file

@ -5,7 +5,7 @@ use git::GitHostingProviderRegistry;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore}; use settings::{Settings, SettingsStore, SettingsUI};
use url::Url; use url::Url;
use util::ResultExt as _; use util::ResultExt as _;
@ -78,7 +78,7 @@ pub struct GitHostingProviderConfig {
pub name: String, pub name: String,
} }
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, SettingsUI)]
pub struct GitHostingProviderSettings { pub struct GitHostingProviderSettings {
/// The list of custom Git hosting providers. /// The list of custom Git hosting providers.
#[serde(default)] #[serde(default)]

View file

@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels; use gpui::Pixels;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use workspace::dock::DockPosition; use workspace::dock::DockPosition;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@ -77,7 +77,7 @@ pub struct GitPanelSettingsContent {
pub collapse_untracked_diff: Option<bool>, pub collapse_untracked_diff: Option<bool>,
} }
#[derive(Deserialize, Debug, Clone, PartialEq)] #[derive(Deserialize, Debug, Clone, PartialEq, SettingsUI)]
pub struct GitPanelSettings { pub struct GitPanelSettings {
pub button: bool, pub button: bool,
pub dock: DockPosition, pub dock: DockPosition,

View file

@ -2,7 +2,7 @@ use editor::{Editor, EditorSettings, MultiBufferSnapshot};
use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity}; use gpui::{App, Entity, FocusHandle, Focusable, Subscription, Task, WeakEntity};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use std::{fmt::Write, num::NonZeroU32, time::Duration}; use std::{fmt::Write, num::NonZeroU32, time::Duration};
use text::{Point, Selection}; use text::{Point, Selection};
use ui::{ 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")] #[serde(rename_all = "snake_case")]
pub(crate) enum LineIndicatorFormat { pub(crate) enum LineIndicatorFormat {
Short, Short,

View file

@ -16,6 +16,13 @@ pub(crate) fn derive_action(input: TokenStream) -> TokenStream {
let mut deprecated = None; let mut deprecated = None;
let mut doc_str: Option<String> = None; let mut doc_str: Option<String> = None;
/*
*
* #[action()]
* Struct Foo {
* bar: bool // is bar considered an attribute
}
*/
for attr in &input.attrs { for attr in &input.attrs {
if attr.path().is_ident("action") { if attr.path().is_ident("action") {
attr.parse_nested_meta(|meta| { attr.parse_nested_meta(|meta| {

View file

@ -1,10 +1,10 @@
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
/// The settings for the image viewer. /// The settings for the image viewer.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, SettingsUI)]
pub struct ImageViewerSettings { pub struct ImageViewerSettings {
/// The unit to use for displaying image file sizes. /// The unit to use for displaying image file sizes.
/// ///

View file

@ -5,7 +5,7 @@ use editor::{Editor, SelectionEffects};
use gpui::{App, AppContext as _, Context, Window, actions}; use gpui::{App, AppContext as _, Context, Window, actions};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions,
path::{Path, PathBuf}, path::{Path, PathBuf},
@ -22,7 +22,7 @@ actions!(
); );
/// Settings specific to journaling /// Settings specific to journaling
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUI)]
pub struct JournalSettings { pub struct JournalSettings {
/// The path of the directory where journal entries are stored. /// The path of the directory where journal entries are stored.
/// ///

View file

@ -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"] }

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -1,3 +1,5 @@
mod ui_components;
use std::{ use std::{
cmp::{self}, cmp::{self},
ops::{Not as _, Range}, ops::{Not as _, Range},
@ -35,7 +37,7 @@ use workspace::{
}; };
use crate::{ use crate::{
keybindings::persistence::KEYBINDING_EDITORS, persistence::KEYBINDING_EDITORS,
ui_components::{ ui_components::{
keystroke_input::{ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording}, keystroke_input::{ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording},
table::{ColumnWidths, ResizeBehavior, Table, TableInteractionState}, table::{ColumnWidths, ResizeBehavior, Table, TableInteractionState},

View file

@ -17,7 +17,7 @@ use serde::{
}; };
use settings::{ use settings::{
ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore, ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore, SettingsUI,
}; };
use shellexpand; use shellexpand;
use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc}; 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. /// The settings for all languages.
#[derive(Debug, Clone)] #[derive(Debug, Clone, SettingsUI)]
pub struct AllLanguageSettings { pub struct AllLanguageSettings {
/// The edit prediction settings. /// The edit prediction settings.
pub edit_predictions: EditPredictionSettings, pub edit_predictions: EditPredictionSettings,

View file

@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use crate::provider::{ use crate::provider::{
self, self,
@ -29,7 +29,7 @@ pub fn init_settings(cx: &mut App) {
AllLanguageModelSettings::register(cx); AllLanguageModelSettings::register(cx);
} }
#[derive(Default)] #[derive(Default, SettingsUI)]
pub struct AllLanguageModelSettings { pub struct AllLanguageModelSettings {
pub anthropic: AnthropicSettings, pub anthropic: AnthropicSettings,
pub bedrock: AmazonBedrockSettings, pub bedrock: AmazonBedrockSettings,

View file

@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels; use gpui::Pixels;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -18,7 +18,7 @@ pub enum ShowIndentGuides {
Never, Never,
} }
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] #[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUI)]
pub struct OutlinePanelSettings { pub struct OutlinePanelSettings {
pub button: bool, pub button: bool,
pub default_width: Pixels, pub default_width: Pixels,

View file

@ -948,7 +948,7 @@ pub enum PulledDiagnostics {
/// Whether to disable all AI features in Zed. /// Whether to disable all AI features in Zed.
/// ///
/// Default: false /// Default: false
#[derive(Copy, Clone, Debug)] #[derive(Copy, Clone, Debug, settings::SettingsUI)]
pub struct DisableAiSettings { pub struct DisableAiSettings {
pub disable_ai: bool, pub disable_ai: bool,
} }

View file

@ -18,8 +18,8 @@ use rpc::{
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{ use settings::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources, SettingsUI, InvalidSettingsError, LocalSettingsKind, Settings,
SettingsStore, parse_json_with_comments, watch_config_file, SettingsLocation, SettingsSources, SettingsStore, parse_json_with_comments, watch_config_file,
}; };
use std::{ use std::{
collections::BTreeMap, collections::BTreeMap,
@ -36,7 +36,7 @@ use crate::{
worktree_store::{WorktreeStore, WorktreeStoreEvent}, worktree_store::{WorktreeStore, WorktreeStoreEvent},
}; };
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUI)]
pub struct ProjectSettings { pub struct ProjectSettings {
/// Configuration for language servers. /// Configuration for language servers.
/// ///

View file

@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels; use gpui::Pixels;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
@ -28,7 +28,7 @@ pub enum EntrySpacing {
Standard, Standard,
} }
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)] #[derive(Deserialize, Debug, Clone, Copy, PartialEq, SettingsUI)]
pub struct ProjectPanelSettings { pub struct ProjectPanelSettings {
pub button: bool, pub button: bool,
pub hide_gitignore: bool, pub hide_gitignore: bool,

View file

@ -19,7 +19,7 @@ use remote::ssh_session::{ConnectionIdentifier, SshPortForwardOption};
use remote::{SshConnectionOptions, SshPlatform, SshRemoteClient}; use remote::{SshConnectionOptions, SshPlatform, SshRemoteClient};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
ActiveTheme, Color, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, ActiveTheme, Color, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
@ -28,7 +28,7 @@ use ui::{
use util::serde::default_true; use util::serde::default_true;
use workspace::{AppState, ModalView, Workspace}; use workspace::{AppState, ModalView, Workspace};
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
pub struct SshSettings { pub struct SshSettings {
pub ssh_connections: Option<Vec<SshConnection>>, pub ssh_connections: Option<Vec<SshConnection>>,
/// Whether to read ~/.ssh/config for ssh connection sources. /// Whether to read ~/.ssh/config for ssh connection sources.

View file

@ -4,9 +4,9 @@ use editor::EditorSettings;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
#[derive(Debug, Default)] #[derive(Debug, Default, SettingsUI)]
pub struct JupyterSettings { pub struct JupyterSettings {
pub kernel_selections: HashMap<String, String>, pub kernel_selections: HashMap<String, String>,
} }

View file

@ -31,6 +31,7 @@ schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_derive.workspace = true serde_derive.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings_ui_macros.workspace = true
serde_json_lenient.workspace = true serde_json_lenient.workspace = true
smallvec.workspace = true smallvec.workspace = true
tree-sitter-json.workspace = true tree-sitter-json.workspace = true

View file

@ -1,13 +1,17 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use crate::{Settings, SettingsSources, VsCodeSettings}; use crate as settings;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; 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. /// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
/// ///
/// Default: VSCode /// 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 { pub enum BaseKeymap {
#[default] #[default]
VSCode, VSCode,

View file

@ -5,6 +5,7 @@ mod keymap_file;
mod settings_file; mod settings_file;
mod settings_json; mod settings_json;
mod settings_store; mod settings_store;
mod settings_ui;
mod vscode_import; mod vscode_import;
use gpui::{App, Global}; use gpui::{App, Global};
@ -25,6 +26,9 @@ pub use settings_store::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
SettingsStore, SettingsStore,
}; };
pub use settings_ui::*;
// Re-export the derive macro
pub use settings_ui_macros::SettingsUI;
pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -31,14 +31,15 @@ use util::{
pub type EditorconfigProperties = ec4rs::Properties; pub type EditorconfigProperties = ec4rs::Properties;
use crate::{ use crate::{
ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, VsCodeSettings, ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUIItem,
WorktreeId, parse_json_with_comments, update_value_in_json_text, 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. /// A value that can be defined as a user setting.
/// ///
/// Settings can be loaded from a combination of multiple JSON files. /// 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 /// 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 /// be deserialized. If this is `None`, then the setting will be deserialized
/// from the root object. /// from the root object.
@ -272,6 +273,7 @@ trait AnySettingValue: 'static + Send + Sync {
text: &mut String, text: &mut String,
edits: &mut Vec<(Range<usize>, String)>, edits: &mut Vec<(Range<usize>, String)>,
); );
fn settings_ui_item(&self) -> SettingsUIItem;
} }
struct DeserializedSetting(Box<dyn Any>); struct DeserializedSetting(Box<dyn Any>);
@ -604,6 +606,12 @@ impl SettingsStore {
rx rx
} }
pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUIItem> {
self.setting_values
.values()
.map(|item| item.settings_ui_item())
}
} }
impl SettingsStore { impl SettingsStore {
@ -1498,6 +1506,10 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
edits, edits,
); );
} }
fn settings_ui_item(&self) -> SettingsUIItem {
<T as SettingsUI>::settings_ui_item()
}
} }
#[cfg(test)] #[cfg(test)]
@ -1505,7 +1517,10 @@ mod tests {
use crate::VsCodeSettingsSource; use crate::VsCodeSettingsSource;
use super::*; use super::*;
// This is so the SettingsUI macro can still work properly
use crate as settings;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use settings_ui_macros::SettingsUI;
use unindent::Unindent; use unindent::Unindent;
#[gpui::test] #[gpui::test]
@ -2048,14 +2063,14 @@ mod tests {
pretty_assertions::assert_eq!(new, expected); pretty_assertions::assert_eq!(new, expected);
} }
#[derive(Debug, PartialEq, Deserialize)] #[derive(Debug, PartialEq, Deserialize, SettingsUI)]
struct UserSettings { struct UserSettings {
name: String, name: String,
age: u32, age: u32,
staff: bool, staff: bool,
} }
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUI)]
struct UserSettingsContent { struct UserSettingsContent {
name: Option<String>, name: Option<String>,
age: Option<u32>, age: Option<u32>,
@ -2075,7 +2090,7 @@ mod tests {
} }
} }
#[derive(Debug, Deserialize, PartialEq)] #[derive(Debug, Deserialize, PartialEq, SettingsUI)]
struct TurboSetting(bool); struct TurboSetting(bool);
impl Settings for TurboSetting { impl Settings for TurboSetting {
@ -2089,7 +2104,7 @@ mod tests {
fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {} fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
} }
#[derive(Clone, Debug, PartialEq, Deserialize)] #[derive(Clone, Debug, PartialEq, Deserialize, SettingsUI)]
struct MultiKeySettings { struct MultiKeySettings {
#[serde(default)] #[serde(default)]
key1: String, key1: String,
@ -2122,7 +2137,7 @@ mod tests {
} }
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, SettingsUI)]
struct JournalSettings { struct JournalSettings {
pub path: String, pub path: String,
pub hour_format: HourFormat, 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 { struct LanguageSettings {
#[serde(default)] #[serde(default)]
languages: HashMap<String, LanguageSettingEntry>, languages: HashMap<String, LanguageSettingEntry>,

View file

@ -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<SettingsUIItem>,
}
pub enum SettingsUIItemSingle {
// TODO: default/builtin variants
SwitchField,
Custom(Box<dyn Fn(&dyn Any, &mut Window, &mut App) -> AnyElement>),
}
pub enum SettingsUIRender {
Group {
title: &'static str,
items: Vec<SettingsUIItem>,
},
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 = <Self as Settings>FileContent::settings_ui
}
FOR PATH:
if derive attr also contains "Settings", then we can use <T as Settings>::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
*/

View file

@ -11,45 +11,24 @@ workspace = true
[lib] [lib]
path = "src/settings_ui.rs" path = "src/settings_ui.rs"
[features]
default = []
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
collections.workspace = true
command_palette.workspace = true command_palette.workspace = true
command_palette_hooks.workspace = true command_palette_hooks.workspace = true
component.workspace = true
db.workspace = true
editor.workspace = true editor.workspace = true
feature_flags.workspace = true feature_flags.workspace = true
fs.workspace = true
fuzzy.workspace = true
gpui.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 theme.workspace = true
tree-sitter-json.workspace = true settings.workspace = true
tree-sitter-rust.workspace = true
ui.workspace = true ui.workspace = true
ui_input.workspace = true
util.workspace = true util.workspace = true
vim.workspace = true
workspace-hack.workspace = true
workspace.workspace = true workspace.workspace = true
zed_actions.workspace = true
[dev-dependencies] # Uncomment other workspace dependencies as needed
db = {"workspace"= true, "features" = ["test-support"]} # assistant.workspace = true
fs = { workspace = true, features = ["test-support"] } # client.workspace = true
gpui = { workspace = true, features = ["test-support"] } # project.workspace = true
project = { workspace = true, features = ["test-support"] } # settings.workspace = true
workspace = { workspace = true, features = ["test-support"] }

View file

@ -5,16 +5,14 @@ use std::any::TypeId;
use command_palette_hooks::CommandPaletteFilter; use command_palette_hooks::CommandPaletteFilter;
use editor::EditorSettingsControls; use editor::EditorSettingsControls;
use feature_flags::{FeatureFlag, FeatureFlagViewExt}; use feature_flags::{FeatureFlag, FeatureFlagViewExt};
use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, actions}; use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, ReadGlobal, actions};
use ui::prelude::*; use settings::{SettingsStore, SettingsUIItemSingle, SettingsUIItemVariant};
use ui::{SwitchField, prelude::*};
use workspace::item::{Item, ItemEvent}; use workspace::item::{Item, ItemEvent};
use workspace::{Workspace, with_active_or_new_workspace}; use workspace::{Workspace, with_active_or_new_workspace};
use crate::appearance_settings_controls::AppearanceSettingsControls; use crate::appearance_settings_controls::AppearanceSettingsControls;
pub mod keybindings;
pub mod ui_components;
pub struct SettingsUiFeatureFlag; pub struct SettingsUiFeatureFlag;
impl FeatureFlag for SettingsUiFeatureFlag { impl FeatureFlag for SettingsUiFeatureFlag {
@ -75,8 +73,6 @@ pub fn init(cx: &mut App) {
.detach(); .detach();
}) })
.detach(); .detach();
keybindings::init(cx);
} }
pub struct SettingsPage { pub struct SettingsPage {
@ -125,6 +121,52 @@ impl Render for SettingsPage {
.p_4() .p_4()
.size_full() .size_full()
.gap_4() .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(Label::new("Settings").size(LabelSize::Large))
.child( .child(
v_flex().gap_1().child(Label::new("Appearance")).child( v_flex().gap_1().child(Label::new("Appearance")).child(

View file

@ -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

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -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::<String>::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::<Token![=]>()?;
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)
}

View file

@ -6,7 +6,7 @@ use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels,
use schemars::JsonSchema; use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use settings::SettingsSources; use settings::{SettingsSources, SettingsUI};
use std::path::PathBuf; use std::path::PathBuf;
use task::Shell; use task::Shell;
use theme::FontFamilyName; use theme::FontFamilyName;
@ -24,7 +24,7 @@ pub struct Toolbar {
pub breadcrumbs: bool, pub breadcrumbs: bool,
} }
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize, SettingsUI)]
pub struct TerminalSettings { pub struct TerminalSettings {
pub shell: Shell, pub shell: Shell,
pub working_directory: WorkingDirectory, pub working_directory: WorkingDirectory,

View file

@ -13,7 +13,7 @@ use gpui::{
use refineable::Refineable; use refineable::Refineable;
use schemars::{JsonSchema, json_schema}; use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{ParameterizedJsonSchema, Settings, SettingsSources}; use settings::{ParameterizedJsonSchema, Settings, SettingsSources, SettingsUI};
use std::sync::Arc; use std::sync::Arc;
use util::ResultExt as _; use util::ResultExt as _;
use util::schemars::replace_subschema; use util::schemars::replace_subschema;
@ -87,7 +87,7 @@ impl From<UiDensity> for String {
} }
/// Customizable settings for the UI and theme system. /// Customizable settings for the UI and theme system.
#[derive(Clone, PartialEq)] #[derive(Clone, PartialEq, SettingsUI)]
pub struct ThemeSettings { pub struct ThemeSettings {
/// The UI font size. Determines the size of text in the UI, /// The UI font size. Determines the size of text in the UI,
/// as well as the size of a [gpui::Rems] unit. /// as well as the size of a [gpui::Rems] unit.

View file

@ -42,7 +42,7 @@ rpc.workspace = true
schemars.workspace = true schemars.workspace = true
serde.workspace = true serde.workspace = true
settings.workspace = true settings.workspace = true
settings_ui.workspace = true keymap_editor.workspace = true
smallvec.workspace = true smallvec.workspace = true
story = { workspace = true, optional = true } story = { workspace = true, optional = true }
telemetry.workspace = true telemetry.workspace = true

View file

@ -27,10 +27,10 @@ use gpui::{
IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled, IntoElement, MouseButton, ParentElement, Render, StatefulInteractiveElement, Styled,
Subscription, WeakEntity, Window, actions, div, Subscription, WeakEntity, Window, actions, div,
}; };
use keymap_editor;
use onboarding_banner::OnboardingBanner; use onboarding_banner::OnboardingBanner;
use project::Project; use project::Project;
use settings::Settings as _; use settings::Settings as _;
use settings_ui::keybindings;
use std::sync::Arc; use std::sync::Arc;
use theme::ActiveTheme; use theme::ActiveTheme;
use title_bar_settings::TitleBarSettings; use title_bar_settings::TitleBarSettings;
@ -681,7 +681,7 @@ impl TitleBar {
"Settings Profiles", "Settings Profiles",
zed_actions::settings_profile_selector::Toggle.boxed_clone(), zed_actions::settings_profile_selector::Toggle.boxed_clone(),
) )
.action("Key Bindings", Box::new(keybindings::OpenKeymapEditor)) .action("Key Bindings", Box::new(keymap_editor::OpenKeymapEditor))
.action( .action(
"Themes…", "Themes…",
zed_actions::theme_selector::Toggle::default().boxed_clone(), zed_actions::theme_selector::Toggle::default().boxed_clone(),
@ -729,7 +729,7 @@ impl TitleBar {
"Settings Profiles", "Settings Profiles",
zed_actions::settings_profile_selector::Toggle.boxed_clone(), zed_actions::settings_profile_selector::Toggle.boxed_clone(),
) )
.action("Key Bindings", Box::new(keybindings::OpenKeymapEditor)) .action("Key Bindings", Box::new(keymap_editor::OpenKeymapEditor))
.action( .action(
"Themes…", "Themes…",
zed_actions::theme_selector::Toggle::default().boxed_clone(), zed_actions::theme_selector::Toggle::default().boxed_clone(),

View file

@ -1,9 +1,10 @@
use db::anyhow; use db::anyhow;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; 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 struct TitleBarSettings {
pub show_branch_icon: bool, pub show_branch_icon: bool,
pub show_onboarding_banner: bool, pub show_onboarding_banner: bool,

View file

@ -39,7 +39,9 @@ use object::Object;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::Deserialize; use serde::Deserialize;
use serde_derive::Serialize; 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 state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use std::{mem, ops::Range, sync::Arc}; use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType; use surrounds::SurroundsType;
@ -1774,7 +1776,7 @@ struct CursorShapeSettings {
pub insert: Option<CursorShape>, pub insert: Option<CursorShape>,
} }
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
struct VimSettings { struct VimSettings {
pub default_mode: Mode, pub default_mode: Mode,
pub toggle_relative_line_numbers: bool, pub toggle_relative_line_numbers: bool,

View file

@ -6,7 +6,7 @@
use anyhow::Result; use anyhow::Result;
use gpui::App; use gpui::App;
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources, SettingsUI};
/// Initializes the `vim_mode_setting` crate. /// Initializes the `vim_mode_setting` crate.
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
@ -17,6 +17,7 @@ pub fn init(cx: &mut App) {
/// Whether or not to enable Vim mode. /// Whether or not to enable Vim mode.
/// ///
/// Default: false /// Default: false
#[derive(SettingsUI)]
pub struct VimModeSetting(pub bool); pub struct VimModeSetting(pub bool);
impl Settings for VimModeSetting { impl Settings for VimModeSetting {
@ -43,6 +44,7 @@ impl Settings for VimModeSetting {
/// Whether or not to enable Helix mode. /// Whether or not to enable Helix mode.
/// ///
/// Default: false /// Default: false
#[derive(SettingsUI)]
pub struct HelixModeSetting(pub bool); pub struct HelixModeSetting(pub bool);
impl Settings for HelixModeSetting { impl Settings for HelixModeSetting {

View file

@ -17,7 +17,7 @@ use gpui::{
use project::{Project, ProjectEntryId, ProjectPath}; use project::{Project, ProjectEntryId, ProjectPath};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsLocation, SettingsSources}; use settings::{SettingsUI, Settings, SettingsLocation, SettingsSources};
use smallvec::SmallVec; use smallvec::SmallVec;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
@ -49,7 +49,7 @@ impl Default for SaveOptions {
} }
} }
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
pub struct ItemSettings { pub struct ItemSettings {
pub git_status: bool, pub git_status: bool,
pub close_position: ClosePosition, pub close_position: ClosePosition,
@ -59,7 +59,7 @@ pub struct ItemSettings {
pub show_close_button: ShowCloseButton, pub show_close_button: ShowCloseButton,
} }
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
pub struct PreviewTabsSettings { pub struct PreviewTabsSettings {
pub enabled: bool, pub enabled: bool,
pub enable_preview_from_file_finder: bool, pub enable_preview_from_file_finder: bool,

View file

@ -6,9 +6,9 @@ use collections::HashMap;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
pub struct WorkspaceSettings { pub struct WorkspaceSettings {
pub active_pane_modifiers: ActivePanelModifiers, pub active_pane_modifiers: ActivePanelModifiers,
pub bottom_dock_layout: BottomDockLayout, pub bottom_dock_layout: BottomDockLayout,
@ -204,7 +204,7 @@ pub struct WorkspaceSettingsContent {
pub close_on_file_delete: Option<bool>, pub close_on_file_delete: Option<bool>,
} }
#[derive(Deserialize)] #[derive(Deserialize, SettingsUI)]
pub struct TabBarSettings { pub struct TabBarSettings {
pub show: bool, pub show: bool,
pub show_nav_history_buttons: bool, pub show_nav_history_buttons: bool,

View file

@ -4,10 +4,10 @@ use anyhow::Context as _;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{SettingsUI, Settings, SettingsSources};
use util::paths::PathMatcher; use util::paths::PathMatcher;
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq, SettingsUI)]
pub struct WorktreeSettings { pub struct WorktreeSettings {
pub file_scan_inclusions: PathMatcher, pub file_scan_inclusions: PathMatcher,
pub file_scan_exclusions: PathMatcher, pub file_scan_exclusions: PathMatcher,

View file

@ -131,6 +131,7 @@ serde_json.workspace = true
session.workspace = true session.workspace = true
settings.workspace = true settings.workspace = true
settings_ui.workspace = true settings_ui.workspace = true
keymap_editor.workspace = true
shellexpand.workspace = true shellexpand.workspace = true
smol.workspace = true smol.workspace = true
snippet_provider.workspace = true snippet_provider.workspace = true

View file

@ -632,6 +632,7 @@ pub fn main() {
svg_preview::init(cx); svg_preview::init(cx);
onboarding::init(cx); onboarding::init(cx);
settings_ui::init(cx); settings_ui::init(cx);
keymap_editor::init(cx);
extensions_ui::init(cx); extensions_ui::init(cx);
zeta::init(cx); zeta::init(cx);
inspector_ui::init(app_state.clone(), cx); inspector_ui::init(app_state.clone(), cx);

View file

@ -1482,7 +1482,7 @@ fn reload_keymaps(cx: &mut App, mut user_key_bindings: Vec<KeyBinding>) {
workspace::NewWindow, workspace::NewWindow,
)]); )]);
// todo: nicer api here? // 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) { pub fn load_default_keymap(cx: &mut App) {

View file

@ -1,6 +1,5 @@
use collab_ui::collab_panel; use collab_ui::collab_panel;
use gpui::{Menu, MenuItem, OsAction}; use gpui::{Menu, MenuItem, OsAction};
use settings_ui::keybindings;
use terminal_view::terminal_panel; use terminal_view::terminal_panel;
pub fn app_menus() -> Vec<Menu> { pub fn app_menus() -> Vec<Menu> {
@ -17,7 +16,7 @@ pub fn app_menus() -> Vec<Menu> {
name: "Settings".into(), name: "Settings".into(),
items: vec![ items: vec![
MenuItem::action("Open Settings", super::OpenSettings), 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 Settings", super::OpenDefaultSettings),
MenuItem::action( MenuItem::action(
"Open Default Key Bindings", "Open Default Key Bindings",

View file

@ -3,7 +3,7 @@ use anyhow::Result;
use gpui::App; use gpui::App;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore}; use settings::{SettingsUI, Settings, SettingsStore};
pub fn init(cx: &mut App) { pub fn init(cx: &mut App) {
ZlogSettings::register(cx); ZlogSettings::register(cx);
@ -15,7 +15,7 @@ pub fn init(cx: &mut App) {
.detach(); .detach();
} }
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUI)]
pub struct ZlogSettings { pub struct ZlogSettings {
#[serde(default, flatten)] #[serde(default, flatten)]
pub scopes: std::collections::HashMap<String, String>, pub scopes: std::collections::HashMap<String, String>,