settings ui: Create settings key trait (#37489)

This PR separates out the associated constant `KEY` from the `Settings`
trait into a new trait `SettingsKey`. This allows for the key trait to
be derived using attributes to specify the path so that the new
`SettingsUi` derive macro can use the same attributes to determine top
level settings paths thereby removing the need to duplicate the path in
both `Settings::KEY` and `#[settings_ui(path = "...")]`

Co-authored-by: Ben Kunkle <ben@zed.dev>

Release Notes:

- N/A

---------

Co-authored-by: Ben Kunkle <ben@zed.dev>
This commit is contained in:
Anthony Eid
2025-09-04 15:19:02 -04:00
committed by GitHub
co-authored by Ben Kunkle
parent 223fda2fe2
commit 5f03202b5c
44 changed files with 473 additions and 255 deletions
Generated
+2
View File
@@ -17995,6 +17995,8 @@ version = "0.1.0"
dependencies = [
"anyhow",
"gpui",
"schemars",
"serde",
"settings",
"workspace-hack",
]
+3 -4
View File
@@ -6,13 +6,14 @@ use collections::HashMap;
use gpui::{App, SharedString};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
pub fn init(cx: &mut App) {
AllAgentServersSettings::register(cx);
}
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi)]
#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "agent_servers")]
pub struct AllAgentServersSettings {
pub gemini: Option<BuiltinAgentServerSettings>,
pub claude: Option<CustomAgentServerSettings>,
@@ -75,8 +76,6 @@ pub struct CustomAgentServerSettings {
}
impl settings::Settings for AllAgentServersSettings {
const KEY: Option<&'static str> = Some("agent_servers");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+3 -6
View File
@@ -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, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use std::borrow::Cow;
pub use crate::agent_profile::*;
@@ -223,7 +223,8 @@ impl AgentSettingsContent {
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi)]
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, Default, SettingsUi, SettingsKey)]
#[settings_key(key = "agent", fallback_key = "assistant")]
pub struct AgentSettingsContent {
/// Whether the Agent is enabled.
///
@@ -399,10 +400,6 @@ pub struct ContextServerPresetContent {
}
impl Settings for AgentSettings {
const KEY: Option<&'static str> = Some("agent");
const FALLBACK_KEY: Option<&'static str> = Some("assistant");
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
type FileContent = AgentSettingsContent;
@@ -2,10 +2,11 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
/// Settings for slash commands.
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi)]
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "slash_commands")]
pub struct SlashCommandSettings {
/// Settings for the `/cargo-workspace` slash command.
#[serde(default)]
@@ -21,8 +22,6 @@ pub struct CargoWorkspaceCommandSettings {
}
impl Settings for SlashCommandSettings {
const KEY: Option<&'static str> = Some("slash_commands");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
+4 -5
View File
@@ -2,9 +2,9 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
pub struct AudioSettings {
/// Opt into the new audio system.
#[serde(rename = "experimental.rodio_audio", default)]
@@ -12,8 +12,9 @@ pub struct AudioSettings {
}
/// Configuration of audio in Zed.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[serde(default)]
#[settings_key(key = "audio")]
pub struct AudioSettingsContent {
/// Whether to use the experimental audio system
#[serde(rename = "experimental.rodio_audio", default)]
@@ -21,8 +22,6 @@ pub struct AudioSettingsContent {
}
impl Settings for AudioSettings {
const KEY: Option<&'static str> = Some("audio");
type FileContent = AudioSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
+3 -4
View File
@@ -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, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi};
use smol::{fs, io::AsyncReadExt};
use smol::{fs::File, process::Command};
use std::{
@@ -118,13 +118,12 @@ struct AutoUpdateSetting(bool);
/// Whether or not to automatically check for updates.
///
/// Default: true
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi)]
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey)]
#[serde(transparent)]
#[settings_key(key = "auto_update")]
struct AutoUpdateSettingContent(bool);
impl Settings for AutoUpdateSetting {
const KEY: Option<&'static str> = Some("auto_update");
type FileContent = AutoUpdateSettingContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+3 -4
View File
@@ -2,7 +2,7 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Deserialize, Debug)]
pub struct CallSettings {
@@ -11,7 +11,8 @@ pub struct CallSettings {
}
/// Configuration of voice calls in Zed.
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "calls")]
pub struct CallSettingsContent {
/// Whether the microphone should be muted when joining a channel or a call.
///
@@ -25,8 +26,6 @@ pub struct CallSettingsContent {
}
impl Settings for CallSettings {
const KEY: Option<&'static str> = Some("calls");
type FileContent = CallSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+7 -10
View File
@@ -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, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use std::{
any::TypeId,
convert::TryFrom,
@@ -96,7 +96,8 @@ actions!(
]
);
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct ClientSettingsContent {
server_url: Option<String>,
}
@@ -107,8 +108,6 @@ pub struct ClientSettings {
}
impl Settings for ClientSettings {
const KEY: Option<&'static str> = None;
type FileContent = ClientSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -122,7 +121,8 @@ impl Settings for ClientSettings {
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
}
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct ProxySettingsContent {
proxy: Option<String>,
}
@@ -133,8 +133,6 @@ pub struct ProxySettings {
}
impl Settings for ProxySettings {
const KEY: Option<&'static str> = None;
type FileContent = ProxySettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -527,7 +525,8 @@ pub struct TelemetrySettings {
}
/// Control what info is collected by Zed.
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "telemetry")]
pub struct TelemetrySettingsContent {
/// Send debug info like crash reports.
///
@@ -540,8 +539,6 @@ pub struct TelemetrySettingsContent {
}
impl settings::Settings for TelemetrySettings {
const KEY: Option<&'static str> = Some("telemetry");
type FileContent = TelemetrySettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+9 -13
View File
@@ -1,7 +1,7 @@
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use workspace::dock::DockPosition;
#[derive(Deserialize, Debug)]
@@ -27,7 +27,8 @@ pub struct ChatPanelSettings {
pub default_width: Pixels,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "chat_panel")]
pub struct ChatPanelSettingsContent {
/// When to show the panel button in the status bar.
///
@@ -43,14 +44,16 @@ pub struct ChatPanelSettingsContent {
pub default_width: Option<f32>,
}
#[derive(Deserialize, Debug)]
#[derive(Deserialize, Debug, SettingsKey)]
#[settings_key(key = "notification_panel")]
pub struct NotificationPanelSettings {
pub button: bool,
pub dock: DockPosition,
pub default_width: Pixels,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "collaboration_panel")]
pub struct PanelSettingsContent {
/// Whether to show the panel button in the status bar.
///
@@ -66,7 +69,8 @@ pub struct PanelSettingsContent {
pub default_width: Option<f32>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "message_editor")]
pub struct MessageEditorSettings {
/// Whether to automatically replace emoji shortcodes with emoji characters.
/// For example: typing `:wave:` gets replaced with `👋`.
@@ -76,8 +80,6 @@ pub struct MessageEditorSettings {
}
impl Settings for CollaborationPanelSettings {
const KEY: Option<&'static str> = Some("collaboration_panel");
type FileContent = PanelSettingsContent;
fn load(
@@ -91,8 +93,6 @@ impl Settings for CollaborationPanelSettings {
}
impl Settings for ChatPanelSettings {
const KEY: Option<&'static str> = Some("chat_panel");
type FileContent = ChatPanelSettingsContent;
fn load(
@@ -106,8 +106,6 @@ impl Settings for ChatPanelSettings {
}
impl Settings for NotificationPanelSettings {
const KEY: Option<&'static str> = Some("notification_panel");
type FileContent = PanelSettingsContent;
fn load(
@@ -121,8 +119,6 @@ impl Settings for NotificationPanelSettings {
}
impl Settings for MessageEditorSettings {
const KEY: Option<&'static str> = Some("message_editor");
type FileContent = MessageEditorSettings;
fn load(
+4 -5
View File
@@ -2,7 +2,7 @@ use dap_types::SteppingGranularity;
use gpui::{App, Global};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, SettingsUi)]
#[serde(rename_all = "snake_case")]
@@ -12,11 +12,12 @@ pub enum DebugPanelDockPosition {
Right,
}
#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUi)]
#[derive(Serialize, Deserialize, JsonSchema, Clone, Copy, SettingsUi, SettingsKey)]
#[serde(default)]
// todo(settings_ui) @ben: I'm pretty sure not having the fields be optional here is a bug,
// it means the defaults will override previously set values if a single key is missing
#[settings_ui(group = "Debugger", path = "debugger")]
#[settings_ui(group = "Debugger")]
#[settings_key(key = "debugger")]
pub struct DebuggerSettings {
/// Determines the stepping granularity.
///
@@ -64,8 +65,6 @@ impl Default for DebuggerSettings {
}
impl Settings for DebuggerSettings {
const KEY: Option<&'static str> = Some("debugger");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
+3 -4
View File
@@ -6,7 +6,7 @@ use language::CursorShape;
use project::project_settings::DiagnosticSeverity;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi, VsCodeSettings};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi, VsCodeSettings};
use util::serde::default_true;
/// Imports from the VSCode settings at
@@ -431,8 +431,9 @@ pub enum SnippetSortOrder {
None,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_ui(group = "Editor")]
#[settings_key(None)]
pub struct EditorSettingsContent {
/// Whether the cursor blinks in the editor.
///
@@ -777,8 +778,6 @@ impl EditorSettings {
}
impl Settings for EditorSettings {
const KEY: Option<&'static str> = None;
type FileContent = EditorSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
@@ -3,10 +3,11 @@ use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use std::sync::Arc;
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi)]
#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct ExtensionSettings {
/// The extensions that should be automatically installed by Zed.
///
@@ -38,8 +39,6 @@ impl ExtensionSettings {
}
impl Settings for ExtensionSettings {
const KEY: Option<&'static str> = None;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _cx: &mut App) -> Result<Self> {
+1 -1
View File
@@ -1345,7 +1345,7 @@ impl ExtensionsPage {
this.update_settings::<VimModeSetting>(
selection,
cx,
|setting, value| *setting = Some(value),
|setting, value| setting.vim_mode = Some(value),
);
}),
)),
@@ -1,7 +1,7 @@
use anyhow::Result;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
pub struct FileFinderSettings {
@@ -11,7 +11,8 @@ pub struct FileFinderSettings {
pub include_ignored: Option<bool>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "file_finder")]
pub struct FileFinderSettingsContent {
/// Whether to show file icons in the file finder.
///
@@ -42,8 +43,6 @@ pub struct FileFinderSettingsContent {
}
impl Settings for FileFinderSettings {
const KEY: Option<&'static str> = Some("file_finder");
type FileContent = FileFinderSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::App) -> Result<Self> {
+3 -4
View File
@@ -5,7 +5,7 @@ use git::GitHostingProviderRegistry;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore, SettingsUi};
use settings::{Settings, SettingsKey, SettingsStore, SettingsUi};
use url::Url;
use util::ResultExt as _;
@@ -78,7 +78,8 @@ pub struct GitHostingProviderConfig {
pub name: String,
}
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Default, Debug, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct GitHostingProviderSettings {
/// The list of custom Git hosting providers.
#[serde(default)]
@@ -86,8 +87,6 @@ pub struct GitHostingProviderSettings {
}
impl Settings for GitHostingProviderSettings {
const KEY: Option<&'static str> = None;
type FileContent = Self;
fn load(sources: settings::SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+3 -4
View File
@@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use workspace::dock::DockPosition;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
@@ -36,7 +36,8 @@ pub enum StatusStyle {
LabelColor,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "git_panel")]
pub struct GitPanelSettingsContent {
/// Whether to show the panel button in the status bar.
///
@@ -90,8 +91,6 @@ pub struct GitPanelSettings {
}
impl Settings for GitPanelSettings {
const KEY: Option<&'static str> = Some("git_panel");
type FileContent = GitPanelSettingsContent;
fn load(
+3 -4
View File
@@ -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, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use std::{fmt::Write, num::NonZeroU32, time::Duration};
use text::{Point, Selection};
use ui::{
@@ -301,13 +301,12 @@ pub(crate) enum LineIndicatorFormat {
Long,
}
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi)]
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey)]
#[serde(transparent)]
#[settings_key(key = "line_indicator_format")]
pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
impl Settings for LineIndicatorFormat {
const KEY: Option<&'static str> = Some("line_indicator_format");
type FileContent = LineIndicatorFormatContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
@@ -1,10 +1,11 @@
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
/// The settings for the image viewer.
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, SettingsUi)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, SettingsUi, SettingsKey)]
#[settings_key(key = "image_viewer")]
pub struct ImageViewerSettings {
/// The unit to use for displaying image file sizes.
///
@@ -24,8 +25,6 @@ pub enum ImageFileSizeUnit {
}
impl Settings for ImageViewerSettings {
const KEY: Option<&'static str> = Some("image_viewer");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
+3 -4
View File
@@ -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, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use std::{
fs::OpenOptions,
path::{Path, PathBuf},
@@ -22,7 +22,8 @@ actions!(
);
/// Settings specific to journaling
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "journal")]
pub struct JournalSettings {
/// The path of the directory where journal entries are stored.
///
@@ -52,8 +53,6 @@ pub enum HourFormat {
}
impl settings::Settings for JournalSettings {
const KEY: Option<&'static str> = Some("journal");
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+6 -4
View File
@@ -17,7 +17,8 @@ use serde::{
};
use settings::{
ParameterizedJsonSchema, Settings, SettingsLocation, SettingsSources, SettingsStore, SettingsUi,
ParameterizedJsonSchema, Settings, SettingsKey, SettingsLocation, SettingsSources,
SettingsStore, SettingsUi,
};
use shellexpand;
use std::{borrow::Cow, num::NonZeroU32, path::Path, slice, sync::Arc};
@@ -292,7 +293,10 @@ pub struct CopilotSettings {
}
/// The settings for all languages.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(
Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
)]
#[settings_key(None)]
pub struct AllLanguageSettingsContent {
/// The settings for enabling/disabling features.
#[serde(default)]
@@ -1213,8 +1217,6 @@ impl InlayHintKind {
}
impl settings::Settings for AllLanguageSettings {
const KEY: Option<&'static str> = None;
type FileContent = AllLanguageSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+5 -4
View File
@@ -5,7 +5,7 @@ use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use crate::provider::{
self,
@@ -46,7 +46,10 @@ pub struct AllLanguageModelSettings {
pub zed_dot_dev: ZedDotDevSettings,
}
#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, SettingsUi)]
#[derive(
Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, SettingsUi, SettingsKey,
)]
#[settings_key(key = "language_models")]
pub struct AllLanguageModelSettingsContent {
pub anthropic: Option<AnthropicSettingsContent>,
pub bedrock: Option<AmazonBedrockSettingsContent>,
@@ -145,8 +148,6 @@ pub struct OpenRouterSettingsContent {
}
impl settings::Settings for AllLanguageModelSettings {
const KEY: Option<&'static str> = Some("language_models");
const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
type FileContent = AllLanguageModelSettingsContent;
+3 -7
View File
@@ -264,13 +264,9 @@ pub(crate) fn render_ai_setup_page(
);
let fs = <dyn Fs>::global(cx);
update_settings_file::<DisableAiSettings>(
fs,
cx,
move |ai_settings: &mut Option<bool>, _| {
*ai_settings = Some(enabled);
},
);
update_settings_file::<DisableAiSettings>(fs, cx, move |ai_settings, _| {
ai_settings.disable_ai = Some(enabled);
});
},
)
.tab_index({
+1 -1
View File
@@ -388,7 +388,7 @@ fn render_vim_mode_switch(tab_index: &mut isize, cx: &mut App) -> impl IntoEleme
}
};
update_settings_file::<VimModeSetting>(fs.clone(), cx, move |setting, _| {
*setting = Some(vim_mode);
setting.vim_mode = Some(vim_mode);
});
telemetry::event!(
@@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
@@ -61,7 +61,8 @@ pub struct IndentGuidesSettingsContent {
pub show: Option<ShowIndentGuides>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "outline_panel")]
pub struct OutlinePanelSettingsContent {
/// Whether to show the outline panel button in the status bar.
///
@@ -116,8 +117,6 @@ pub struct OutlinePanelSettingsContent {
}
impl Settings for OutlinePanelSettings {
const KEY: Option<&'static str> = Some("outline_panel");
type FileContent = OutlinePanelSettingsContent;
fn load(
+52 -27
View File
@@ -28,6 +28,7 @@ use context_server_store::ContextServerStore;
pub use environment::{EnvironmentErrorMessage, ProjectEnvironmentEvent};
use git::repository::get_git_committer;
use git_store::{Repository, RepositoryId};
use schemars::JsonSchema;
pub mod search_history;
mod yarn;
@@ -94,7 +95,10 @@ use rpc::{
};
use search::{SearchInputKind, SearchQuery, SearchResult};
use search_history::SearchHistory;
use settings::{InvalidSettingsError, Settings, SettingsLocation, SettingsSources, SettingsStore};
use settings::{
InvalidSettingsError, Settings, SettingsKey, SettingsLocation, SettingsSources, SettingsStore,
SettingsUi,
};
use smol::channel::Receiver;
use snippet::Snippet;
use snippet_provider::SnippetProvider;
@@ -968,10 +972,26 @@ pub struct DisableAiSettings {
pub disable_ai: bool,
}
impl settings::Settings for DisableAiSettings {
const KEY: Option<&'static str> = Some("disable_ai");
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Debug,
Default,
serde::Serialize,
serde::Deserialize,
SettingsUi,
SettingsKey,
JsonSchema,
)]
#[settings_key(None)]
pub struct DisableAiSettingContent {
pub disable_ai: Option<bool>,
}
type FileContent = Option<bool>;
impl settings::Settings for DisableAiSettings {
type FileContent = DisableAiSettingContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
// For security reasons, settings can only make AI restrictions MORE strict, not less.
@@ -984,7 +1004,7 @@ impl settings::Settings for DisableAiSettings {
.iter()
.chain(sources.user.iter())
.chain(sources.server.iter())
.any(|disabled| **disabled == Some(true));
.any(|disabled| disabled.disable_ai == Some(true));
Ok(Self { disable_ai })
}
@@ -5550,10 +5570,15 @@ mod disable_ai_settings_tests {
#[gpui::test]
async fn test_disable_ai_settings_security(cx: &mut TestAppContext) {
fn disable_setting(value: Option<bool>) -> DisableAiSettingContent {
DisableAiSettingContent { disable_ai: value }
}
cx.update(|cx| {
// Test 1: Default is false (AI enabled)
let sources = SettingsSources {
default: &Some(false),
default: &DisableAiSettingContent {
disable_ai: Some(false),
},
global: None,
extensions: None,
user: None,
@@ -5567,10 +5592,10 @@ mod disable_ai_settings_tests {
assert!(!settings.disable_ai, "Default should allow AI");
// Test 2: Global true, local false -> still disabled (local cannot re-enable)
let global_true = Some(true);
let local_false = Some(false);
let global_true = disable_setting(Some(true));
let local_false = disable_setting(Some(false));
let sources = SettingsSources {
default: &Some(false),
default: &disable_setting(Some(false)),
global: None,
extensions: None,
user: Some(&global_true),
@@ -5587,10 +5612,10 @@ mod disable_ai_settings_tests {
);
// Test 3: Global false, local true -> disabled (local can make more restrictive)
let global_false = Some(false);
let local_true = Some(true);
let global_false = disable_setting(Some(false));
let local_true = disable_setting(Some(true));
let sources = SettingsSources {
default: &Some(false),
default: &disable_setting(Some(false)),
global: None,
extensions: None,
user: Some(&global_false),
@@ -5604,10 +5629,10 @@ mod disable_ai_settings_tests {
assert!(settings.disable_ai, "Local true can override global false");
// Test 4: Server can only make more restrictive (set to true)
let user_false = Some(false);
let server_true = Some(true);
let user_false = disable_setting(Some(false));
let server_true = disable_setting(Some(true));
let sources = SettingsSources {
default: &Some(false),
default: &disable_setting(Some(false)),
global: None,
extensions: None,
user: Some(&user_false),
@@ -5624,10 +5649,10 @@ mod disable_ai_settings_tests {
);
// Test 5: Server false cannot override user true
let user_true = Some(true);
let server_false = Some(false);
let user_true = disable_setting(Some(true));
let server_false = disable_setting(Some(false));
let sources = SettingsSources {
default: &Some(false),
default: &disable_setting(Some(false)),
global: None,
extensions: None,
user: Some(&user_true),
@@ -5644,12 +5669,12 @@ mod disable_ai_settings_tests {
);
// Test 6: Multiple local settings, any true disables AI
let global_false = Some(false);
let local_false3 = Some(false);
let local_true2 = Some(true);
let local_false4 = Some(false);
let global_false = disable_setting(Some(false));
let local_false3 = disable_setting(Some(false));
let local_true2 = disable_setting(Some(true));
let local_false4 = disable_setting(Some(false));
let sources = SettingsSources {
default: &Some(false),
default: &disable_setting(Some(false)),
global: None,
extensions: None,
user: Some(&global_false),
@@ -5663,11 +5688,11 @@ mod disable_ai_settings_tests {
assert!(settings.disable_ai, "Any local true should disable AI");
// Test 7: All three sources can independently disable AI
let user_false2 = Some(false);
let server_false2 = Some(false);
let local_true3 = Some(true);
let user_false2 = disable_setting(Some(false));
let server_false2 = disable_setting(Some(false));
let local_true3 = disable_setting(Some(true));
let sources = SettingsSources {
default: &Some(false),
default: &disable_setting(Some(false)),
global: None,
extensions: None,
user: Some(&user_false2),
+4 -5
View File
@@ -18,8 +18,8 @@ use rpc::{
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
SettingsStore, SettingsUi, parse_json_with_comments, watch_config_file,
InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation,
SettingsSources, SettingsStore, SettingsUi, parse_json_with_comments, watch_config_file,
};
use std::{
collections::BTreeMap,
@@ -36,7 +36,8 @@ use crate::{
worktree_store::{WorktreeStore, WorktreeStoreEvent},
};
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct ProjectSettings {
/// Configuration for language servers.
///
@@ -568,8 +569,6 @@ impl Default for SessionSettings {
}
impl Settings for ProjectSettings {
const KEY: Option<&'static str> = None;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
@@ -2,7 +2,7 @@ use editor::ShowScrollbar;
use gpui::Pixels;
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)]
#[serde(rename_all = "snake_case")]
@@ -92,7 +92,8 @@ pub enum ShowDiagnostics {
All,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "project_panel")]
pub struct ProjectPanelSettingsContent {
/// Whether to show the project panel button in the status bar.
///
@@ -168,8 +169,6 @@ pub struct ProjectPanelSettingsContent {
}
impl Settings for ProjectPanelSettings {
const KEY: Option<&'static str> = Some("project_panel");
type FileContent = ProjectPanelSettingsContent;
fn load(
@@ -21,7 +21,7 @@ use remote::{
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use theme::ThemeSettings;
use ui::{
ActiveTheme, Color, CommonAnimationExt, Context, Icon, IconName, IconSize, InteractiveElement,
@@ -121,15 +121,14 @@ pub struct SshProject {
pub paths: Vec<String>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct RemoteSettingsContent {
pub ssh_connections: Option<Vec<SshConnection>>,
pub read_ssh_config: Option<bool>,
}
impl Settings for SshSettings {
const KEY: Option<&'static str> = None;
type FileContent = RemoteSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+3 -4
View File
@@ -4,7 +4,7 @@ use editor::EditorSettings;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Debug, Default)]
pub struct JupyterSettings {
@@ -20,7 +20,8 @@ impl JupyterSettings {
}
}
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[derive(Clone, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey)]
#[settings_key(key = "jupyter")]
pub struct JupyterSettingsContent {
/// Default kernels to select for each language.
///
@@ -37,8 +38,6 @@ impl Default for JupyterSettingsContent {
}
impl Settings for JupyterSettings {
const KEY: Option<&'static str> = Some("jupyter");
type FileContent = JupyterSettingsContent;
fn load(
+14 -5
View File
@@ -1,10 +1,10 @@
use std::fmt::{Display, Formatter};
use crate as settings;
use crate::{self as settings};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, VsCodeSettings};
use settings_ui_macros::SettingsUi;
use settings_ui_macros::{SettingsKey, SettingsUi};
/// Base key bindings scheme. Base keymaps can be overridden with user keymaps.
///
@@ -101,16 +101,25 @@ impl BaseKeymap {
}
#[derive(
Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default, SettingsUi,
Copy,
Clone,
Debug,
Serialize,
Deserialize,
JsonSchema,
PartialEq,
Eq,
Default,
SettingsUi,
SettingsKey,
)]
// extracted so that it can be an option, and still work with derive(SettingsUi)
#[settings_key(None)]
pub struct BaseKeymapSetting {
pub base_keymap: Option<BaseKeymap>,
}
impl Settings for BaseKeymap {
const KEY: Option<&'static str> = None;
type FileContent = BaseKeymapSetting;
fn load(
+3 -3
View File
@@ -21,12 +21,12 @@ pub use keymap_file::{
pub use settings_file::*;
pub use settings_json::*;
pub use settings_store::{
InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, SettingsSources,
SettingsStore,
InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation,
SettingsSources, SettingsStore,
};
pub use settings_ui_core::*;
// Re-export the derive macro
pub use settings_ui_macros::SettingsUi;
pub use settings_ui_macros::{SettingsKey, SettingsUi};
pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource};
#[derive(Clone, Debug, PartialEq)]
+70 -25
View File
@@ -36,17 +36,19 @@ use crate::{
settings_ui_core::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 SettingsKey: '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.
const KEY: Option<&'static str>;
const FALLBACK_KEY: Option<&'static str> = None;
}
/// 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 {
/// The name of the keys in the [`FileContent`](Self::FileContent) that should
/// always be written to a settings file, even if their value matches the default
/// value.
@@ -57,8 +59,19 @@ pub trait Settings: 'static + Send + Sync {
const PRESERVED_KEYS: Option<&'static [&'static str]> = None;
/// The type that is stored in an individual JSON file.
type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema + SettingsUi;
type FileContent: Clone
+ Default
+ Serialize
+ DeserializeOwned
+ JsonSchema
+ SettingsUi
+ SettingsKey;
/*
* let path = Settings
*
*
*/
/// The logic for combining together values from one or more JSON files into the
/// final value for this setting.
///
@@ -71,7 +84,7 @@ pub trait Settings: 'static + Send + Sync {
Self: Sized;
fn missing_default() -> anyhow::Error {
anyhow::anyhow!("missing default")
anyhow::anyhow!("missing default for: {}", std::any::type_name::<Self>())
}
/// Use [the helpers in the vscode_import module](crate::vscode_import) to apply known
@@ -1393,7 +1406,7 @@ impl Debug for SettingsStore {
impl<T: Settings> AnySettingValue for SettingValue<T> {
fn key(&self) -> Option<&'static str> {
T::KEY
T::FileContent::KEY
}
fn setting_type_name(&self) -> &'static str {
@@ -1445,16 +1458,21 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
mut json: &Value,
) -> (Option<&'static str>, Result<DeserializedSetting>) {
let mut key = None;
if let Some(k) = T::KEY {
if let Some(k) = T::FileContent::KEY {
if let Some(value) = json.get(k) {
json = value;
key = Some(k);
} else if let Some((k, value)) = T::FALLBACK_KEY.and_then(|k| Some((k, json.get(k)?))) {
} else if let Some((k, value)) =
T::FileContent::FALLBACK_KEY.and_then(|k| Some((k, json.get(k)?)))
{
json = value;
key = Some(k);
} else {
let value = T::FileContent::default();
return (T::KEY, Ok(DeserializedSetting(Box::new(value))));
return (
T::FileContent::KEY,
Ok(DeserializedSetting(Box::new(value))),
);
}
}
let value = serde_path_to_error::deserialize::<_, T::FileContent>(json)
@@ -1498,6 +1516,7 @@ impl<T: Settings> AnySettingValue for SettingValue<T> {
}
}
}
self.global_value
.as_ref()
.unwrap_or_else(|| panic!("no default value for setting {}", self.setting_type_name()))
@@ -1570,7 +1589,7 @@ mod tests {
// This is so the SettingsUi macro can still work properly
use crate as settings;
use serde_derive::Deserialize;
use settings_ui_macros::SettingsUi;
use settings_ui_macros::{SettingsKey, SettingsUi};
use unindent::Unindent;
#[gpui::test]
@@ -2120,7 +2139,8 @@ mod tests {
staff: bool,
}
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "user")]
struct UserSettingsContent {
name: Option<String>,
age: Option<u32>,
@@ -2128,7 +2148,6 @@ mod tests {
}
impl Settings for UserSettings {
const KEY: Option<&'static str> = Some("user");
type FileContent = UserSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -2143,12 +2162,37 @@ mod tests {
#[derive(Debug, Deserialize, PartialEq)]
struct TurboSetting(bool);
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Debug,
Default,
serde::Serialize,
serde::Deserialize,
SettingsUi,
SettingsKey,
JsonSchema,
)]
#[serde(default)]
#[settings_key(None)]
pub struct TurboSettingContent {
turbo: Option<bool>,
}
impl Settings for TurboSetting {
const KEY: Option<&'static str> = Some("turbo");
type FileContent = bool;
type FileContent = TurboSettingContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
sources.json_merge()
Ok(Self(
sources
.user
.or(sources.server)
.unwrap_or(sources.default)
.turbo
.unwrap_or_default(),
))
}
fn import_from_vscode(_vscode: &VsCodeSettings, _current: &mut Self::FileContent) {}
@@ -2162,15 +2206,14 @@ mod tests {
key2: String,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
struct MultiKeySettingsJson {
key1: Option<String>,
key2: Option<String>,
}
impl Settings for MultiKeySettings {
const KEY: Option<&'static str> = None;
type FileContent = MultiKeySettingsJson;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -2200,15 +2243,16 @@ mod tests {
Hour24,
}
#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(
Clone, Default, Debug, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
)]
#[settings_key(key = "journal")]
struct JournalSettingsJson {
pub path: Option<String>,
pub hour_format: Option<HourFormat>,
}
impl Settings for JournalSettings {
const KEY: Option<&'static str> = Some("journal");
type FileContent = JournalSettingsJson;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -2288,7 +2332,10 @@ mod tests {
);
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(
Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey,
)]
#[settings_key(None)]
struct LanguageSettings {
#[serde(default)]
languages: HashMap<String, LanguageSettingEntry>,
@@ -2301,8 +2348,6 @@ mod tests {
}
impl Settings for LanguageSettings {
const KEY: Option<&'static str> = None;
type FileContent = Self;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -43,10 +43,9 @@ pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenSt
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
// todo(settings_ui) rely entirely on settings_key, remove path attribute
if path_name.is_some() {
return Err(meta.error("Only one 'path' can be specified"));
return Err(meta.error("Only one 'path' can be specified, either with `path` in `settings_ui` or with `settings_key`"));
}
meta.input.parse::<Token![=]>()?;
let lit: LitStr = meta.input.parse()?;
@@ -55,6 +54,12 @@ pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenSt
Ok(())
})
.unwrap_or_else(|e| panic!("in #[settings_ui] attribute: {}", e));
} else if let Some(settings_key) = parse_setting_key_attr(attr) {
// todo(settings_ui) either remove fallback key or handle it here
if path_name.is_some() && settings_key.key.is_some() {
panic!("Both 'path' and 'settings_key' are specified. Must specify only one");
}
path_name = settings_key.key;
}
}
@@ -212,3 +217,126 @@ fn generate_ui_item_body(
},
}
}
struct SettingsKey {
key: Option<String>,
fallback_key: Option<String>,
}
fn parse_setting_key_attr(attr: &syn::Attribute) -> Option<SettingsKey> {
if !attr.path().is_ident("settings_key") {
return None;
}
let mut settings_key = SettingsKey {
key: None,
fallback_key: None,
};
let mut found_none = false;
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("None") {
found_none = true;
} else if meta.path.is_ident("key") {
if settings_key.key.is_some() {
return Err(meta.error("Only one 'group' path can be specified"));
}
meta.input.parse::<Token![=]>()?;
let lit: LitStr = meta.input.parse()?;
settings_key.key = Some(lit.value());
} else if meta.path.is_ident("fallback_key") {
if found_none {
return Err(meta.error("Cannot specify 'fallback_key' and 'None'"));
}
if settings_key.fallback_key.is_some() {
return Err(meta.error("Only one 'fallback_key' can be specified"));
}
meta.input.parse::<Token![=]>()?;
let lit: LitStr = meta.input.parse()?;
settings_key.fallback_key = Some(lit.value());
}
Ok(())
})
.unwrap_or_else(|e| panic!("in #[settings_key] attribute: {}", e));
if found_none && settings_key.fallback_key.is_some() {
panic!("in #[settings_key] attribute: Cannot specify 'None' and 'fallback_key'");
}
if found_none && settings_key.key.is_some() {
panic!("in #[settings_key] attribute: Cannot specify 'None' and 'key'");
}
if !found_none && settings_key.key.is_none() {
panic!("in #[settings_key] attribute: 'key' must be specified");
}
return Some(settings_key);
}
#[proc_macro_derive(SettingsKey, attributes(settings_key))]
pub fn derive_settings_key(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 settings_key = Option::<SettingsKey>::None;
for attr in &input.attrs {
let parsed_settings_key = parse_setting_key_attr(attr);
if parsed_settings_key.is_some() && settings_key.is_some() {
panic!("Duplicate #[settings_key] attribute");
}
settings_key = parsed_settings_key;
}
let Some(SettingsKey { key, fallback_key }) = settings_key else {
panic!("Missing #[settings_key] attribute");
};
let key = key.map_or_else(|| quote! {None}, |key| quote! {Some(#key)});
let fallback_key = fallback_key.map_or_else(
|| quote! {None},
|fallback_key| quote! {Some(#fallback_key)},
);
let expanded = quote! {
impl #impl_generics settings::SettingsKey for #name #ty_generics #where_clause {
const KEY: Option<&'static str> = #key;
const FALLBACK_KEY: Option<&'static str> = #fallback_key;
};
};
proc_macro::TokenStream::from(expanded)
}
#[cfg(test)]
mod tests {
use syn::{Attribute, parse_quote};
use super::*;
#[test]
fn test_extract_key() {
let input: Attribute = parse_quote!(
#[settings_key(key = "my_key")]
);
let settings_key = parse_setting_key_attr(&input).unwrap();
assert_eq!(settings_key.key, Some("my_key".to_string()));
assert_eq!(settings_key.fallback_key, None);
}
#[test]
fn test_empty_key() {
let input: Attribute = parse_quote!(
#[settings_key(None)]
);
let settings_key = parse_setting_key_attr(&input).unwrap();
assert_eq!(settings_key.key, None);
assert_eq!(settings_key.fallback_key, None);
}
}
+3 -4
View File
@@ -6,7 +6,7 @@ use gpui::{AbsoluteLength, App, FontFallbacks, FontFeatures, FontWeight, Pixels,
use schemars::JsonSchema;
use serde_derive::{Deserialize, Serialize};
use settings::{SettingsSources, SettingsUi};
use settings::{SettingsKey, SettingsSources, SettingsUi};
use std::path::PathBuf;
use task::Shell;
use theme::FontFamilyName;
@@ -135,7 +135,8 @@ pub enum ActivateScript {
Pyenv,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "terminal")]
pub struct TerminalSettingsContent {
/// What shell to use when opening a terminal.
///
@@ -253,8 +254,6 @@ pub struct TerminalSettingsContent {
}
impl settings::Settings for TerminalSettings {
const KEY: Option<&'static str> = Some("terminal");
type FileContent = TerminalSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
+3 -4
View File
@@ -13,7 +13,7 @@ use gpui::{
use refineable::Refineable;
use schemars::{JsonSchema, json_schema};
use serde::{Deserialize, Serialize};
use settings::{ParameterizedJsonSchema, Settings, SettingsSources, SettingsUi};
use settings::{ParameterizedJsonSchema, Settings, SettingsKey, SettingsSources, SettingsUi};
use std::sync::Arc;
use util::ResultExt as _;
use util::schemars::replace_subschema;
@@ -366,7 +366,8 @@ impl IconThemeSelection {
}
/// Settings for rendering text in UI and text buffers.
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct ThemeSettingsContent {
/// The default font size for text in the UI.
#[serde(default)]
@@ -818,8 +819,6 @@ fn clamp_font_weight(weight: f32) -> FontWeight {
}
impl settings::Settings for ThemeSettings {
const KEY: Option<&'static str> = None;
type FileContent = ThemeSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
+6 -5
View File
@@ -1,7 +1,7 @@
use db::anyhow;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Copy, Clone, Deserialize, Debug)]
pub struct TitleBarSettings {
@@ -14,8 +14,11 @@ pub struct TitleBarSettings {
pub show_menus: bool,
}
#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi)]
#[settings_ui(group = "Title Bar", path = "title_bar")]
#[derive(
Copy, Clone, Default, Serialize, Deserialize, JsonSchema, Debug, SettingsUi, SettingsKey,
)]
#[settings_ui(group = "Title Bar")]
#[settings_key(key = "title_bar")]
pub struct TitleBarSettingsContent {
/// Whether to show the branch icon beside branch switcher in the title bar.
///
@@ -48,8 +51,6 @@ pub struct TitleBarSettingsContent {
}
impl Settings for TitleBarSettings {
const KEY: Option<&'static str> = Some("title_bar");
type FileContent = TitleBarSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut gpui::App) -> anyhow::Result<Self>
+4 -4
View File
@@ -68,7 +68,7 @@ impl VimTestContext {
pub fn init_keybindings(enabled: bool, cx: &mut App) {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(enabled));
store.update_user_settings::<VimModeSetting>(cx, |s| s.vim_mode = Some(enabled));
});
let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
"keymaps/default-macos.json",
@@ -134,7 +134,7 @@ impl VimTestContext {
pub fn enable_vim(&mut self) {
self.cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(true));
store.update_user_settings::<VimModeSetting>(cx, |s| s.vim_mode = Some(true));
});
})
}
@@ -142,7 +142,7 @@ impl VimTestContext {
pub fn disable_vim(&mut self) {
self.cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<VimModeSetting>(cx, |s| *s = Some(false));
store.update_user_settings::<VimModeSetting>(cx, |s| s.vim_mode = Some(false));
});
})
}
@@ -151,7 +151,7 @@ impl VimTestContext {
self.cx.update(|_, cx| {
SettingsStore::update_global(cx, |store, cx| {
store.update_user_settings::<vim_mode_setting::HelixModeSetting>(cx, |s| {
*s = Some(true)
s.helix_mode = Some(true)
});
});
})
+6 -5
View File
@@ -39,7 +39,9 @@ use object::Object;
use schemars::JsonSchema;
use serde::Deserialize;
use serde_derive::Serialize;
use settings::{Settings, SettingsSources, SettingsStore, SettingsUi, update_settings_file};
use settings::{
Settings, SettingsKey, SettingsSources, SettingsStore, SettingsUi, update_settings_file,
};
use state::{Mode, Operator, RecordedSelection, SearchState, VimGlobals};
use std::{mem, ops::Range, sync::Arc};
use surrounds::SurroundsType;
@@ -247,7 +249,7 @@ pub fn init(cx: &mut App) {
let fs = workspace.app_state().fs.clone();
let currently_enabled = Vim::enabled(cx);
update_settings_file::<VimModeSetting>(fs, cx, move |setting, _| {
*setting = Some(!currently_enabled)
setting.vim_mode = Some(!currently_enabled)
})
});
@@ -1785,7 +1787,8 @@ struct VimSettings {
pub cursor_shape: CursorShapeSettings,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "vim")]
struct VimSettingsContent {
pub default_mode: Option<ModeContent>,
pub toggle_relative_line_numbers: Option<bool>,
@@ -1824,8 +1827,6 @@ impl From<ModeContent> for Mode {
}
impl Settings for VimSettings {
const KEY: Option<&'static str> = Some("vim");
type FileContent = VimSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+2
View File
@@ -14,5 +14,7 @@ path = "src/vim_mode_setting.rs"
[dependencies]
anyhow.workspace = true
gpui.workspace = true
schemars.workspace = true
serde.workspace = true
settings.workspace = true
workspace-hack.workspace = true
+55 -23
View File
@@ -6,7 +6,8 @@
use anyhow::Result;
use gpui::App;
use settings::{Settings, SettingsSources, SettingsUi};
use schemars::JsonSchema;
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
/// Initializes the `vim_mode_setting` crate.
pub fn init(cx: &mut App) {
@@ -14,25 +15,40 @@ pub fn init(cx: &mut App) {
HelixModeSetting::register(cx);
}
/// Whether or not to enable Vim mode.
///
/// Default: false
#[derive(SettingsUi)]
pub struct VimModeSetting(pub bool);
impl Settings for VimModeSetting {
const KEY: Option<&'static str> = Some("vim_mode");
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Debug,
Default,
serde::Serialize,
serde::Deserialize,
SettingsUi,
SettingsKey,
JsonSchema,
)]
#[settings_key(None)]
pub struct VimModeSettingContent {
/// Whether or not to enable Vim mode.
///
/// Default: false
pub vim_mode: Option<bool>,
}
type FileContent = Option<bool>;
impl Settings for VimModeSetting {
type FileContent = VimModeSettingContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self(
sources
.user
.or(sources.server)
.copied()
.flatten()
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
.and_then(|mode| mode.vim_mode)
.or(sources.server.and_then(|mode| mode.vim_mode))
.or(sources.default.vim_mode)
.ok_or_else(Self::missing_default)?,
))
}
@@ -41,25 +57,41 @@ impl Settings for VimModeSetting {
}
}
/// Whether or not to enable Helix mode.
///
/// Default: false
#[derive(SettingsUi)]
#[derive(Debug)]
pub struct HelixModeSetting(pub bool);
impl Settings for HelixModeSetting {
const KEY: Option<&'static str> = Some("helix_mode");
#[derive(
Copy,
Clone,
PartialEq,
Eq,
Debug,
Default,
serde::Serialize,
serde::Deserialize,
SettingsUi,
SettingsKey,
JsonSchema,
)]
#[settings_key(None)]
pub struct HelixModeSettingContent {
/// Whether or not to enable Helix mode.
///
/// Default: false
pub helix_mode: Option<bool>,
}
type FileContent = Option<bool>;
impl Settings for HelixModeSetting {
type FileContent = HelixModeSettingContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
Ok(Self(
sources
.user
.or(sources.server)
.copied()
.flatten()
.unwrap_or(sources.default.ok_or_else(Self::missing_default)?),
.and_then(|mode| mode.helix_mode)
.or(sources.server.and_then(|mode| mode.helix_mode))
.or(sources.default.helix_mode)
.ok_or_else(Self::missing_default)?,
))
}
+5 -7
View File
@@ -17,7 +17,7 @@ use gpui::{
use project::{Project, ProjectEntryId, ProjectPath};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsLocation, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsLocation, SettingsSources, SettingsUi};
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
@@ -101,7 +101,8 @@ pub enum ActivateOnClose {
LeftNeighbour,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "tabs")]
pub struct ItemSettingsContent {
/// Whether to show the Git file status on a tab item.
///
@@ -130,7 +131,8 @@ pub struct ItemSettingsContent {
show_close_button: Option<ShowCloseButton>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "preview_tabs")]
pub struct PreviewTabsSettingsContent {
/// Whether to show opened editors as preview tabs.
/// Preview tabs do not stay open, are reused until explicitly set to be kept open opened (via double-click or editing) and show file names in italic.
@@ -148,8 +150,6 @@ pub struct PreviewTabsSettingsContent {
}
impl Settings for ItemSettings {
const KEY: Option<&'static str> = Some("tabs");
type FileContent = ItemSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -187,8 +187,6 @@ impl Settings for ItemSettings {
}
impl Settings for PreviewTabsSettings {
const KEY: Option<&'static str> = Some("preview_tabs");
type FileContent = PreviewTabsSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+5 -7
View File
@@ -6,7 +6,7 @@ use collections::HashMap;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
#[derive(Deserialize)]
pub struct WorkspaceSettings {
@@ -118,7 +118,8 @@ pub enum RestoreOnStartupBehavior {
LastSession,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct WorkspaceSettingsContent {
/// Active pane styling settings.
pub active_pane_modifiers: Option<ActivePanelModifiers>,
@@ -223,7 +224,8 @@ pub struct TabBarSettings {
pub show_tab_bar_buttons: bool,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(key = "tab_bar")]
pub struct TabBarSettingsContent {
/// Whether or not to show the tab bar in the editor.
///
@@ -282,8 +284,6 @@ pub struct CenteredLayoutSettings {
}
impl Settings for WorkspaceSettings {
const KEY: Option<&'static str> = None;
type FileContent = WorkspaceSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
@@ -373,8 +373,6 @@ impl Settings for WorkspaceSettings {
}
impl Settings for TabBarSettings {
const KEY: Option<&'static str> = Some("tab_bar");
type FileContent = TabBarSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
+3 -4
View File
@@ -4,7 +4,7 @@ use anyhow::Context as _;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources, SettingsUi};
use settings::{Settings, SettingsKey, SettingsSources, SettingsUi};
use util::paths::PathMatcher;
#[derive(Clone, PartialEq, Eq)]
@@ -31,7 +31,8 @@ impl WorktreeSettings {
}
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi)]
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)]
#[settings_key(None)]
pub struct WorktreeSettingsContent {
/// Completely ignore files matching globs from `file_scan_exclusions`. Overrides
/// `file_scan_inclusions`.
@@ -65,8 +66,6 @@ pub struct WorktreeSettingsContent {
}
impl Settings for WorktreeSettings {
const KEY: Option<&'static str> = None;
type FileContent = WorktreeSettingsContent;
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
+14 -4
View File
@@ -3,7 +3,7 @@ use anyhow::Result;
use gpui::App;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsStore, SettingsUi};
use settings::{Settings, SettingsKey, SettingsStore, SettingsUi};
pub fn init(cx: &mut App) {
ZlogSettings::register(cx);
@@ -15,15 +15,25 @@ pub fn init(cx: &mut App) {
.detach();
}
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, SettingsUi)]
#[derive(
Clone,
Debug,
Default,
Serialize,
Deserialize,
PartialEq,
Eq,
JsonSchema,
SettingsUi,
SettingsKey,
)]
#[settings_key(key = "log")]
pub struct ZlogSettings {
#[serde(default, flatten)]
pub scopes: std::collections::HashMap<String, String>,
}
impl Settings for ZlogSettings {
const KEY: Option<&'static str> = Some("log");
type FileContent = Self;
fn load(sources: settings::SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self>