Detailed changes
@@ -8951,6 +8951,44 @@ dependencies = [
"uuid",
]
+[[package]]
+name = "keymap_editor"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "collections",
+ "command_palette",
+ "component",
+ "db",
+ "editor",
+ "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 +14894,7 @@ dependencies = [
"serde_derive",
"serde_json",
"serde_json_lenient",
+ "settings_ui_macros",
"smallvec",
"tree-sitter",
"tree-sitter-json",
@@ -14891,39 +14930,28 @@ 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",
+ "smallvec",
"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",
+ "workspace-hack",
]
[[package]]
@@ -16739,6 +16767,7 @@ dependencies = [
"db",
"gpui",
"http_client",
+ "keymap_editor",
"notifications",
"pretty_assertions",
"project",
@@ -16747,7 +16776,6 @@ dependencies = [
"schemars",
"serde",
"settings",
- "settings_ui",
"smallvec",
"story",
"telemetry",
@@ -20458,6 +20486,7 @@ dependencies = [
"itertools 0.14.0",
"jj_ui",
"journal",
+ "keymap_editor",
"language",
"language_extension",
"language_model",
@@ -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" }
@@ -1922,7 +1922,10 @@
"debugger": {
"stepping_granularity": "line",
"save_breakpoints": true,
+ "timeout": 2000,
"dock": "bottom",
+ "log_dap_communications": true,
+ "format_dap_log_messages": true,
"button": true
},
// Configures any number of settings profiles that are temporarily applied on
@@ -6,13 +6,13 @@ use collections::HashMap;
use gpui::{App, SharedString};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
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<BuiltinAgentServerSettings>,
pub claude: Option<CustomAgentServerSettings>,
@@ -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::{Settings, SettingsSources, SettingsUi};
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,
@@ -2,10 +2,10 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
/// 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)]
@@ -2,9 +2,9 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
-#[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)]
@@ -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::{Settings, SettingsSources, SettingsStore, SettingsUi};
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.
@@ -2,9 +2,9 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
-#[derive(Deserialize, Debug)]
+#[derive(Deserialize, Debug, SettingsUi)]
pub struct CallSettings {
pub mute_on_join: bool,
pub share_on_join: bool,
@@ -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::{Settings, SettingsSources, SettingsUi};
use std::{
any::TypeId,
convert::TryFrom,
@@ -101,7 +101,7 @@ pub struct ClientSettingsContent {
server_url: Option<String>,
}
-#[derive(Deserialize)]
+#[derive(Deserialize, SettingsUi)]
pub struct ClientSettings {
pub server_url: String,
}
@@ -127,7 +127,7 @@ pub struct ProxySettingsContent {
proxy: Option<String>,
}
-#[derive(Deserialize, Default)]
+#[derive(Deserialize, Default, SettingsUi)]
pub struct ProxySettings {
pub proxy: Option<String>,
}
@@ -520,7 +520,7 @@ impl<T: 'static> Drop for PendingEntitySubscription<T> {
}
}
-#[derive(Copy, Clone, Deserialize, Debug)]
+#[derive(Copy, Clone, Deserialize, Debug, SettingsUi)]
pub struct TelemetrySettings {
pub diagnostics: bool,
pub metrics: bool,
@@ -1,10 +1,10 @@
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
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<f32>,
}
-#[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<f32>,
}
-#[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 `π`.
@@ -2,9 +2,9 @@ use dap_types::SteppingGranularity;
use gpui::{App, Global};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
-#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
+#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
#[serde(rename_all = "snake_case")]
pub enum DebugPanelDockPosition {
Left,
@@ -12,12 +12,14 @@ pub enum DebugPanelDockPosition {
Right,
}
-#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy)]
+#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUi)]
#[serde(default)]
+#[settings_ui(group = "Debugger", path = "debugger")]
pub struct DebuggerSettings {
/// Determines the stepping granularity.
///
/// Default: line
+ #[settings_ui(skip)]
pub stepping_granularity: SteppingGranularity,
/// Whether the breakpoints should be reused across Zed sessions.
///
@@ -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::{Settings, SettingsSources, SettingsUi, 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<CursorShape>,
@@ -3,10 +3,10 @@ use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
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.
///
@@ -1,9 +1,9 @@
use anyhow::Result;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
-#[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<FileFinderWidth>,
@@ -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)]
@@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
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<bool>,
}
-#[derive(Deserialize, Debug, Clone, PartialEq)]
+#[derive(Deserialize, Debug, Clone, PartialEq, SettingsUi)]
pub struct GitPanelSettings {
pub button: bool,
pub dock: DockPosition,
@@ -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::{Settings, SettingsSources, SettingsUi};
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,
@@ -16,6 +16,13 @@ pub(crate) fn derive_action(input: TokenStream) -> TokenStream {
let mut deprecated = None;
let mut doc_str: Option<String> = 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| {
@@ -1,10 +1,10 @@
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
/// 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.
///
@@ -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::{Settings, SettingsSources, SettingsUi};
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.
///
@@ -0,0 +1,53 @@
+[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
+component.workspace = true
+db.workspace = true
+editor.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"] }
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -5,6 +5,8 @@ use std::{
time::Duration,
};
+mod ui_components;
+
use anyhow::{Context as _, anyhow};
use collections::{HashMap, HashSet};
use editor::{CompletionProvider, Editor, EditorEvent};
@@ -34,8 +36,10 @@ use workspace::{
register_serializable_item,
};
+pub use ui_components::*;
+
use crate::{
- keybindings::persistence::KEYBINDING_EDITORS,
+ persistence::KEYBINDING_EDITORS,
ui_components::{
keystroke_input::{ClearKeystrokes, KeystrokeInput, StartRecording, StopRecording},
table::{ColumnWidths, ResizeBehavior, Table, TableInteractionState},
@@ -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,
@@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
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,
@@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
#[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,
@@ -952,7 +952,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,
}
@@ -19,7 +19,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
- SettingsStore, parse_json_with_comments, watch_config_file,
+ SettingsStore, SettingsUi, 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.
///
@@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
#[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,
@@ -20,7 +20,7 @@ use remote::{
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
use theme::ThemeSettings;
use ui::{
ActiveTheme, Color, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label,
@@ -29,7 +29,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<Vec<SshConnection>>,
/// Whether to read ~/.ssh/config for ssh connection sources.
@@ -4,9 +4,9 @@ use editor::EditorSettings;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
-#[derive(Debug, Default)]
+#[derive(Debug, Default, SettingsUi)]
pub struct JupyterSettings {
pub kernel_selections: HashMap<String, String>,
}
@@ -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
@@ -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,
@@ -4,6 +4,7 @@ mod keymap_file;
mod settings_file;
mod settings_json;
mod settings_store;
+mod settings_ui;
mod vscode_import;
use gpui::{App, Global};
@@ -23,6 +24,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)]
@@ -87,9 +87,9 @@ pub fn update_value_in_json_text<'a>(
}
/// * `replace_key` - When an exact key match according to `key_path` is found, replace the key with `replace_key` if `Some`.
-fn replace_value_in_json_text(
+pub fn replace_value_in_json_text<T: AsRef<str>>(
text: &str,
- key_path: &[&str],
+ key_path: &[T],
tab_size: usize,
new_value: Option<&Value>,
replace_key: Option<&str>,
@@ -141,7 +141,7 @@ fn replace_value_in_json_text(
let found_key = text
.get(key_range.clone())
.map(|key_text| {
- depth < key_path.len() && key_text == format!("\"{}\"", key_path[depth])
+ depth < key_path.len() && key_text == format!("\"{}\"", key_path[depth].as_ref())
})
.unwrap_or(false);
@@ -226,13 +226,13 @@ fn replace_value_in_json_text(
}
} else {
// We have key paths, construct the sub objects
- let new_key = key_path[depth];
+ let new_key = key_path[depth].as_ref();
// We don't have the key, construct the nested objects
let mut new_value =
serde_json::to_value(new_value.unwrap_or(&serde_json::Value::Null)).unwrap();
for key in key_path[(depth + 1)..].iter().rev() {
- new_value = serde_json::json!({ key.to_string(): new_value });
+ new_value = serde_json::json!({ key.as_ref().to_string(): new_value });
}
if let Some(first_key_start) = first_key_start {
@@ -465,7 +465,7 @@ pub fn append_top_level_array_value_in_json_text(
}
let (mut replace_range, mut replace_value) =
- replace_value_in_json_text("", &[], tab_size, Some(new_value), None);
+ replace_value_in_json_text::<&str>("", &[], tab_size, Some(new_value), None);
replace_range.start = close_bracket_start;
replace_range.end = close_bracket_start;
@@ -7,7 +7,7 @@ use futures::{
channel::{mpsc, oneshot},
future::LocalBoxFuture,
};
-use gpui::{App, AsyncApp, BorrowAppContext, Global, Task, UpdateGlobal};
+use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal};
use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name};
use schemars::JsonSchema;
@@ -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, SettingsUiEntry,
+ VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text,
+ 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.
@@ -284,6 +285,7 @@ trait AnySettingValue: 'static + Send + Sync {
text: &mut String,
edits: &mut Vec<(Range<usize>, String)>,
);
+ fn settings_ui_item(&self) -> SettingsUiEntry;
}
struct DeserializedSetting(Box<dyn Any>);
@@ -480,6 +482,11 @@ impl SettingsStore {
self.raw_global_settings.as_ref()
}
+ /// Access the raw JSON value of the default settings.
+ pub fn raw_default_settings(&self) -> &Value {
+ &self.raw_default_settings
+ }
+
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut App) -> Self {
let mut this = Self::new(cx);
@@ -532,49 +539,10 @@ impl SettingsStore {
}
}
- pub fn update_settings_file<T: Settings>(
+ fn update_settings_file_inner(
&self,
fs: Arc<dyn Fs>,
- update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
- ) {
- self.setting_file_updates_tx
- .unbounded_send(Box::new(move |cx: AsyncApp| {
- async move {
- let old_text = Self::load_settings(&fs).await?;
- let new_text = cx.read_global(|store: &SettingsStore, cx| {
- store.new_text_for_update::<T>(old_text, |content| update(content, cx))
- })?;
- let settings_path = paths::settings_file().as_path();
- if fs.is_file(settings_path).await {
- let resolved_path =
- fs.canonicalize(settings_path).await.with_context(|| {
- format!("Failed to canonicalize settings path {:?}", settings_path)
- })?;
-
- fs.atomic_write(resolved_path.clone(), new_text)
- .await
- .with_context(|| {
- format!("Failed to write settings to file {:?}", resolved_path)
- })?;
- } else {
- fs.atomic_write(settings_path.to_path_buf(), new_text)
- .await
- .with_context(|| {
- format!("Failed to write settings to file {:?}", settings_path)
- })?;
- }
-
- anyhow::Ok(())
- }
- .boxed_local()
- }))
- .ok();
- }
-
- pub fn import_vscode_settings(
- &self,
- fs: Arc<dyn Fs>,
- vscode_settings: VsCodeSettings,
+ update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>,
) -> oneshot::Receiver<Result<()>> {
let (tx, rx) = oneshot::channel::<Result<()>>();
self.setting_file_updates_tx
@@ -582,9 +550,7 @@ impl SettingsStore {
async move {
let res = async move {
let old_text = Self::load_settings(&fs).await?;
- let new_text = cx.read_global(|store: &SettingsStore, _cx| {
- store.get_vscode_edits(old_text, &vscode_settings)
- })?;
+ let new_text = update(old_text, cx)?;
let settings_path = paths::settings_file().as_path();
if fs.is_file(settings_path).await {
let resolved_path =
@@ -607,7 +573,6 @@ impl SettingsStore {
format!("Failed to write settings to file {:?}", settings_path)
})?;
}
-
anyhow::Ok(())
}
.await;
@@ -622,9 +587,67 @@ impl SettingsStore {
}
.boxed_local()
}))
- .ok();
+ .map_err(|err| anyhow::format_err!("Failed to update settings file: {}", err))
+ .log_with_level(log::Level::Warn);
+ return rx;
+ }
+
+ pub fn update_settings_file_at_path(
+ &self,
+ fs: Arc<dyn Fs>,
+ path: &[&str],
+ new_value: serde_json::Value,
+ ) -> oneshot::Receiver<Result<()>> {
+ let key_path = path
+ .into_iter()
+ .cloned()
+ .map(SharedString::new)
+ .collect::<Vec<_>>();
+ let update = move |mut old_text: String, cx: AsyncApp| {
+ cx.read_global(|store: &SettingsStore, _cx| {
+ // todo(settings_ui) use `update_value_in_json_text` for merging new and old objects with comment preservation, needs old value though...
+ let (range, replacement) = replace_value_in_json_text(
+ &old_text,
+ key_path.as_slice(),
+ store.json_tab_size(),
+ Some(&new_value),
+ None,
+ );
+ old_text.replace_range(range, &replacement);
+ old_text
+ })
+ };
+ self.update_settings_file_inner(fs, update)
+ }
- rx
+ pub fn update_settings_file<T: Settings>(
+ &self,
+ fs: Arc<dyn Fs>,
+ update: impl 'static + Send + FnOnce(&mut T::FileContent, &App),
+ ) {
+ _ = self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
+ cx.read_global(|store: &SettingsStore, cx| {
+ store.new_text_for_update::<T>(old_text, |content| update(content, cx))
+ })
+ });
+ }
+
+ pub fn import_vscode_settings(
+ &self,
+ fs: Arc<dyn Fs>,
+ vscode_settings: VsCodeSettings,
+ ) -> oneshot::Receiver<Result<()>> {
+ self.update_settings_file_inner(fs, move |old_text: String, cx: AsyncApp| {
+ cx.read_global(|store: &SettingsStore, _cx| {
+ store.get_vscode_edits(old_text, &vscode_settings)
+ })
+ })
+ }
+
+ pub fn settings_ui_items(&self) -> impl IntoIterator<Item = SettingsUiEntry> {
+ self.setting_values
+ .values()
+ .map(|item| item.settings_ui_item())
}
}
@@ -1520,6 +1543,10 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
edits,
);
}
+
+ fn settings_ui_item(&self) -> SettingsUiEntry {
+ <T as SettingsUi>::settings_ui_entry()
+ }
}
#[cfg(test)]
@@ -1527,7 +1554,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]
@@ -2070,14 +2100,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<String>,
age: Option<u32>,
@@ -2097,7 +2127,7 @@ mod tests {
}
}
- #[derive(Debug, Deserialize, PartialEq)]
+ #[derive(Debug, Deserialize, PartialEq, SettingsUi)]
struct TurboSetting(bool);
impl Settings for TurboSetting {
@@ -2111,7 +2141,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,
@@ -2144,7 +2174,7 @@ mod tests {
}
}
- #[derive(Debug, Deserialize)]
+ #[derive(Debug, Deserialize, SettingsUi)]
struct JournalSettings {
pub path: String,
pub hour_format: HourFormat,
@@ -2245,7 +2275,7 @@ mod tests {
);
}
- #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
+ #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
struct LanguageSettings {
#[serde(default)]
languages: HashMap<String, LanguageSettingEntry>,
@@ -0,0 +1,118 @@
+use anyhow::Context as _;
+use fs::Fs;
+use gpui::{AnyElement, App, AppContext as _, ReadGlobal as _, Window};
+use smallvec::SmallVec;
+
+use crate::SettingsStore;
+
+pub trait SettingsUi {
+ fn settings_ui_item() -> SettingsUiItem {
+ SettingsUiItem::None
+ }
+ fn settings_ui_entry() -> SettingsUiEntry;
+}
+
+pub struct SettingsUiEntry {
+ // todo(settings_ui): move this back here once there isn't a None variant
+ // pub path: &'static str,
+ // pub title: &'static str,
+ pub item: SettingsUiEntryVariant,
+}
+
+pub enum SettingsUiEntryVariant {
+ Group {
+ path: &'static str,
+ title: &'static str,
+ items: Vec<SettingsUiEntry>,
+ },
+ Item {
+ path: &'static str,
+ item: SettingsUiItemSingle,
+ },
+ // todo(settings_ui): remove
+ None,
+}
+
+pub enum SettingsUiItemSingle {
+ SwitchField,
+ NumericStepper,
+ ToggleGroup(&'static [&'static str]),
+ /// This should be used when toggle group size > 6
+ DropDown(&'static [&'static str]),
+ Custom(Box<dyn Fn(SettingsValue<serde_json::Value>, &mut Window, &mut App) -> AnyElement>),
+}
+
+pub struct SettingsValue<T> {
+ pub title: &'static str,
+ pub path: SmallVec<[&'static str; 1]>,
+ pub value: Option<T>,
+ pub default_value: T,
+}
+
+impl<T> SettingsValue<T> {
+ pub fn read(&self) -> &T {
+ match &self.value {
+ Some(value) => value,
+ None => &self.default_value,
+ }
+ }
+}
+
+impl SettingsValue<serde_json::Value> {
+ pub fn write_value(path: &SmallVec<[&'static str; 1]>, value: serde_json::Value, cx: &mut App) {
+ let settings_store = SettingsStore::global(cx);
+ let fs = <dyn Fs>::global(cx);
+
+ let rx = settings_store.update_settings_file_at_path(fs.clone(), path.as_slice(), value);
+ let path = path.clone();
+ cx.background_spawn(async move {
+ rx.await?
+ .with_context(|| format!("Failed to update setting at path `{:?}`", path.join(".")))
+ })
+ .detach_and_log_err(cx);
+ }
+}
+
+impl<T: serde::Serialize> SettingsValue<T> {
+ pub fn write(
+ path: &SmallVec<[&'static str; 1]>,
+ value: T,
+ cx: &mut App,
+ ) -> Result<(), serde_json::Error> {
+ SettingsValue::write_value(path, serde_json::to_value(value)?, cx);
+ Ok(())
+ }
+}
+
+pub enum SettingsUiItem {
+ Group {
+ title: &'static str,
+ items: Vec<SettingsUiEntry>,
+ },
+ Single(SettingsUiItemSingle),
+ None,
+}
+
+impl SettingsUi for bool {
+ fn settings_ui_item() -> SettingsUiItem {
+ SettingsUiItem::Single(SettingsUiItemSingle::SwitchField)
+ }
+
+ fn settings_ui_entry() -> SettingsUiEntry {
+ SettingsUiEntry {
+ item: SettingsUiEntryVariant::None,
+ }
+ }
+}
+
+impl SettingsUi for u64 {
+ fn settings_ui_item() -> SettingsUiItem {
+ SettingsUiItem::Single(SettingsUiItemSingle::NumericStepper)
+ }
+
+ fn settings_ui_entry() -> SettingsUiEntry {
+ SettingsUiEntry {
+ item: SettingsUiEntryVariant::None,
+ }
+ }
+}
@@ -2,7 +2,7 @@ use anyhow::{Context as _, Result, anyhow};
use fs::Fs;
use paths::{cursor_settings_file_paths, vscode_settings_file_paths};
use serde_json::{Map, Value};
-use std::{path::Path, rc::Rc, sync::Arc};
+use std::{path::Path, sync::Arc};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum VsCodeSettingsSource {
@@ -21,7 +21,7 @@ impl std::fmt::Display for VsCodeSettingsSource {
pub struct VsCodeSettings {
pub source: VsCodeSettingsSource,
- pub path: Rc<Path>,
+ pub path: Arc<Path>,
content: Map<String, Value>,
}
@@ -11,45 +11,26 @@ 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
+serde.workspace = true
settings.workspace = true
-telemetry.workspace = true
-tempfile.workspace = true
+smallvec.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
+workspace-hack.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
@@ -1,20 +1,24 @@
mod appearance_settings_controls;
use std::any::TypeId;
+use std::ops::{Not, Range};
+use anyhow::Context as _;
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 workspace::item::{Item, ItemEvent};
-use workspace::{Workspace, with_active_or_new_workspace};
+use gpui::{App, Entity, EventEmitter, FocusHandle, Focusable, ReadGlobal, actions};
+use settings::{SettingsStore, SettingsUiEntryVariant, SettingsUiItemSingle, SettingsValue};
+use smallvec::SmallVec;
+use ui::{NumericStepper, SwitchField, ToggleButtonGroup, ToggleButtonSimple, prelude::*};
+use workspace::{
+ Workspace,
+ item::{Item, ItemEvent},
+ 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,18 +79,18 @@ pub fn init(cx: &mut App) {
.detach();
})
.detach();
-
- keybindings::init(cx);
}
pub struct SettingsPage {
focus_handle: FocusHandle,
+ settings_tree: SettingsUiTree,
}
impl SettingsPage {
pub fn new(_workspace: &Workspace, cx: &mut Context<Workspace>) -> Entity<Self> {
cx.new(|cx| Self {
focus_handle: cx.focus_handle(),
+ settings_tree: SettingsUiTree::new(cx),
})
}
}
@@ -119,26 +123,472 @@ impl Item for SettingsPage {
}
}
+// We want to iterate over the side bar with root groups
+// - this is a loop over top level groups, and if any are expanded, recursively displaying their items
+// - Should be able to get all items from a group (flatten a group)
+// - Should be able to toggle/untoggle groups in UI (at least in sidebar)
+// - Search should be available
+// - there should be an index of text -> item mappings, for using fuzzy::match
+// - Do we want to show the parent groups when a item is matched?
+
+struct UIEntry {
+ title: &'static str,
+ path: &'static str,
+ _depth: usize,
+ // a
+ // b < a descendant range < a total descendant range
+ // f | |
+ // g | |
+ // c < |
+ // d |
+ // e <
+ descendant_range: Range<usize>,
+ total_descendant_range: Range<usize>,
+ next_sibling: Option<usize>,
+ // expanded: bool,
+ render: Option<SettingsUiItemSingle>,
+}
+
+struct SettingsUiTree {
+ root_entry_indices: Vec<usize>,
+ entries: Vec<UIEntry>,
+ active_entry_index: usize,
+}
+
+fn build_tree_item(
+ tree: &mut Vec<UIEntry>,
+ group: SettingsUiEntryVariant,
+ depth: usize,
+ prev_index: Option<usize>,
+) {
+ let index = tree.len();
+ tree.push(UIEntry {
+ title: "",
+ path: "",
+ _depth: depth,
+ descendant_range: index + 1..index + 1,
+ total_descendant_range: index + 1..index + 1,
+ render: None,
+ next_sibling: None,
+ });
+ if let Some(prev_index) = prev_index {
+ tree[prev_index].next_sibling = Some(index);
+ }
+ match group {
+ SettingsUiEntryVariant::Group {
+ path,
+ title,
+ items: group_items,
+ } => {
+ tree[index].path = path;
+ tree[index].title = title;
+ for group_item in group_items {
+ let prev_index = tree[index]
+ .descendant_range
+ .is_empty()
+ .not()
+ .then_some(tree[index].descendant_range.end - 1);
+ tree[index].descendant_range.end = tree.len() + 1;
+ build_tree_item(tree, group_item.item, depth + 1, prev_index);
+ tree[index].total_descendant_range.end = tree.len();
+ }
+ }
+ SettingsUiEntryVariant::Item { path, item } => {
+ tree[index].path = path;
+ // todo(settings_ui) create title from path in macro, and use here
+ tree[index].title = path;
+ tree[index].render = Some(item);
+ }
+ SettingsUiEntryVariant::None => {
+ return;
+ }
+ }
+}
+
+impl SettingsUiTree {
+ fn new(cx: &App) -> Self {
+ let settings_store = SettingsStore::global(cx);
+ let mut tree = vec![];
+ let mut root_entry_indices = vec![];
+ for item in settings_store.settings_ui_items() {
+ if matches!(item.item, SettingsUiEntryVariant::None) {
+ continue;
+ }
+
+ assert!(
+ matches!(item.item, SettingsUiEntryVariant::Group { .. }),
+ "top level items must be groups: {:?}",
+ match item.item {
+ SettingsUiEntryVariant::Item { path, .. } => path,
+ _ => unreachable!(),
+ }
+ );
+ let prev_root_entry_index = root_entry_indices.last().copied();
+ root_entry_indices.push(tree.len());
+ build_tree_item(&mut tree, item.item, 0, prev_root_entry_index);
+ }
+
+ root_entry_indices.sort_by_key(|i| tree[*i].title);
+
+ let active_entry_index = root_entry_indices[0];
+ Self {
+ entries: tree,
+ root_entry_indices,
+ active_entry_index,
+ }
+ }
+}
+
+fn render_nav(tree: &SettingsUiTree, _window: &mut Window, cx: &mut Context<SettingsPage>) -> Div {
+ let mut nav = v_flex().p_4().gap_2();
+ for &index in &tree.root_entry_indices {
+ nav = nav.child(
+ div()
+ .id(index)
+ .on_click(cx.listener(move |settings, _, _, _| {
+ settings.settings_tree.active_entry_index = index;
+ }))
+ .child(
+ Label::new(SharedString::new_static(tree.entries[index].title))
+ .size(LabelSize::Large)
+ .when(tree.active_entry_index == index, |this| {
+ this.color(Color::Selected)
+ }),
+ ),
+ );
+ }
+ nav
+}
+
+fn render_content(
+ tree: &SettingsUiTree,
+ window: &mut Window,
+ cx: &mut Context<SettingsPage>,
+) -> impl IntoElement {
+ let Some(entry) = tree.entries.get(tree.active_entry_index) else {
+ return div()
+ .size_full()
+ .child(Label::new(SharedString::new_static("No settings found")).color(Color::Error));
+ };
+ let mut content = v_flex().size_full().gap_4();
+
+ let mut child_index = entry
+ .descendant_range
+ .is_empty()
+ .not()
+ .then_some(entry.descendant_range.start);
+ let mut path = smallvec::smallvec![entry.path];
+
+ while let Some(index) = child_index {
+ let child = &tree.entries[index];
+ child_index = child.next_sibling;
+ if child.render.is_none() {
+ // todo(settings_ui): subgroups?
+ continue;
+ }
+ path.push(child.path);
+ let settings_value = settings_value_from_settings_and_path(
+ path.clone(),
+ // PERF: how to structure this better? There feels like there's a way to avoid the clone
+ // and every value lookup
+ SettingsStore::global(cx).raw_user_settings(),
+ SettingsStore::global(cx).raw_default_settings(),
+ );
+ content = content.child(
+ div()
+ .child(
+ Label::new(SharedString::new_static(tree.entries[index].title))
+ .size(LabelSize::Large)
+ .when(tree.active_entry_index == index, |this| {
+ this.color(Color::Selected)
+ }),
+ )
+ .child(render_item_single(
+ settings_value,
+ child.render.as_ref().unwrap(),
+ window,
+ cx,
+ )),
+ );
+
+ path.pop();
+ }
+
+ return content;
+}
+
impl Render for SettingsPage {
- fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
- v_flex()
+ fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+ div()
+ .grid()
+ .grid_cols(16)
.p_4()
+ .bg(cx.theme().colors().editor_background)
.size_full()
- .gap_4()
- .child(Label::new("Settings").size(LabelSize::Large))
- .child(
- v_flex().gap_1().child(Label::new("Appearance")).child(
- v_flex()
- .elevation_2(cx)
- .child(AppearanceSettingsControls::new()),
- ),
- )
.child(
- v_flex().gap_1().child(Label::new("Editor")).child(
- v_flex()
- .elevation_2(cx)
- .child(EditorSettingsControls::new()),
- ),
+ div()
+ .col_span(2)
+ .h_full()
+ .child(render_nav(&self.settings_tree, window, cx)),
)
+ .child(div().col_span(4).h_full().child(render_content(
+ &self.settings_tree,
+ window,
+ cx,
+ )))
}
}
+
+// todo(settings_ui): remove, only here as inspiration
+#[allow(dead_code)]
+fn render_old_appearance_settings(cx: &mut App) -> impl IntoElement {
+ v_flex()
+ .p_4()
+ .size_full()
+ .gap_4()
+ .child(Label::new("Settings").size(LabelSize::Large))
+ .child(
+ v_flex().gap_1().child(Label::new("Appearance")).child(
+ v_flex()
+ .elevation_2(cx)
+ .child(AppearanceSettingsControls::new()),
+ ),
+ )
+ .child(
+ v_flex().gap_1().child(Label::new("Editor")).child(
+ v_flex()
+ .elevation_2(cx)
+ .child(EditorSettingsControls::new()),
+ ),
+ )
+}
+
+fn element_id_from_path(path: &[&'static str]) -> ElementId {
+ if path.len() == 0 {
+ panic!("Path length must not be zero");
+ } else if path.len() == 1 {
+ ElementId::Name(SharedString::new_static(path[0]))
+ } else {
+ ElementId::from((
+ ElementId::from(SharedString::new_static(path[path.len() - 2])),
+ SharedString::new_static(path[path.len() - 1]),
+ ))
+ }
+}
+
+fn render_item_single(
+ settings_value: SettingsValue<serde_json::Value>,
+ item: &SettingsUiItemSingle,
+ window: &mut Window,
+ cx: &mut App,
+) -> AnyElement {
+ match item {
+ SettingsUiItemSingle::Custom(_) => div()
+ .child(format!("Item: {}", settings_value.path.join(".")))
+ .into_any_element(),
+ SettingsUiItemSingle::SwitchField => {
+ render_any_item(settings_value, render_switch_field, window, cx)
+ }
+ SettingsUiItemSingle::NumericStepper => {
+ render_any_item(settings_value, render_numeric_stepper, window, cx)
+ }
+ SettingsUiItemSingle::ToggleGroup(variants) => {
+ render_toggle_button_group(settings_value, variants, window, cx)
+ }
+ SettingsUiItemSingle::DropDown(_) => {
+ unimplemented!("This")
+ }
+ }
+}
+
+fn read_settings_value_from_path<'a>(
+ settings_contents: &'a serde_json::Value,
+ path: &[&'static str],
+) -> Option<&'a serde_json::Value> {
+ let Some((key, remaining)) = path.split_first() else {
+ return Some(settings_contents);
+ };
+ let Some(value) = settings_contents.get(key) else {
+ return None;
+ };
+
+ read_settings_value_from_path(value, remaining)
+}
+
+fn downcast_any_item<T: serde::de::DeserializeOwned>(
+ settings_value: SettingsValue<serde_json::Value>,
+) -> SettingsValue<T> {
+ let value = settings_value
+ .value
+ .map(|value| serde_json::from_value::<T>(value).expect("value is not a T"));
+ // todo(settings_ui) Create test that constructs UI tree, and asserts that all elements have default values
+ let default_value = serde_json::from_value::<T>(settings_value.default_value)
+ .expect("default value is not an Option<T>");
+ let deserialized_setting_value = SettingsValue {
+ title: settings_value.title,
+ path: settings_value.path,
+ value,
+ default_value,
+ };
+ deserialized_setting_value
+}
+
+fn render_any_item<T: serde::de::DeserializeOwned>(
+ settings_value: SettingsValue<serde_json::Value>,
+ render_fn: impl Fn(SettingsValue<T>, &mut Window, &mut App) -> AnyElement + 'static,
+ window: &mut Window,
+ cx: &mut App,
+) -> AnyElement {
+ let deserialized_setting_value = downcast_any_item(settings_value);
+ render_fn(deserialized_setting_value, window, cx)
+}
+
+fn render_numeric_stepper(
+ value: SettingsValue<u64>,
+ _window: &mut Window,
+ _cx: &mut App,
+) -> AnyElement {
+ let id = element_id_from_path(&value.path);
+ let path = value.path.clone();
+ let num = value.value.unwrap_or_else(|| value.default_value);
+
+ NumericStepper::new(
+ id,
+ num.to_string(),
+ {
+ let path = value.path.clone();
+ move |_, _, cx| {
+ let Some(number) = serde_json::Number::from_u128(num.saturating_sub(1) as u128)
+ else {
+ return;
+ };
+ let new_value = serde_json::Value::Number(number);
+ SettingsValue::write_value(&path, new_value, cx);
+ }
+ },
+ move |_, _, cx| {
+ let Some(number) = serde_json::Number::from_u128(num.saturating_add(1) as u128) else {
+ return;
+ };
+
+ let new_value = serde_json::Value::Number(number);
+
+ SettingsValue::write_value(&path, new_value, cx);
+ },
+ )
+ .style(ui::NumericStepperStyle::Outlined)
+ .into_any_element()
+}
+
+fn render_switch_field(
+ value: SettingsValue<bool>,
+ _window: &mut Window,
+ _cx: &mut App,
+) -> AnyElement {
+ let id = element_id_from_path(&value.path);
+ let path = value.path.clone();
+ SwitchField::new(
+ id,
+ SharedString::new_static(value.title),
+ None,
+ match value.read() {
+ true => ToggleState::Selected,
+ false => ToggleState::Unselected,
+ },
+ move |toggle_state, _, cx| {
+ let new_value = serde_json::Value::Bool(match toggle_state {
+ ToggleState::Indeterminate => {
+ return;
+ }
+ ToggleState::Selected => true,
+ ToggleState::Unselected => false,
+ });
+
+ SettingsValue::write_value(&path, new_value, cx);
+ },
+ )
+ .into_any_element()
+}
+
+fn render_toggle_button_group(
+ value: SettingsValue<serde_json::Value>,
+ variants: &'static [&'static str],
+ _: &mut Window,
+ _: &mut App,
+) -> AnyElement {
+ let value = downcast_any_item::<String>(value);
+
+ fn make_toggle_group<const LEN: usize>(
+ group_name: &'static str,
+ value: SettingsValue<String>,
+ variants: &'static [&'static str],
+ ) -> AnyElement {
+ let mut variants_array: [&'static str; LEN] = ["default"; LEN];
+ variants_array.copy_from_slice(variants);
+ let active_value = value.read();
+
+ let selected_idx = variants_array
+ .iter()
+ .enumerate()
+ .find_map(|(idx, variant)| {
+ if variant == &active_value {
+ Some(idx)
+ } else {
+ None
+ }
+ });
+
+ ToggleButtonGroup::single_row(
+ group_name,
+ variants_array.map(|variant| {
+ let path = value.path.clone();
+ ToggleButtonSimple::new(variant, move |_, _, cx| {
+ SettingsValue::write_value(
+ &path,
+ serde_json::Value::String(variant.to_string()),
+ cx,
+ );
+ })
+ }),
+ )
+ .when_some(selected_idx, |this, ix| this.selected_index(ix))
+ .style(ui::ToggleButtonGroupStyle::Filled)
+ .into_any_element()
+ }
+
+ macro_rules! templ_toggl_with_const_param {
+ ($len:expr) => {
+ if variants.len() == $len {
+ return make_toggle_group::<$len>(value.title, value, variants);
+ }
+ };
+ }
+ templ_toggl_with_const_param!(1);
+ templ_toggl_with_const_param!(2);
+ templ_toggl_with_const_param!(3);
+ templ_toggl_with_const_param!(4);
+ templ_toggl_with_const_param!(5);
+ templ_toggl_with_const_param!(6);
+ unreachable!("Too many variants");
+}
+
+fn settings_value_from_settings_and_path(
+ path: SmallVec<[&'static str; 1]>,
+ user_settings: &serde_json::Value,
+ default_settings: &serde_json::Value,
+) -> SettingsValue<serde_json::Value> {
+ let default_value = read_settings_value_from_path(default_settings, &path)
+ .with_context(|| format!("No default value for item at path {:?}", path.join(".")))
+ .expect("Default value set for item")
+ .clone();
+
+ let value = read_settings_value_from_path(user_settings, &path).cloned();
+ let settings_value = SettingsValue {
+ default_value,
+ value,
+ path: path.clone(),
+ // todo(settings_ui) title for items
+ title: path.last().expect("path non empty"),
+ };
+ return settings_value;
+}
@@ -0,0 +1,22 @@
+[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
+workspace-hack.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,201 @@
+use proc_macro2::TokenStream;
+use quote::{ToTokens, quote};
+use syn::{Data, 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;
+ let mut path_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());
+ } else if meta.path.is_ident("path") {
+ // todo(settings_ui) try get KEY from Settings if possible, and once we do,
+ // if can get key from settings, throw error if path also passed
+ if path_name.is_some() {
+ return Err(meta.error("Only one 'path' can be specified"));
+ }
+ meta.input.parse::<Token![=]>()?;
+ let lit: LitStr = meta.input.parse()?;
+ path_name = Some(lit.value());
+ }
+ Ok(())
+ })
+ .unwrap_or_else(|e| panic!("in #[settings_ui] attribute: {}", e));
+ }
+ }
+
+ if path_name.is_none() && group_name.is_some() {
+ // todo(settings_ui) derive path from settings
+ panic!("path is required when group is specified");
+ }
+
+ let ui_render_fn_body = generate_ui_item_body(group_name.as_ref(), path_name.as_ref(), &input);
+
+ let settings_ui_item_fn_body = path_name
+ .as_ref()
+ .map(|path_name| map_ui_item_to_render(path_name, quote! { Self }))
+ .unwrap_or(quote! {
+ settings::SettingsUiEntry {
+ item: settings::SettingsUiEntryVariant::None
+ }
+ });
+
+ let expanded = quote! {
+ impl #impl_generics settings::SettingsUi for #name #ty_generics #where_clause {
+ fn settings_ui_item() -> settings::SettingsUiItem {
+ #ui_render_fn_body
+ }
+
+ fn settings_ui_entry() -> settings::SettingsUiEntry {
+ #settings_ui_item_fn_body
+ }
+ }
+ };
+
+ proc_macro::TokenStream::from(expanded)
+}
+
+fn map_ui_item_to_render(path: &str, ty: TokenStream) -> TokenStream {
+ quote! {
+ settings::SettingsUiEntry {
+ item: match #ty::settings_ui_item() {
+ settings::SettingsUiItem::Group{title, items} => settings::SettingsUiEntryVariant::Group {
+ title,
+ path: #path,
+ items,
+ },
+ settings::SettingsUiItem::Single(item) => settings::SettingsUiEntryVariant::Item {
+ path: #path,
+ item,
+ },
+ settings::SettingsUiItem::None => settings::SettingsUiEntryVariant::None,
+ }
+ }
+ }
+}
+
+fn generate_ui_item_body(
+ group_name: Option<&String>,
+ path_name: Option<&String>,
+ input: &syn::DeriveInput,
+) -> TokenStream {
+ match (group_name, path_name, &input.data) {
+ (_, _, Data::Union(_)) => unimplemented!("Derive SettingsUi for Unions"),
+ (None, None, Data::Struct(_)) => quote! {
+ settings::SettingsUiItem::None
+ },
+ (Some(_), None, Data::Struct(_)) => quote! {
+ settings::SettingsUiItem::None
+ },
+ (None, Some(_), Data::Struct(_)) => quote! {
+ settings::SettingsUiItem::None
+ },
+ (Some(group_name), _, Data::Struct(data_struct)) => {
+ let fields = data_struct
+ .fields
+ .iter()
+ .filter(|field| {
+ !field.attrs.iter().any(|attr| {
+ let mut has_skip = false;
+ if attr.path().is_ident("settings_ui") {
+ let _ = attr.parse_nested_meta(|meta| {
+ if meta.path.is_ident("skip") {
+ has_skip = true;
+ }
+ Ok(())
+ });
+ }
+
+ has_skip
+ })
+ })
+ .map(|field| {
+ (
+ field.ident.clone().expect("tuple fields").to_string(),
+ field.ty.to_token_stream(),
+ )
+ })
+ .map(|(name, ty)| map_ui_item_to_render(&name, ty));
+
+ quote! {
+ settings::SettingsUiItem::Group{ title: #group_name, items: vec![#(#fields),*] }
+ }
+ }
+ (None, _, Data::Enum(data_enum)) => {
+ let mut lowercase = false;
+ for attr in &input.attrs {
+ if attr.path().is_ident("serde") {
+ attr.parse_nested_meta(|meta| {
+ if meta.path.is_ident("rename_all") {
+ meta.input.parse::<Token![=]>()?;
+ let lit = meta.input.parse::<LitStr>()?.value();
+ // todo(settings_ui) snake case
+ lowercase = lit == "lowercase" || lit == "snake_case";
+ }
+ Ok(())
+ })
+ .ok();
+ }
+ }
+ let length = data_enum.variants.len();
+
+ let variants = data_enum.variants.iter().map(|variant| {
+ let string = variant.ident.clone().to_string();
+
+ if lowercase {
+ string.to_lowercase()
+ } else {
+ string
+ }
+ });
+
+ if length > 6 {
+ quote! {
+ settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::DropDown(&[#(#variants),*]))
+ }
+ } else {
+ quote! {
+ settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::ToggleGroup(&[#(#variants),*]))
+ }
+ }
+ }
+ // todo(settings_ui) discriminated unions
+ (_, _, Data::Enum(_)) => quote! {
+ settings::SettingsUiItem::None
+ },
+ }
+}
@@ -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,
@@ -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<UiDensity> 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.
@@ -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
@@ -29,10 +29,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;
@@ -684,7 +684,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(),
@@ -732,7 +732,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(),
@@ -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", path = "title_bar")]
pub struct TitleBarSettings {
pub show_branch_icon: bool,
pub show_onboarding_banner: bool,
@@ -39,7 +39,7 @@ use object::Object;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
-use settings::{Settings, SettingsSources, SettingsStore, update_settings_file};
+use settings::{Settings, SettingsSources, SettingsStore, SettingsUi, update_settings_file};
use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType;
@@ -1774,7 +1774,7 @@ struct CursorShapeSettings {
pub insert: Option<CursorShape>,
}
-#[derive(Deserialize)]
+#[derive(Deserialize, SettingsUi)]
struct VimSettings {
pub default_mode: Mode,
pub toggle_relative_line_numbers: bool,
@@ -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 {
@@ -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::{Settings, SettingsLocation, SettingsSources, SettingsUi};
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,
@@ -6,9 +6,9 @@ use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
-#[derive(Deserialize)]
+#[derive(Deserialize, SettingsUi)]
pub struct WorkspaceSettings {
pub active_pane_modifiers: ActivePanelModifiers,
pub bottom_dock_layout: BottomDockLayout,
@@ -216,7 +216,7 @@ pub struct WorkspaceSettingsContent {
pub zoomed_padding: Option<bool>,
}
-#[derive(Deserialize)]
+#[derive(Deserialize, SettingsUi)]
pub struct TabBarSettings {
pub show: bool,
pub show_nav_history_buttons: bool,
@@ -4,10 +4,10 @@ use anyhow::Context as _;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsSources};
+use settings::{Settings, SettingsSources, SettingsUi};
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,
@@ -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
@@ -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);
@@ -1491,7 +1491,7 @@ fn reload_keymaps(cx: &mut App, mut user_key_bindings: Vec<KeyBinding>) {
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) {
@@ -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<Menu> {
@@ -17,7 +16,7 @@ pub fn app_menus() -> Vec<Menu> {
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",
@@ -3,7 +3,7 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use settings::{Settings, SettingsStore};
+use settings::{Settings, SettingsStore, SettingsUi};
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<String, String>,