diff --git a/Cargo.lock b/Cargo.lock index 28247c7f4c6bcab64a8acce951e072e80abf751b..5654d206701f5efd5d91cd33c9ab456701b1e667 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3144,7 +3144,6 @@ dependencies = [ "release_channel", "rpc", "rustls-pki-types", - "schemars 1.0.1", "serde", "serde_json", "serde_urlencoded", @@ -9302,7 +9301,6 @@ dependencies = [ "serde", "settings", "shellexpand 2.1.2", - "util", "workspace", "workspace-hack", ] @@ -9523,7 +9521,6 @@ dependencies = [ "http_client", "imara-diff", "indoc", - "inventory", "itertools 0.14.0", "log", "lsp", @@ -15499,7 +15496,7 @@ dependencies = [ "serde_path_to_error", "serde_repr", "serde_with", - "settings_ui_macros", + "settings_macros", "smallvec", "tree-sitter", "tree-sitter-json", @@ -15509,6 +15506,16 @@ dependencies = [ "zlog", ] +[[package]] +name = "settings_macros" +version = "0.1.0" +dependencies = [ + "quote", + "settings", + "syn 2.0.101", + "workspace-hack", +] + [[package]] name = "settings_profile_selector" version = "0.1.0" @@ -15530,18 +15537,6 @@ dependencies = [ "zed_actions", ] -[[package]] -name = "settings_ui_macros" -version = "0.1.0" -dependencies = [ - "heck 0.5.0", - "proc-macro2", - "quote", - "settings", - "syn 2.0.101", - "workspace-hack", -] - [[package]] name = "sha1" version = "0.10.6" @@ -17190,7 +17185,6 @@ dependencies = [ "futures 0.3.31", "gpui", "indexmap 2.9.0", - "inventory", "log", "palette", "parking_lot", diff --git a/Cargo.toml b/Cargo.toml index 8a67b28b0e4a7b6e146f4c6a4b84c73d229ceb27..6939fb4dd60cd443e07d16988f187d7074535de7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -150,8 +150,8 @@ members = [ "crates/semantic_version", "crates/session", "crates/settings", + "crates/settings_macros", "crates/settings_profile_selector", - "crates/settings_ui_macros", "crates/snippet", "crates/snippet_provider", "crates/snippets_ui", @@ -383,7 +383,6 @@ semantic_version = { path = "crates/semantic_version" } session = { path = "crates/session" } settings = { path = "crates/settings" } settings_ui = { path = "crates/settings_ui" } -settings_ui_macros = { path = "crates/settings_ui_macros" } snippet = { path = "crates/snippet" } snippet_provider = { path = "crates/snippet_provider" } snippets_ui = { path = "crates/snippets_ui" } diff --git a/crates/agent2/src/tools/grep_tool.rs b/crates/agent2/src/tools/grep_tool.rs index a1cd088c858683429103670c604ed3e08d179483..7a2fa4db4009d94644455710429a44d51fdf20dc 100644 --- a/crates/agent2/src/tools/grep_tool.rs +++ b/crates/agent2/src/tools/grep_tool.rs @@ -834,11 +834,14 @@ mod tests { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -1064,7 +1067,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/agent2/src/tools/list_directory_tool.rs b/crates/agent2/src/tools/list_directory_tool.rs index 41198269d69c17e028cca250069c2e1000ac8dfe..fe7e2a4d85d115d7b9b87be9a2b90f3f7bd19028 100644 --- a/crates/agent2/src/tools/list_directory_tool.rs +++ b/crates/agent2/src/tools/list_directory_tool.rs @@ -427,11 +427,14 @@ mod tests { "**/.mymetadata".to_string(), "**/.hidden_subdir".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -568,7 +571,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index 3a9ddb88fe1a44677e43248184902c12724f6936..6fa157630d487c517a536126d6b8dd4d4d53a4e1 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -593,11 +593,14 @@ mod test { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -804,7 +807,8 @@ mod test { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/agent_settings/src/agent_settings.rs b/crates/agent_settings/src/agent_settings.rs index 176e8b1a1aa85214f79be1b8ec2696ab387434c1..e416ce73e5451e840af8c36e8ffee301bacc79b3 100644 --- a/crates/agent_settings/src/agent_settings.rs +++ b/crates/agent_settings/src/agent_settings.rs @@ -11,7 +11,6 @@ use settings::{ DefaultAgentView, DockPosition, LanguageModelParameters, LanguageModelSelection, NotifyWhenAgentWaiting, Settings, SettingsContent, }; -use util::MergeFrom; pub use crate::agent_profile::*; @@ -147,7 +146,7 @@ impl Default for AgentProfileId { } impl Settings for AgentSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let agent = content.agent.clone().unwrap(); Self { enabled: agent.enabled.unwrap(), @@ -183,66 +182,6 @@ impl Settings for AgentSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - let Some(value) = &content.agent else { return }; - self.enabled.merge_from(&value.enabled); - self.button.merge_from(&value.button); - self.dock.merge_from(&value.dock); - self.default_width - .merge_from(&value.default_width.map(Into::into)); - self.default_height - .merge_from(&value.default_height.map(Into::into)); - self.default_model = value.default_model.clone().or(self.default_model.take()); - - self.inline_assistant_model = value - .inline_assistant_model - .clone() - .or(self.inline_assistant_model.take()); - self.commit_message_model = value - .clone() - .commit_message_model - .or(self.commit_message_model.take()); - self.thread_summary_model = value - .clone() - .thread_summary_model - .or(self.thread_summary_model.take()); - self.inline_alternatives - .merge_from(&value.inline_alternatives.clone()); - self.default_profile - .merge_from(&value.default_profile.clone().map(AgentProfileId)); - self.default_view.merge_from(&value.default_view); - self.always_allow_tool_actions - .merge_from(&value.always_allow_tool_actions); - self.notify_when_agent_waiting - .merge_from(&value.notify_when_agent_waiting); - self.play_sound_when_agent_done - .merge_from(&value.play_sound_when_agent_done); - self.stream_edits.merge_from(&value.stream_edits); - self.single_file_review - .merge_from(&value.single_file_review); - self.preferred_completion_mode - .merge_from(&value.preferred_completion_mode.map(Into::into)); - self.enable_feedback.merge_from(&value.enable_feedback); - self.expand_edit_card.merge_from(&value.expand_edit_card); - self.expand_terminal_card - .merge_from(&value.expand_terminal_card); - self.use_modifier_to_send - .merge_from(&value.use_modifier_to_send); - - self.model_parameters - .extend_from_slice(&value.model_parameters); - self.message_editor_min_lines - .merge_from(&value.message_editor_min_lines); - - if let Some(profiles) = value.profiles.as_ref() { - self.profiles.extend( - profiles - .into_iter() - .map(|(id, profile)| (AgentProfileId(id.clone()), profile.clone().into())), - ); - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(b) = vscode .read_value("chat.agent.enabled") diff --git a/crates/agent_ui/src/agent_panel.rs b/crates/agent_ui/src/agent_panel.rs index 976dbb411990b4eb647bdec9af83f97c3a9bda84..ee9ac73b2ee23a8b4326ddbc4e60c345ef4a3526 100644 --- a/crates/agent_ui/src/agent_panel.rs +++ b/crates/agent_ui/src/agent_panel.rs @@ -1067,7 +1067,7 @@ impl AgentPanel { let _ = settings .theme .agent_font_size - .insert(Some(theme::clamp_font_size(agent_font_size).into())); + .insert(theme::clamp_font_size(agent_font_size).into()); }); } else { theme::adjust_agent_font_size(cx, |size| size + delta); diff --git a/crates/assistant_tools/src/grep_tool.rs b/crates/assistant_tools/src/grep_tool.rs index 9f536df995cdfc5860cc3377dce65871386d50e0..c0de8a8d7e1d5552455656e5a76f3201898e5b67 100644 --- a/crates/assistant_tools/src/grep_tool.rs +++ b/crates/assistant_tools/src/grep_tool.rs @@ -856,11 +856,14 @@ mod tests { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -1160,7 +1163,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index 9b4b501bfa55c3037bf6658686d92668cac966a6..9303a50468c428ddd4e603c69d75030dc860e876 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -513,11 +513,14 @@ mod tests { "**/.mymetadata".to_string(), "**/.hidden_subdir".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -701,7 +704,8 @@ mod tests { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index 4ac2ec14ab9dde97b0ff89a40db356fef42d3741..7006cc690375b904be2128e16d254cc6acbaac01 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -684,11 +684,14 @@ mod test { "**/.secretdir".to_string(), "**/.mymetadata".to_string(), ]); - settings.project.worktree.private_files = Some(vec![ - "**/.mysecrets".to_string(), - "**/*.privatekey".to_string(), - "**/*.mysensitive".to_string(), - ]); + settings.project.worktree.private_files = Some( + vec![ + "**/.mysecrets".to_string(), + "**/*.privatekey".to_string(), + "**/*.mysensitive".to_string(), + ] + .into(), + ); }); }); }); @@ -970,7 +973,8 @@ mod test { store.update_user_settings(cx, |settings| { settings.project.worktree.file_scan_exclusions = Some(vec!["**/.git".to_string(), "**/node_modules".to_string()]); - settings.project.worktree.private_files = Some(vec!["**/.env".to_string()]); + settings.project.worktree.private_files = + Some(vec!["**/.env".to_string()].into()); }); }); }); diff --git a/crates/audio/src/audio_settings.rs b/crates/audio/src/audio_settings.rs index 28f97c4b4df6f8741c3fd0552ed883cc7a8838a3..2c9db4989efa5edcf4ef84c4e3031b53980fad51 100644 --- a/crates/audio/src/audio_settings.rs +++ b/crates/audio/src/audio_settings.rs @@ -2,7 +2,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use gpui::App; use settings::{Settings, SettingsStore}; -use util::MergeFrom as _; #[derive(Clone, Debug)] pub struct AudioSettings { @@ -23,7 +22,7 @@ pub struct AudioSettings { /// Configuration of audio in Zed impl Settings for AudioSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let audio = &content.audio.as_ref().unwrap(); AudioSettings { control_input_volume: audio.control_input_volume.unwrap(), @@ -32,17 +31,6 @@ impl Settings for AudioSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(audio) = content.audio.as_ref() else { - return; - }; - self.control_input_volume - .merge_from(&audio.control_input_volume); - self.control_output_volume - .merge_from(&audio.control_output_volume); - self.rodio_audio.merge_from(&audio.rodio_audio); - } - fn import_from_vscode( _vscode: &settings::VsCodeSettings, _current: &mut settings::SettingsContent, diff --git a/crates/auto_update/src/auto_update.rs b/crates/auto_update/src/auto_update.rs index 4e0348575e687c2b4e36fcde7df83b8f329733d0..6c46c145c15cbcb3a6c6790f21a9958c109b846c 100644 --- a/crates/auto_update/src/auto_update.rs +++ b/crates/auto_update/src/auto_update.rs @@ -9,7 +9,7 @@ use http_client::{AsyncBody, HttpClient, HttpClientWithUrl}; use paths::remote_servers_dir; use release_channel::{AppCommitSha, ReleaseChannel}; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsContent, SettingsStore}; +use settings::{Settings, SettingsStore}; use smol::{fs, io::AsyncReadExt}; use smol::{fs::File, process::Command}; use std::{ @@ -119,21 +119,9 @@ struct AutoUpdateSetting(bool); /// /// Default: true impl Settings for AutoUpdateSetting { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { - debug_assert_eq!(content.auto_update.unwrap(), true); + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self(content.auto_update.unwrap()) } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - if let Some(auto_update) = content.auto_update { - self.0 = auto_update; - } - } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) { - // We could match on vscode's update.mode here, but - // I think it's more important to have more people updating zed by default. - } } #[derive(Default)] diff --git a/crates/call/src/call_settings.rs b/crates/call/src/call_settings.rs index cce8142903c17ec41244d346a79feb8f4cad70e6..a97ac682022ef30c603ca94fe60fe78064726f42 100644 --- a/crates/call/src/call_settings.rs +++ b/crates/call/src/call_settings.rs @@ -1,6 +1,5 @@ use gpui::App; use settings::Settings; -use util::MergeFrom; #[derive(Debug)] pub struct CallSettings { @@ -9,7 +8,7 @@ pub struct CallSettings { } impl Settings for CallSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let call = content.calls.clone().unwrap(); CallSettings { mute_on_join: call.mute_on_join.unwrap(), @@ -17,13 +16,6 @@ impl Settings for CallSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - if let Some(call) = content.calls.clone() { - self.mute_on_join.merge_from(&call.mute_on_join); - self.share_on_join.merge_from(&call.share_on_join); - } - } - fn import_from_vscode( _vscode: &settings::VsCodeSettings, _current: &mut settings::SettingsContent, diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 01007cdc6618996735c859284e3860b936f540e8..86ecb1b34e323289b542d3bd6f48520c50867ad6 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -41,7 +41,6 @@ rand.workspace = true regex.workspace = true release_channel.workspace = true rpc = { workspace = true, features = ["gpui"] } -schemars.workspace = true serde.workspace = true serde_json.workspace = true serde_urlencoded.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 237eaa11d954db7c95eaa513e8e921a1000faac6..e098e7aed52281605c2882514b23c81d2041c6db 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -29,9 +29,8 @@ use proxy::connect_proxy_stream; use rand::prelude::*; use release_channel::{AppVersion, ReleaseChannel}; use rpc::proto::{AnyTypedEnvelope, EnvelopedMessage, PeerId, RequestMessage}; -use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{Settings, SettingsContent, SettingsKey, SettingsUi}; +use settings::{Settings, SettingsContent}; use std::{ any::TypeId, convert::TryFrom, @@ -50,7 +49,7 @@ use telemetry::Telemetry; use thiserror::Error; use tokio::net::TcpStream; use url::Url; -use util::{ConnectionResult, MergeFrom, ResultExt}; +use util::{ConnectionResult, ResultExt}; pub use rpc::*; pub use telemetry_events::Event; @@ -102,7 +101,7 @@ pub struct ClientSettings { } impl Settings for ClientSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { if let Some(server_url) = &*ZED_SERVER_URL { return Self { server_url: server_url.clone(), @@ -112,23 +111,6 @@ impl Settings for ClientSettings { server_url: content.server_url.clone().unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if ZED_SERVER_URL.is_some() { - return; - } - if let Some(server_url) = content.server_url.clone() { - self.server_url = server_url; - } - } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} -} - -#[derive(Default, Clone, Serialize, Deserialize, JsonSchema, SettingsUi, SettingsKey)] -#[settings_key(None)] -pub struct ProxySettingsContent { - proxy: Option, } #[derive(Deserialize, Default)] @@ -151,18 +133,12 @@ impl ProxySettings { } impl Settings for ProxySettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { proxy: content.proxy.clone(), } } - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if let Some(proxy) = content.proxy.clone() { - self.proxy = Some(proxy) - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { vscode.string_setting("http.proxy", &mut current.proxy); } @@ -543,21 +519,13 @@ pub struct TelemetrySettings { } impl settings::Settings for TelemetrySettings { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { Self { diagnostics: content.telemetry.as_ref().unwrap().diagnostics.unwrap(), metrics: content.telemetry.as_ref().unwrap().metrics.unwrap(), } } - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let Some(telemetry) = &content.telemetry else { - return; - }; - self.diagnostics.merge_from(&telemetry.diagnostics); - self.metrics.merge_from(&telemetry.metrics); - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { let mut telemetry = settings::TelemetrySettingsContent::default(); vscode.enum_setting("telemetry.telemetryLevel", &mut telemetry.metrics, |s| { diff --git a/crates/collab_ui/src/panel_settings.rs b/crates/collab_ui/src/panel_settings.rs index f1cd24c820a631f86c215f810011cc5870ae1ff9..58be0c358b2626426bc050b2eb7940f35690b37b 100644 --- a/crates/collab_ui/src/panel_settings.rs +++ b/crates/collab_ui/src/panel_settings.rs @@ -1,7 +1,6 @@ use gpui::Pixels; use settings::Settings; use ui::px; -use util::MergeFrom as _; use workspace::dock::DockPosition; #[derive(Debug)] @@ -19,7 +18,7 @@ pub struct NotificationPanelSettings { } impl Settings for CollaborationPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let panel = content.collaboration_panel.as_ref().unwrap(); Self { @@ -28,25 +27,10 @@ impl Settings for CollaborationPanelSettings { default_width: panel.default_width.map(px).unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - if let Some(panel) = content.collaboration_panel.as_ref() { - self.button.merge_from(&panel.button); - self.default_width - .merge_from(&panel.default_width.map(Pixels::from)); - self.dock.merge_from(&panel.dock.map(Into::into)); - } - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _content: &mut settings::SettingsContent, - ) { - } } impl Settings for NotificationPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let panel = content.notification_panel.as_ref().unwrap(); return Self { button: panel.button.unwrap(), @@ -54,19 +38,4 @@ impl Settings for NotificationPanelSettings { default_width: panel.default_width.map(px).unwrap(), }; } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - let Some(panel) = content.notification_panel.as_ref() else { - return; - }; - self.button.merge_from(&panel.button); - self.dock.merge_from(&panel.dock.map(Into::into)); - self.default_width.merge_from(&panel.default_width.map(px)); - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - } } diff --git a/crates/collections/src/collections.rs b/crates/collections/src/collections.rs index be7bbdb59f646682e0eb84ddc40d3e260ef96d94..6544d90b917899764795ee516e8082d179628e7f 100644 --- a/crates/collections/src/collections.rs +++ b/crates/collections/src/collections.rs @@ -22,6 +22,7 @@ pub type IndexMap = indexmap::IndexMap; #[cfg(not(feature = "test-support"))] pub type IndexSet = indexmap::IndexSet; +pub use indexmap::Equivalent; pub use rustc_hash::FxHasher; pub use rustc_hash::{FxHashMap, FxHashSet}; pub use std::collections::*; diff --git a/crates/dap/src/debugger_settings.rs b/crates/dap/src/debugger_settings.rs index 5fa3d60dfc08cd847dd961c322555b5ef54c78f7..114f858eec5a5660e74b1cf8a80aecf812f17f93 100644 --- a/crates/dap/src/debugger_settings.rs +++ b/crates/dap/src/debugger_settings.rs @@ -1,7 +1,6 @@ use dap_types::SteppingGranularity; use gpui::App; use settings::{Settings, SettingsContent}; -use util::MergeFrom; pub struct DebuggerSettings { /// Determines the stepping granularity. @@ -35,7 +34,7 @@ pub struct DebuggerSettings { } impl Settings for DebuggerSettings { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { let content = content.debugger.clone().unwrap(); Self { stepping_granularity: dap_granularity_from_settings( @@ -49,27 +48,6 @@ impl Settings for DebuggerSettings { dock: content.dock.unwrap(), } } - - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let Some(content) = &content.debugger else { - return; - }; - self.stepping_granularity.merge_from( - &content - .stepping_granularity - .map(dap_granularity_from_settings), - ); - self.save_breakpoints.merge_from(&content.save_breakpoints); - self.button.merge_from(&content.button); - self.timeout.merge_from(&content.timeout); - self.log_dap_communications - .merge_from(&content.log_dap_communications); - self.format_dap_log_messages - .merge_from(&content.format_dap_log_messages); - self.dock.merge_from(&content.dock); - } - - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } fn dap_granularity_from_settings( diff --git a/crates/editor/src/editor_settings.rs b/crates/editor/src/editor_settings.rs index a6893a10791b1e5ce19d4ad484327d6f16539a74..6ad8304f911a54fc0817f963aa593dc6e908637b 100644 --- a/crates/editor/src/editor_settings.rs +++ b/crates/editor/src/editor_settings.rs @@ -12,7 +12,6 @@ pub use settings::{ }; use settings::{Settings, SettingsContent}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; -use util::MergeFrom; /// Imports from the VSCode settings at /// https://code.visualstudio.com/docs/reference/default-settings @@ -190,7 +189,7 @@ impl ScrollbarVisibility for EditorSettings { } impl Settings for EditorSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let editor = content.editor.clone(); let scrollbar = editor.scrollbar.unwrap(); let minimap = editor.minimap.unwrap(); @@ -238,7 +237,7 @@ impl Settings for EditorSettings { display_in: minimap.display_in.unwrap(), thumb: minimap.thumb.unwrap(), thumb_border: minimap.thumb_border.unwrap(), - current_line_highlight: minimap.current_line_highlight.flatten(), + current_line_highlight: minimap.current_line_highlight, max_width_columns: minimap.max_width_columns.unwrap(), }, gutter: Gutter { @@ -290,162 +289,6 @@ impl Settings for EditorSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let editor = &content.editor; - self.cursor_blink.merge_from(&editor.cursor_blink); - if let Some(cursor_shape) = editor.cursor_shape { - self.cursor_shape = Some(cursor_shape.into()) - } - self.current_line_highlight - .merge_from(&editor.current_line_highlight); - self.selection_highlight - .merge_from(&editor.selection_highlight); - self.rounded_selection.merge_from(&editor.rounded_selection); - self.lsp_highlight_debounce - .merge_from(&editor.lsp_highlight_debounce); - self.hover_popover_enabled - .merge_from(&editor.hover_popover_enabled); - self.hover_popover_delay - .merge_from(&editor.hover_popover_delay); - self.scroll_beyond_last_line - .merge_from(&editor.scroll_beyond_last_line); - self.vertical_scroll_margin - .merge_from(&editor.vertical_scroll_margin); - self.autoscroll_on_clicks - .merge_from(&editor.autoscroll_on_clicks); - self.horizontal_scroll_margin - .merge_from(&editor.horizontal_scroll_margin); - self.scroll_sensitivity - .merge_from(&editor.scroll_sensitivity); - self.fast_scroll_sensitivity - .merge_from(&editor.fast_scroll_sensitivity); - self.relative_line_numbers - .merge_from(&editor.relative_line_numbers); - self.seed_search_query_from_cursor - .merge_from(&editor.seed_search_query_from_cursor); - self.use_smartcase_search - .merge_from(&editor.use_smartcase_search); - self.multi_cursor_modifier - .merge_from(&editor.multi_cursor_modifier); - self.redact_private_values - .merge_from(&editor.redact_private_values); - self.expand_excerpt_lines - .merge_from(&editor.expand_excerpt_lines); - self.excerpt_context_lines - .merge_from(&editor.excerpt_context_lines); - self.middle_click_paste - .merge_from(&editor.middle_click_paste); - self.double_click_in_multibuffer - .merge_from(&editor.double_click_in_multibuffer); - self.search_wrap.merge_from(&editor.search_wrap); - self.auto_signature_help - .merge_from(&editor.auto_signature_help); - self.show_signature_help_after_edits - .merge_from(&editor.show_signature_help_after_edits); - self.go_to_definition_fallback - .merge_from(&editor.go_to_definition_fallback); - if let Some(hide_mouse) = editor.hide_mouse { - self.hide_mouse = Some(hide_mouse) - } - self.snippet_sort_order - .merge_from(&editor.snippet_sort_order); - if let Some(diagnostics_max_severity) = editor.diagnostics_max_severity { - self.diagnostics_max_severity = Some(diagnostics_max_severity.into()); - } - self.inline_code_actions - .merge_from(&editor.inline_code_actions); - self.lsp_document_colors - .merge_from(&editor.lsp_document_colors); - self.minimum_contrast_for_highlights - .merge_from(&editor.minimum_contrast_for_highlights); - - if let Some(status_bar) = &editor.status_bar { - self.status_bar - .active_language_button - .merge_from(&status_bar.active_language_button); - self.status_bar - .cursor_position_button - .merge_from(&status_bar.cursor_position_button); - } - if let Some(toolbar) = &editor.toolbar { - self.toolbar.breadcrumbs.merge_from(&toolbar.breadcrumbs); - self.toolbar - .quick_actions - .merge_from(&toolbar.quick_actions); - self.toolbar - .selections_menu - .merge_from(&toolbar.selections_menu); - self.toolbar.agent_review.merge_from(&toolbar.agent_review); - self.toolbar.code_actions.merge_from(&toolbar.code_actions); - } - if let Some(scrollbar) = &editor.scrollbar { - self.scrollbar - .show - .merge_from(&scrollbar.show.map(Into::into)); - self.scrollbar.git_diff.merge_from(&scrollbar.git_diff); - self.scrollbar - .selected_text - .merge_from(&scrollbar.selected_text); - self.scrollbar - .selected_symbol - .merge_from(&scrollbar.selected_symbol); - self.scrollbar - .search_results - .merge_from(&scrollbar.search_results); - self.scrollbar - .diagnostics - .merge_from(&scrollbar.diagnostics); - self.scrollbar.cursors.merge_from(&scrollbar.cursors); - if let Some(axes) = &scrollbar.axes { - self.scrollbar.axes.horizontal.merge_from(&axes.horizontal); - self.scrollbar.axes.vertical.merge_from(&axes.vertical); - } - } - if let Some(minimap) = &editor.minimap { - self.minimap.show.merge_from(&minimap.show); - self.minimap.display_in.merge_from(&minimap.display_in); - self.minimap.thumb.merge_from(&minimap.thumb); - self.minimap.thumb_border.merge_from(&minimap.thumb_border); - self.minimap - .current_line_highlight - .merge_from(&minimap.current_line_highlight); - self.minimap - .max_width_columns - .merge_from(&minimap.max_width_columns); - } - if let Some(gutter) = &editor.gutter { - self.gutter - .min_line_number_digits - .merge_from(&gutter.min_line_number_digits); - self.gutter.line_numbers.merge_from(&gutter.line_numbers); - self.gutter.runnables.merge_from(&gutter.runnables); - self.gutter.breakpoints.merge_from(&gutter.breakpoints); - self.gutter.folds.merge_from(&gutter.folds); - } - if let Some(search) = &editor.search { - self.search.button.merge_from(&search.button); - self.search.whole_word.merge_from(&search.whole_word); - self.search - .case_sensitive - .merge_from(&search.case_sensitive); - self.search - .include_ignored - .merge_from(&search.include_ignored); - self.search.regex.merge_from(&search.regex); - } - if let Some(enabled) = editor.jupyter.as_ref().and_then(|jupyter| jupyter.enabled) { - self.jupyter.enabled = enabled; - } - if let Some(drag_and_drop_selection) = &editor.drag_and_drop_selection { - self.drag_and_drop_selection - .enabled - .merge_from(&drag_and_drop_selection.enabled); - self.drag_and_drop_selection - .delay - .merge_from(&drag_and_drop_selection.delay); - } - } - fn import_from_vscode(vscode: &VsCodeSettings, current: &mut SettingsContent) { vscode.enum_setting( "editor.cursorBlinking", diff --git a/crates/extension_host/src/extension_settings.rs b/crates/extension_host/src/extension_settings.rs index b9e1609b42d6e7fa2f4dab63486a77a13f29824f..0f15add8cbf7e422b2d90c684a46c464672ad1d6 100644 --- a/crates/extension_host/src/extension_settings.rs +++ b/crates/extension_host/src/extension_settings.rs @@ -33,26 +33,10 @@ impl ExtensionSettings { } impl Settings for ExtensionSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { auto_install_extensions: content.extension.auto_install_extensions.clone(), auto_update_extensions: content.extension.auto_update_extensions.clone(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - self.auto_install_extensions - .extend(content.extension.auto_install_extensions.clone()); - self.auto_update_extensions - .extend(content.extension.auto_update_extensions.clone()); - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - // settingsSync.ignoredExtensions controls autoupdate for vscode extensions, but we - // don't have a mapping to zed-extensions. there's also extensions.autoCheckUpdates - // and extensions.autoUpdate which are global switches, we don't support those yet - } } diff --git a/crates/file_finder/src/file_finder_settings.rs b/crates/file_finder/src/file_finder_settings.rs index 3f974b7412d740dd30e96fdd61487ac8774daa0e..cf2b4f4bfb87f7a71c2dcc2a1d0a2218131c988a 100644 --- a/crates/file_finder/src/file_finder_settings.rs +++ b/crates/file_finder/src/file_finder_settings.rs @@ -1,7 +1,6 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::Settings; -use util::MergeFrom; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct FileFinderSettings { @@ -12,30 +11,16 @@ pub struct FileFinderSettings { } impl Settings for FileFinderSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let file_finder = content.file_finder.as_ref().unwrap(); Self { file_icons: file_finder.file_icons.unwrap(), modal_max_width: file_finder.modal_max_width.unwrap().into(), skip_focus_for_active_in_search: file_finder.skip_focus_for_active_in_search.unwrap(), - include_ignored: file_finder.include_ignored.flatten(), + include_ignored: file_finder.include_ignored, } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - let Some(file_finder) = content.file_finder.as_ref() else { - return; - }; - - self.file_icons.merge_from(&file_finder.file_icons); - self.modal_max_width - .merge_from(&file_finder.modal_max_width.map(Into::into)); - self.skip_focus_for_active_in_search - .merge_from(&file_finder.skip_focus_for_active_in_search); - self.include_ignored - .merge_from(&file_finder.include_ignored); - } } #[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] diff --git a/crates/git_hosting_providers/src/settings.rs b/crates/git_hosting_providers/src/settings.rs index b6aabc47f3fba9fca6c9b908de87ca7319afa616..e045fae08b7a4dc019177361d7365f286c95518c 100644 --- a/crates/git_hosting_providers/src/settings.rs +++ b/crates/git_hosting_providers/src/settings.rs @@ -58,17 +58,14 @@ pub struct GitHostingProviderSettings { } impl Settings for GitHostingProviderSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { - git_hosting_providers: content.project.git_hosting_providers.clone().unwrap(), + git_hosting_providers: content + .project + .git_hosting_providers + .clone() + .unwrap() + .into(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if let Some(more) = &content.project.git_hosting_providers { - self.git_hosting_providers.extend_from_slice(&more.clone()); - } - } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} } diff --git a/crates/git_ui/src/git_panel_settings.rs b/crates/git_ui/src/git_panel_settings.rs index c82ff469857f084ce59201b359a79883056fee43..b137988539510a3d3242656bd1f6cc6d85a07703 100644 --- a/crates/git_ui/src/git_panel_settings.rs +++ b/crates/git_ui/src/git_panel_settings.rs @@ -7,7 +7,6 @@ use ui::{ px, scrollbars::{ScrollbarVisibility, ShowScrollbar}, }; -use util::MergeFrom; use workspace::dock::DockPosition; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] @@ -52,7 +51,7 @@ impl ScrollbarVisibility for GitPanelSettings { } impl Settings for GitPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let git_panel = content.git_panel.clone().unwrap(); Self { button: git_panel.button.unwrap(), @@ -68,25 +67,6 @@ impl Settings for GitPanelSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut ui::App) { - let Some(git_panel) = &content.git_panel else { - return; - }; - self.button.merge_from(&git_panel.button); - self.dock.merge_from(&git_panel.dock.map(Into::into)); - self.default_width - .merge_from(&git_panel.default_width.map(px)); - self.status_style.merge_from(&git_panel.status_style); - self.fallback_branch_name - .merge_from(&git_panel.fallback_branch_name); - self.sort_by_path.merge_from(&git_panel.sort_by_path); - self.collapse_untracked_diff - .merge_from(&git_panel.collapse_untracked_diff); - if let Some(show) = git_panel.scrollbar.as_ref().and_then(|s| s.show) { - self.scrollbar.show = Some(show.into()) - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(git_enabled) = vscode.read_bool("git.enabled") { current.git_panel.get_or_insert_default().button = Some(git_enabled); diff --git a/crates/go_to_line/src/cursor_position.rs b/crates/go_to_line/src/cursor_position.rs index 9c109db8de6e092da5513317d9bc5b686146edf8..9387a5359ef3013574876602b4c67a44497c65cc 100644 --- a/crates/go_to_line/src/cursor_position.rs +++ b/crates/go_to_line/src/cursor_position.rs @@ -7,7 +7,7 @@ use ui::{ Button, ButtonCommon, Clickable, Context, FluentBuilder, IntoElement, LabelSize, ParentElement, Render, Tooltip, Window, div, }; -use util::{MergeFrom, paths::FILE_ROW_COLUMN_DELIMITER}; +use util::paths::FILE_ROW_COLUMN_DELIMITER; use workspace::{StatusItemView, Workspace, item::ItemHandle}; #[derive(Copy, Clone, Debug, Default, PartialOrd, PartialEq)] @@ -307,11 +307,7 @@ impl From for LineIndicatorFormat { } impl Settings for LineIndicatorFormat { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { content.line_indicator_format.unwrap().into() } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - self.merge_from(&content.line_indicator_format.map(Into::into)); - } } diff --git a/crates/image_viewer/src/image_viewer_settings.rs b/crates/image_viewer/src/image_viewer_settings.rs index e093a5fc826fa23c9894ecdb56466dcb71e03465..64f2e4948284265c1f348cb90be85d18d8c22d8e 100644 --- a/crates/image_viewer/src/image_viewer_settings.rs +++ b/crates/image_viewer/src/image_viewer_settings.rs @@ -1,7 +1,6 @@ use gpui::App; pub use settings::ImageFileSizeUnit; use settings::Settings; -use util::MergeFrom; /// The settings for the image viewer. #[derive(Clone, Debug, Default)] @@ -13,18 +12,9 @@ pub struct ImageViewerSettings { } impl Settings for ImageViewerSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { unit: content.image_viewer.clone().unwrap().unit.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - self.unit.merge_from( - &content - .image_viewer - .as_ref() - .and_then(|image_viewer| image_viewer.unit), - ); - } } diff --git a/crates/journal/Cargo.toml b/crates/journal/Cargo.toml index 3c98e4a712a4e53358d2d31e7df981dccb86fa3c..1b32c9cdbb7a20a13e39a2d61554e4dd7018d81b 100644 --- a/crates/journal/Cargo.toml +++ b/crates/journal/Cargo.toml @@ -23,7 +23,6 @@ settings.workspace = true shellexpand.workspace = true workspace.workspace = true workspace-hack.workspace = true -util.workspace = true [dev-dependencies] editor = { workspace = true, features = ["test-support"] } diff --git a/crates/journal/src/journal.rs b/crates/journal/src/journal.rs index 24c3fecf78c2ea3ce1cd036e8b90f88a5d889ccd..52d93ba21a828b076141dd8d21a1b8f88bc20be8 100644 --- a/crates/journal/src/journal.rs +++ b/crates/journal/src/journal.rs @@ -9,7 +9,6 @@ use std::{ path::{Path, PathBuf}, sync::Arc, }; -use util::MergeFrom; use workspace::{AppState, OpenVisible, Workspace}; actions!( @@ -34,7 +33,7 @@ pub struct JournalSettings { } impl settings::Settings for JournalSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let journal = content.journal.clone().unwrap(); Self { @@ -42,14 +41,6 @@ impl settings::Settings for JournalSettings { hour_format: journal.hour_format.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(journal) = content.journal.as_ref() else { - return; - }; - self.path.merge_from(&journal.path); - self.hour_format.merge_from(&journal.hour_format); - } } pub fn init(_: Arc, cx: &mut App) { diff --git a/crates/language/Cargo.toml b/crates/language/Cargo.toml index 4ab56d6647db5246bf0af7343c8485d946c8b156..4a4f51a58be7e4ffc61617dd74c2ed0ad7b49d34 100644 --- a/crates/language/Cargo.toml +++ b/crates/language/Cargo.toml @@ -39,7 +39,6 @@ globset.workspace = true gpui.workspace = true http_client.workspace = true imara-diff.workspace = true -inventory.workspace = true itertools.workspace = true log.workspace = true lsp.workspace = true diff --git a/crates/language/src/buffer_tests.rs b/crates/language/src/buffer_tests.rs index e079029b478af1c70ff626acea0791cdb204421f..a60399538e840d18d42d3ed2b6d6f62927d4e046 100644 --- a/crates/language/src/buffer_tests.rs +++ b/crates/language/src/buffer_tests.rs @@ -269,15 +269,15 @@ async fn test_language_for_file_with_custom_file_types(cx: &mut TestAppContext) cx.update(|cx| { init_settings(cx, |settings| { settings.file_types.extend([ - ("TypeScript".into(), vec!["js".into()]), + ("TypeScript".into(), vec!["js".into()].into()), ( "JavaScript".into(), - vec!["*longer.ts".into(), "ecmascript".into()], + vec!["*longer.ts".into(), "ecmascript".into()].into(), ), - ("C++".into(), vec!["c".into(), "*.dev".into()]), + ("C++".into(), vec!["c".into(), "*.dev".into()].into()), ( "Dockerfile".into(), - vec!["Dockerfile".into(), "Dockerfile.*".into()], + vec!["Dockerfile".into(), "Dockerfile.*".into()].into(), ), ]); }) diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index ff7b432c5f26043799d94458621aa35f969ade06..64744ee99d24a56abb357e0c034e11afa4dae9d0 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -9,22 +9,15 @@ use ec4rs::{ use globset::{Glob, GlobMatcher, GlobSet, GlobSetBuilder}; use gpui::{App, Modifiers}; use itertools::{Either, Itertools}; -use schemars::json_schema; pub use settings::{ CompletionSettingsContent, EditPredictionProvider, EditPredictionsMode, FormatOnSave, Formatter, FormatterList, InlayHintKind, LanguageSettingsContent, LspInsertMode, RewrapBehavior, SelectedFormatter, ShowWhitespaceSetting, SoftWrap, WordsCompletionMode, }; -use settings::{ - IndentGuideSettingsContent, LanguageTaskSettingsContent, ParameterizedJsonSchema, - PrettierSettingsContent, Settings, SettingsContent, SettingsLocation, SettingsStore, - SettingsUi, -}; +use settings::{ExtendingVec, Settings, SettingsContent, SettingsLocation, SettingsStore}; use shellexpand; use std::{borrow::Cow, num::NonZeroU32, path::Path, sync::Arc}; -use util::MergeFrom; -use util::{ResultExt, schemars::replace_subschema}; /// Initializes the language settings. pub fn init(cx: &mut App) { @@ -64,7 +57,6 @@ pub struct AllLanguageSettings { pub defaults: LanguageSettings, languages: HashMap, pub(crate) file_types: FxHashMap, GlobSet>, - pub(crate) file_globs: FxHashMap, Vec>, } /// The settings for a particular language. @@ -189,18 +181,6 @@ pub struct CompletionSettings { pub lsp_insert_mode: LspInsertMode, } -impl CompletionSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src else { return }; - self.words.merge_from(&src.words); - self.words_min_length.merge_from(&src.words_min_length); - self.lsp.merge_from(&src.lsp); - self.lsp_fetch_timeout_ms - .merge_from(&src.lsp_fetch_timeout_ms); - self.lsp_insert_mode.merge_from(&src.lsp_insert_mode); - } -} - /// The settings for indent guides. #[derive(Debug, Clone, PartialEq)] pub struct IndentGuideSettings { @@ -226,19 +206,6 @@ pub struct IndentGuideSettings { pub background_coloring: settings::IndentGuideBackgroundColoring, } -impl IndentGuideSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src else { return }; - - self.enabled.merge_from(&src.enabled); - self.line_width.merge_from(&src.line_width); - self.active_line_width.merge_from(&src.active_line_width); - self.coloring.merge_from(&src.coloring); - self.background_coloring - .merge_from(&src.background_coloring); - } -} - #[derive(Debug, Clone)] pub struct LanguageTaskSettings { /// Extra task variables to set for a particular language. @@ -254,17 +221,6 @@ pub struct LanguageTaskSettings { pub prefer_lsp: bool, } -impl LanguageTaskSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src.clone() else { - return; - }; - self.variables.extend(src.variables); - self.enabled.merge_from(&src.enabled); - self.prefer_lsp.merge_from(&src.prefer_lsp); - } -} - /// Allows to enable/disable formatting with Prettier /// and configure default Prettier, used when no project-level Prettier installation is found. /// Prettier formatting is disabled by default. @@ -285,16 +241,6 @@ pub struct PrettierSettings { pub options: HashMap, } -impl PrettierSettings { - pub fn merge_from(&mut self, src: &Option) { - let Some(src) = src.clone() else { return }; - self.allowed.merge_from(&src.allowed); - self.parser = src.parser.clone(); - self.plugins.extend(src.plugins); - self.options.extend(src.options); - } -} - impl LanguageSettings { /// A token representing the rest of the available language servers. const REST_OF_LANGUAGE_SERVERS: &'static str = "..."; @@ -413,14 +359,13 @@ impl InlayHintSettings { /// The settings for edit predictions, such as [GitHub Copilot](https://github.com/features/copilot) /// or [Supermaven](https://supermaven.com). -#[derive(Clone, Debug, Default, SettingsUi)] +#[derive(Clone, Debug, Default)] pub struct EditPredictionSettings { /// The provider that supplies edit predictions. pub provider: settings::EditPredictionProvider, /// A list of globs representing files that edit predictions should be disabled for. /// This list adds to a pre-existing, sensible default set of globs. /// Any additional ones you add are combined with them. - #[settings_ui(skip)] pub disabled_globs: Vec, /// Configures how edit predictions are displayed in the buffer. pub mode: settings::EditPredictionsMode, @@ -451,41 +396,16 @@ pub struct DisabledGlob { is_absolute: bool, } -#[derive(Clone, Debug, Default, SettingsUi)] +#[derive(Clone, Debug, Default)] pub struct CopilotSettings { /// HTTP/HTTPS proxy to use for Copilot. - #[settings_ui(skip)] pub proxy: Option, /// Disable certificate verification for proxy (not recommended). pub proxy_no_verify: Option, /// Enterprise URI for Copilot. - #[settings_ui(skip)] pub enterprise_uri: Option, } -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - let language_settings_content_ref = generator - .subschema_for::() - .to_value(); - replace_subschema::(generator, || json_schema!({ - "type": "object", - "properties": params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - language_settings_content_ref.clone(), - ) - }) - .collect::>() - })) - } - } -} - impl AllLanguageSettings { /// Returns the [`LanguageSettings`] for the language with the specified name. pub fn language<'a>( @@ -574,93 +494,99 @@ fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigPr } impl settings::Settings for AllLanguageSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let all_languages = &content.project.all_languages; - let defaults = all_languages.defaults.clone(); - let inlay_hints = defaults.inlay_hints.unwrap(); - let completions = defaults.completions.unwrap(); - let prettier = defaults.prettier.unwrap(); - let indent_guides = defaults.indent_guides.unwrap(); - let tasks = defaults.tasks.unwrap(); - - let default_language_settings = LanguageSettings { - tab_size: defaults.tab_size.unwrap(), - hard_tabs: defaults.hard_tabs.unwrap(), - soft_wrap: defaults.soft_wrap.unwrap(), - preferred_line_length: defaults.preferred_line_length.unwrap(), - show_wrap_guides: defaults.show_wrap_guides.unwrap(), - wrap_guides: defaults.wrap_guides.unwrap(), - indent_guides: IndentGuideSettings { - enabled: indent_guides.enabled.unwrap(), - line_width: indent_guides.line_width.unwrap(), - active_line_width: indent_guides.active_line_width.unwrap(), - coloring: indent_guides.coloring.unwrap(), - background_coloring: indent_guides.background_coloring.unwrap(), - }, - format_on_save: defaults.format_on_save.unwrap(), - remove_trailing_whitespace_on_save: defaults - .remove_trailing_whitespace_on_save - .unwrap(), - ensure_final_newline_on_save: defaults.ensure_final_newline_on_save.unwrap(), - formatter: defaults.formatter.unwrap(), - prettier: PrettierSettings { - allowed: prettier.allowed.unwrap(), - parser: prettier.parser, - plugins: prettier.plugins, - options: prettier.options, - }, - jsx_tag_auto_close: defaults.jsx_tag_auto_close.unwrap().enabled.unwrap(), - enable_language_server: defaults.enable_language_server.unwrap(), - language_servers: defaults.language_servers.unwrap(), - allow_rewrap: defaults.allow_rewrap.unwrap(), - show_edit_predictions: defaults.show_edit_predictions.unwrap(), - edit_predictions_disabled_in: defaults.edit_predictions_disabled_in.unwrap(), - show_whitespaces: defaults.show_whitespaces.unwrap(), - whitespace_map: defaults.whitespace_map.unwrap(), - extend_comment_on_newline: defaults.extend_comment_on_newline.unwrap(), - inlay_hints: InlayHintSettings { - enabled: inlay_hints.enabled.unwrap(), - show_value_hints: inlay_hints.show_value_hints.unwrap(), - show_type_hints: inlay_hints.show_type_hints.unwrap(), - show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(), - show_other_hints: inlay_hints.show_other_hints.unwrap(), - show_background: inlay_hints.show_background.unwrap(), - edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(), - scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(), - toggle_on_modifiers_press: inlay_hints.toggle_on_modifiers_press, - }, - use_autoclose: defaults.use_autoclose.unwrap(), - use_auto_surround: defaults.use_auto_surround.unwrap(), - use_on_type_format: defaults.use_on_type_format.unwrap(), - auto_indent: defaults.auto_indent.unwrap(), - auto_indent_on_paste: defaults.auto_indent_on_paste.unwrap(), - always_treat_brackets_as_autoclosed: defaults - .always_treat_brackets_as_autoclosed - .unwrap(), - code_actions_on_format: defaults.code_actions_on_format.unwrap(), - linked_edits: defaults.linked_edits.unwrap(), - tasks: LanguageTaskSettings { - variables: tasks.variables, - enabled: tasks.enabled.unwrap(), - prefer_lsp: tasks.prefer_lsp.unwrap(), - }, - show_completions_on_input: defaults.show_completions_on_input.unwrap(), - show_completion_documentation: defaults.show_completion_documentation.unwrap(), - completions: CompletionSettings { - words: completions.words.unwrap(), - words_min_length: completions.words_min_length.unwrap(), - lsp: completions.lsp.unwrap(), - lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(), - lsp_insert_mode: completions.lsp_insert_mode.unwrap(), - }, - debuggers: defaults.debuggers.unwrap(), - }; + + fn load_from_content(settings: LanguageSettingsContent) -> LanguageSettings { + let inlay_hints = settings.inlay_hints.unwrap(); + let completions = settings.completions.unwrap(); + let prettier = settings.prettier.unwrap(); + let indent_guides = settings.indent_guides.unwrap(); + let tasks = settings.tasks.unwrap(); + LanguageSettings { + tab_size: settings.tab_size.unwrap(), + hard_tabs: settings.hard_tabs.unwrap(), + soft_wrap: settings.soft_wrap.unwrap(), + preferred_line_length: settings.preferred_line_length.unwrap(), + show_wrap_guides: settings.show_wrap_guides.unwrap(), + wrap_guides: settings.wrap_guides.unwrap(), + indent_guides: IndentGuideSettings { + enabled: indent_guides.enabled.unwrap(), + line_width: indent_guides.line_width.unwrap(), + active_line_width: indent_guides.active_line_width.unwrap(), + coloring: indent_guides.coloring.unwrap(), + background_coloring: indent_guides.background_coloring.unwrap(), + }, + format_on_save: settings.format_on_save.unwrap(), + remove_trailing_whitespace_on_save: settings + .remove_trailing_whitespace_on_save + .unwrap(), + ensure_final_newline_on_save: settings.ensure_final_newline_on_save.unwrap(), + formatter: settings.formatter.unwrap(), + prettier: PrettierSettings { + allowed: prettier.allowed.unwrap(), + parser: prettier.parser, + plugins: prettier.plugins, + options: prettier.options, + }, + jsx_tag_auto_close: settings.jsx_tag_auto_close.unwrap().enabled.unwrap(), + enable_language_server: settings.enable_language_server.unwrap(), + language_servers: settings.language_servers.unwrap(), + allow_rewrap: settings.allow_rewrap.unwrap(), + show_edit_predictions: settings.show_edit_predictions.unwrap(), + edit_predictions_disabled_in: settings.edit_predictions_disabled_in.unwrap(), + show_whitespaces: settings.show_whitespaces.unwrap(), + whitespace_map: settings.whitespace_map.unwrap(), + extend_comment_on_newline: settings.extend_comment_on_newline.unwrap(), + inlay_hints: InlayHintSettings { + enabled: inlay_hints.enabled.unwrap(), + show_value_hints: inlay_hints.show_value_hints.unwrap(), + show_type_hints: inlay_hints.show_type_hints.unwrap(), + show_parameter_hints: inlay_hints.show_parameter_hints.unwrap(), + show_other_hints: inlay_hints.show_other_hints.unwrap(), + show_background: inlay_hints.show_background.unwrap(), + edit_debounce_ms: inlay_hints.edit_debounce_ms.unwrap(), + scroll_debounce_ms: inlay_hints.scroll_debounce_ms.unwrap(), + toggle_on_modifiers_press: inlay_hints.toggle_on_modifiers_press, + }, + use_autoclose: settings.use_autoclose.unwrap(), + use_auto_surround: settings.use_auto_surround.unwrap(), + use_on_type_format: settings.use_on_type_format.unwrap(), + auto_indent: settings.auto_indent.unwrap(), + auto_indent_on_paste: settings.auto_indent_on_paste.unwrap(), + always_treat_brackets_as_autoclosed: settings + .always_treat_brackets_as_autoclosed + .unwrap(), + code_actions_on_format: settings.code_actions_on_format.unwrap(), + linked_edits: settings.linked_edits.unwrap(), + tasks: LanguageTaskSettings { + variables: tasks.variables, + enabled: tasks.enabled.unwrap(), + prefer_lsp: tasks.prefer_lsp.unwrap(), + }, + show_completions_on_input: settings.show_completions_on_input.unwrap(), + show_completion_documentation: settings.show_completion_documentation.unwrap(), + completions: CompletionSettings { + words: completions.words.unwrap(), + words_min_length: completions.words_min_length.unwrap(), + lsp: completions.lsp.unwrap(), + lsp_fetch_timeout_ms: completions.lsp_fetch_timeout_ms.unwrap(), + lsp_insert_mode: completions.lsp_insert_mode.unwrap(), + }, + debuggers: settings.debuggers.unwrap(), + } + } + + let default_language_settings = load_from_content(all_languages.defaults.clone()); let mut languages = HashMap::default(); for (language_name, settings) in &all_languages.languages.0 { - let mut language_settings = default_language_settings.clone(); - merge_settings(&mut language_settings, settings); - languages.insert(LanguageName(language_name.clone()), language_settings); + let mut language_settings = all_languages.defaults.clone(); + settings::merge_from::MergeFrom::merge_from(&mut language_settings, Some(settings)); + languages.insert( + LanguageName(language_name.clone()), + load_from_content(language_settings), + ); } let edit_prediction_provider = all_languages @@ -688,17 +614,15 @@ impl settings::Settings for AllLanguageSettings { let enabled_in_text_threads = edit_predictions.enabled_in_text_threads.unwrap(); let mut file_types: FxHashMap, GlobSet> = FxHashMap::default(); - let mut file_globs: FxHashMap, Vec> = FxHashMap::default(); for (language, patterns) in &all_languages.file_types { let mut builder = GlobSetBuilder::new(); - for pattern in patterns { + for pattern in &patterns.0 { builder.add(Glob::new(pattern).unwrap()); } file_types.insert(language.clone(), builder.build().unwrap()); - file_globs.insert(language.clone(), patterns.clone()); } Self { @@ -725,110 +649,6 @@ impl settings::Settings for AllLanguageSettings { defaults: default_language_settings, languages, file_types, - file_globs, - } - } - - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let all_languages = &content.project.all_languages; - if let Some(provider) = all_languages - .features - .as_ref() - .and_then(|f| f.edit_prediction_provider) - { - self.edit_predictions.provider = provider; - } - - if let Some(edit_predictions) = all_languages.edit_predictions.as_ref() { - self.edit_predictions - .mode - .merge_from(&edit_predictions.mode); - self.edit_predictions - .enabled_in_text_threads - .merge_from(&edit_predictions.enabled_in_text_threads); - - if let Some(disabled_globs) = edit_predictions.disabled_globs.as_ref() { - self.edit_predictions - .disabled_globs - .extend(disabled_globs.iter().filter_map(|g| { - let expanded_g = shellexpand::tilde(g).into_owned(); - Some(DisabledGlob { - matcher: globset::Glob::new(&expanded_g).ok()?.compile_matcher(), - is_absolute: Path::new(&expanded_g).is_absolute(), - }) - })); - } - } - - if let Some(proxy) = all_languages - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.as_ref()?.proxy.clone()) - { - self.edit_predictions.copilot.proxy = Some(proxy); - } - - if let Some(proxy_no_verify) = all_languages - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.as_ref()?.proxy_no_verify) - { - self.edit_predictions.copilot.proxy_no_verify = Some(proxy_no_verify); - } - - if let Some(enterprise_uri) = all_languages - .edit_predictions - .as_ref() - .and_then(|settings| settings.copilot.as_ref()?.enterprise_uri.clone()) - { - self.edit_predictions.copilot.enterprise_uri = Some(enterprise_uri); - } - - // A user's global settings override the default global settings and - // all default language-specific settings. - merge_settings(&mut self.defaults, &all_languages.defaults); - for language_settings in self.languages.values_mut() { - merge_settings(language_settings, &all_languages.defaults); - } - - // A user's language-specific settings override default language-specific settings. - for (language_name, user_language_settings) in &all_languages.languages.0 { - merge_settings( - self.languages - .entry(LanguageName(language_name.clone())) - .or_insert_with(|| self.defaults.clone()), - user_language_settings, - ); - } - - for (language, patterns) in &all_languages.file_types { - let mut builder = GlobSetBuilder::new(); - - let default_value = self.file_globs.get(&language.clone()); - - // Merge the default value with the user's value. - if let Some(patterns) = default_value { - for pattern in patterns { - if let Some(glob) = Glob::new(pattern).log_err() { - builder.add(glob); - } - } - } - - for pattern in patterns { - if let Some(glob) = Glob::new(pattern).log_err() { - builder.add(glob); - } - } - - self.file_globs - .entry(language.clone()) - .or_default() - .extend(patterns.clone()); - - if let Some(matcher) = builder.build().log_err() { - self.file_types.insert(language.clone(), matcher); - } } } @@ -917,14 +737,14 @@ impl settings::Settings for AllLanguageSettings { // TODO: pull ^ out into helper and reuse for per-language settings // vscodes file association map is inverted from ours, so we flip the mapping before merging - let mut associations: HashMap, Vec> = HashMap::default(); + let mut associations: HashMap, ExtendingVec> = HashMap::default(); if let Some(map) = vscode .read_value("files.associations") .and_then(|v| v.as_object()) { for (k, v) in map { let Some(v) = v.as_str() else { continue }; - associations.entry(v.into()).or_default().push(k.clone()); + associations.entry(v.into()).or_default().0.push(k.clone()); } } @@ -957,121 +777,7 @@ impl settings::Settings for AllLanguageSettings { } } -fn merge_settings(settings: &mut LanguageSettings, src: &LanguageSettingsContent) { - settings.tab_size.merge_from(&src.tab_size); - settings.tab_size = settings - .tab_size - .clamp(NonZeroU32::new(1).unwrap(), NonZeroU32::new(16).unwrap()); - - settings.hard_tabs.merge_from(&src.hard_tabs); - settings.soft_wrap.merge_from(&src.soft_wrap); - settings.use_autoclose.merge_from(&src.use_autoclose); - settings - .use_auto_surround - .merge_from(&src.use_auto_surround); - settings - .use_on_type_format - .merge_from(&src.use_on_type_format); - settings.auto_indent.merge_from(&src.auto_indent); - settings - .auto_indent_on_paste - .merge_from(&src.auto_indent_on_paste); - settings - .always_treat_brackets_as_autoclosed - .merge_from(&src.always_treat_brackets_as_autoclosed); - settings.show_wrap_guides.merge_from(&src.show_wrap_guides); - settings.wrap_guides.merge_from(&src.wrap_guides); - settings.indent_guides.merge_from(&src.indent_guides); - settings - .code_actions_on_format - .merge_from(&src.code_actions_on_format.clone()); - settings.linked_edits.merge_from(&src.linked_edits); - settings.tasks.merge_from(&src.tasks); - - settings - .preferred_line_length - .merge_from(&src.preferred_line_length); - settings.formatter.merge_from(&src.formatter.clone()); - settings.prettier.merge_from(&src.prettier.clone()); - settings - .jsx_tag_auto_close - .merge_from(&src.jsx_tag_auto_close.as_ref().and_then(|v| v.enabled)); - settings - .format_on_save - .merge_from(&src.format_on_save.clone()); - settings - .remove_trailing_whitespace_on_save - .merge_from(&src.remove_trailing_whitespace_on_save); - settings - .ensure_final_newline_on_save - .merge_from(&src.ensure_final_newline_on_save); - settings - .enable_language_server - .merge_from(&src.enable_language_server); - settings - .language_servers - .merge_from(&src.language_servers.clone()); - settings.allow_rewrap.merge_from(&src.allow_rewrap); - settings - .show_edit_predictions - .merge_from(&src.show_edit_predictions); - settings - .edit_predictions_disabled_in - .merge_from(&src.edit_predictions_disabled_in.clone()); - settings.show_whitespaces.merge_from(&src.show_whitespaces); - settings - .whitespace_map - .merge_from(&src.whitespace_map.clone()); - settings - .extend_comment_on_newline - .merge_from(&src.extend_comment_on_newline); - if let Some(inlay_hints) = &src.inlay_hints { - settings - .inlay_hints - .enabled - .merge_from(&inlay_hints.enabled); - settings - .inlay_hints - .show_value_hints - .merge_from(&inlay_hints.show_value_hints); - settings - .inlay_hints - .show_type_hints - .merge_from(&inlay_hints.show_type_hints); - settings - .inlay_hints - .show_parameter_hints - .merge_from(&inlay_hints.show_parameter_hints); - settings - .inlay_hints - .show_other_hints - .merge_from(&inlay_hints.show_other_hints); - settings - .inlay_hints - .show_background - .merge_from(&inlay_hints.show_background); - settings - .inlay_hints - .edit_debounce_ms - .merge_from(&inlay_hints.edit_debounce_ms); - settings - .inlay_hints - .scroll_debounce_ms - .merge_from(&inlay_hints.scroll_debounce_ms); - if let Some(toggle_on_modifiers_press) = &inlay_hints.toggle_on_modifiers_press { - settings.inlay_hints.toggle_on_modifiers_press = Some(*toggle_on_modifiers_press); - } - } - settings - .show_completions_on_input - .merge_from(&src.show_completions_on_input); - settings - .show_completion_documentation - .merge_from(&src.show_completion_documentation); - settings.completions.merge_from(&src.completions); -} - -#[derive(Default, Debug, Clone, PartialEq, Eq, SettingsUi)] +#[derive(Default, Debug, Clone, PartialEq, Eq)] pub struct JsxTagAutoCloseSettings { /// Enables or disables auto-closing of JSX tags. pub enabled: bool, diff --git a/crates/language_models/src/settings.rs b/crates/language_models/src/settings.rs index 30b208a170794a0d4018e8ebce39f1020b4c9b29..178703bd93d0d2cf0ece2e82ed26cedb49a38196 100644 --- a/crates/language_models/src/settings.rs +++ b/crates/language_models/src/settings.rs @@ -3,7 +3,6 @@ use std::sync::Arc; use collections::HashMap; use gpui::App; use settings::Settings; -use util::MergeFrom; use crate::provider::{ anthropic::AnthropicSettings, bedrock::AmazonBedrockSettings, cloud::ZedDotDevSettings, @@ -37,7 +36,7 @@ pub struct AllLanguageModelSettings { impl settings::Settings for AllLanguageModelSettings { const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]); - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let language_models = content.language_models.clone().unwrap(); let anthropic = language_models.anthropic.unwrap(); let bedrock = language_models.bedrock.unwrap(); @@ -118,113 +117,4 @@ impl settings::Settings for AllLanguageModelSettings { }, } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(models) = content.language_models.as_ref() else { - return; - }; - - if let Some(anthropic) = models.anthropic.as_ref() { - self.anthropic - .available_models - .merge_from(&anthropic.available_models); - self.anthropic.api_url.merge_from(&anthropic.api_url); - } - - if let Some(bedrock) = models.bedrock.clone() { - self.bedrock - .available_models - .merge_from(&bedrock.available_models); - - if let Some(endpoint_url) = bedrock.endpoint_url { - self.bedrock.endpoint = Some(endpoint_url) - } - - if let Some(region) = bedrock.region { - self.bedrock.region = Some(region) - } - - if let Some(profile_name) = bedrock.profile { - self.bedrock.profile_name = Some(profile_name); - } - - if let Some(auth_method) = bedrock.authentication_method { - self.bedrock.authentication_method = Some(auth_method.into()); - } - } - - if let Some(deepseek) = models.deepseek.as_ref() { - self.deepseek - .available_models - .merge_from(&deepseek.available_models); - self.deepseek.api_url.merge_from(&deepseek.api_url); - } - - if let Some(google) = models.google.as_ref() { - self.google - .available_models - .merge_from(&google.available_models); - self.google.api_url.merge_from(&google.api_url); - } - - if let Some(lmstudio) = models.lmstudio.as_ref() { - self.lmstudio - .available_models - .merge_from(&lmstudio.available_models); - self.lmstudio.api_url.merge_from(&lmstudio.api_url); - } - - if let Some(mistral) = models.mistral.as_ref() { - self.mistral - .available_models - .merge_from(&mistral.available_models); - self.mistral.api_url.merge_from(&mistral.api_url); - } - if let Some(ollama) = models.ollama.as_ref() { - self.ollama - .available_models - .merge_from(&ollama.available_models); - self.ollama.api_url.merge_from(&ollama.api_url); - } - if let Some(open_router) = models.open_router.as_ref() { - self.open_router - .available_models - .merge_from(&open_router.available_models); - self.open_router.api_url.merge_from(&open_router.api_url); - } - if let Some(openai) = models.openai.as_ref() { - self.openai - .available_models - .merge_from(&openai.available_models); - self.openai.api_url.merge_from(&openai.api_url); - } - if let Some(openai_compatible) = models.openai_compatible.clone() { - for (name, value) in openai_compatible { - self.openai_compatible.insert( - name, - OpenAiCompatibleSettings { - api_url: value.api_url, - available_models: value.available_models, - }, - ); - } - } - if let Some(vercel) = models.vercel.as_ref() { - self.vercel - .available_models - .merge_from(&vercel.available_models); - self.vercel.api_url.merge_from(&vercel.api_url); - } - if let Some(x_ai) = models.x_ai.as_ref() { - self.x_ai - .available_models - .merge_from(&x_ai.available_models); - self.x_ai.api_url.merge_from(&x_ai.api_url); - } - if let Some(zed_dot_dev) = models.zed_dot_dev.as_ref() { - self.zed_dot_dev - .available_models - .merge_from(&zed_dot_dev.available_models); - } - } } diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index f08c548ddcb10a311e1d5b29a9bf50a7b5bc4fb1..efcbaa57ef88b36820150d9634eea6b27d0969cf 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -74,6 +74,7 @@ snippet_provider.workspace = true url.workspace = true task.workspace = true tempfile.workspace = true +theme.workspace = true toml.workspace = true tree-sitter = { workspace = true, optional = true } tree-sitter-bash = { workspace = true, optional = true } diff --git a/crates/languages/src/json.rs b/crates/languages/src/json.rs index df4a0d114611a2bab7a49417bb0a0bac79eaf734..496b0389e6e331f5c1d694d3ad30b5abffbee106 100644 --- a/crates/languages/src/json.rs +++ b/crates/languages/src/json.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use collections::HashMap; use dap::DapRegistry; use futures::StreamExt; -use gpui::{App, AsyncApp, Task}; +use gpui::{App, AsyncApp, SharedString, Task}; use http_client::github::{GitHubLspBinaryVersion, latest_github_release}; use language::{ ContextProvider, LanguageName, LanguageRegistry, LocalFile as _, LspAdapter, @@ -29,6 +29,7 @@ use std::{ sync::Arc, }; use task::{AdapterSchemas, TaskTemplate, TaskTemplates, VariableName}; +use theme::ThemeRegistry; use util::{ResultExt, archive::extract_zip, fs::remove_matching, maybe, merge_json_value_into}; use crate::PackageJsonData; @@ -156,13 +157,20 @@ impl JsonLspAdapter { ) -> Value { let keymap_schema = KeymapFile::generate_json_schema_for_registered_actions(cx); let font_names = &cx.text_system().all_font_names(); - let settings_schema = cx.global::().json_schema( - &SettingsJsonSchemaParams { + let theme_names = &ThemeRegistry::global(cx).list_names(); + let icon_theme_names = &ThemeRegistry::global(cx) + .list_icon_themes() + .into_iter() + .map(|icon_theme| icon_theme.name) + .collect::>(); + let settings_schema = cx + .global::() + .json_schema(&SettingsJsonSchemaParams { language_names: &language_names, font_names, - }, - cx, - ); + theme_names, + icon_theme_names, + }); let tasks_schema = task::TaskTemplates::generate_json_schema(); let debug_schema = task::DebugTaskFile::generate_json_schema(&adapter_schemas); diff --git a/crates/onboarding/src/ai_setup_page.rs b/crates/onboarding/src/ai_setup_page.rs index 6ed5e5e240201752938ea7e589c9f98b674b919c..64c97863c0ee720f3f9ddd1d466b553b043086e0 100644 --- a/crates/onboarding/src/ai_setup_page.rs +++ b/crates/onboarding/src/ai_setup_page.rs @@ -265,7 +265,7 @@ pub(crate) fn render_ai_setup_page( let fs = ::global(cx); update_settings_file(fs, cx, move |settings, _| { - settings.disable_ai = Some(enabled); + settings.disable_ai = Some(enabled.into()); }); }, ) diff --git a/crates/outline_panel/src/outline_panel_settings.rs b/crates/outline_panel/src/outline_panel_settings.rs index 7652c633f1e1166f2515e0338cba3323d6017fb3..7e09e21c2311780ec23f24b9616270c4a1f24854 100644 --- a/crates/outline_panel/src/outline_panel_settings.rs +++ b/crates/outline_panel/src/outline_panel_settings.rs @@ -2,7 +2,6 @@ use editor::EditorSettings; use gpui::{App, Pixels}; pub use settings::{DockSide, Settings, ShowIndentGuides}; use ui::scrollbars::{ScrollbarVisibility, ShowScrollbar}; -use util::MergeFrom; #[derive(Debug, Clone, Copy, PartialEq)] pub struct OutlinePanelSettings { @@ -42,7 +41,7 @@ impl ScrollbarVisibility for OutlinePanelSettings { } impl Settings for OutlinePanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let panel = content.outline_panel.as_ref().unwrap(); Self { button: panel.button.unwrap(), @@ -58,40 +57,12 @@ impl Settings for OutlinePanelSettings { auto_reveal_entries: panel.auto_reveal_entries.unwrap(), auto_fold_dirs: panel.auto_fold_dirs.unwrap(), scrollbar: ScrollbarSettings { - show: panel.scrollbar.unwrap().show.flatten().map(Into::into), + show: panel.scrollbar.unwrap().show.map(Into::into), }, expand_outlines_with_depth: panel.expand_outlines_with_depth.unwrap(), } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(panel) = content.outline_panel.as_ref() else { - return; - }; - - self.button.merge_from(&panel.button); - self.default_width - .merge_from(&panel.default_width.map(Pixels::from)); - self.dock.merge_from(&panel.dock); - self.file_icons.merge_from(&panel.file_icons); - self.folder_icons.merge_from(&panel.folder_icons); - self.git_status.merge_from(&panel.git_status); - self.indent_size.merge_from(&panel.indent_size); - - if let Some(indent_guides) = panel.indent_guides.as_ref() { - self.indent_guides.show.merge_from(&indent_guides.show); - } - - self.auto_reveal_entries - .merge_from(&panel.auto_reveal_entries); - self.auto_fold_dirs.merge_from(&panel.auto_fold_dirs); - - if let Some(scrollbar) = panel.scrollbar.as_ref() - && let Some(show) = scrollbar.show.flatten() - { - self.scrollbar.show = Some(show.into()) - } - } fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/project/src/agent_server_store.rs b/crates/project/src/agent_server_store.rs index b7d1a75521480353433cf4953960b505201ab3c1..3d270bcb0db13e9c616687ce5d40cbc48bd4cbb9 100644 --- a/crates/project/src/agent_server_store.rs +++ b/crates/project/src/agent_server_store.rs @@ -22,7 +22,7 @@ use rpc::{ }; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use settings::{SettingsContent, SettingsKey, SettingsStore, SettingsUi}; +use settings::{SettingsContent, SettingsStore}; use util::{ResultExt as _, debug_panic}; use crate::ProjectEnvironment; @@ -989,8 +989,7 @@ impl ExternalAgentServer for LocalCustomAgent { pub const GEMINI_NAME: &'static str = "gemini"; pub const CLAUDE_CODE_NAME: &'static str = "claude"; -#[derive(Default, Clone, JsonSchema, Debug, SettingsUi, SettingsKey, PartialEq)] -#[settings_key(key = "agent_servers")] +#[derive(Default, Clone, JsonSchema, Debug, PartialEq)] pub struct AllAgentServersSettings { pub gemini: Option, pub claude: Option, @@ -1063,7 +1062,7 @@ impl From for CustomAgentServerSettings { } impl settings::Settings for AllAgentServersSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let agent_settings = content.agent_servers.clone().unwrap(); Self { gemini: agent_settings.gemini.map(Into::into), @@ -1076,20 +1075,5 @@ impl settings::Settings for AllAgentServersSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(content) = &content.agent_servers else { - return; - }; - if let Some(gemini) = content.gemini.clone() { - self.gemini = Some(gemini.into()) - }; - if let Some(claude) = content.claude.clone() { - self.claude = Some(claude.into()); - } - for (name, config) in content.custom.clone() { - self.custom.insert(name, config.into()); - } - } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 571a7b5db48eaf3660aadf268d61bf1864140066..27fb02f00bd383c0cd55c68cfcd19605d5ab33ae 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -971,23 +971,18 @@ pub enum PulledDiagnostics { /// Whether to disable all AI features in Zed. /// /// Default: false -#[derive(Copy, Clone, Debug, settings::SettingsUi)] +#[derive(Copy, Clone, Debug)] pub struct DisableAiSettings { pub disable_ai: bool, } impl settings::Settings for DisableAiSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { Self { - disable_ai: content.disable_ai.unwrap(), + disable_ai: content.disable_ai.unwrap().0, } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - // If disable_ai is true *in any file*, it is disabled. - self.disable_ai = self.disable_ai || content.disable_ai.unwrap_or(false); - } - fn import_from_vscode( _vscode: &settings::VsCodeSettings, _current: &mut settings::SettingsContent, diff --git a/crates/project/src/project_settings.rs b/crates/project/src/project_settings.rs index 097224e02cd9c1cbbb51ccc6d3d373d3b5a71f85..c95c20a3352bb067e492874f6f650d38f04671b2 100644 --- a/crates/project/src/project_settings.rs +++ b/crates/project/src/project_settings.rs @@ -21,7 +21,7 @@ pub use settings::DirenvSettings; pub use settings::LspSettings; use settings::{ DapSettingsContent, InvalidSettingsError, LocalSettingsKind, Settings, SettingsLocation, - SettingsStore, SettingsUi, parse_json_with_comments, watch_config_file, + SettingsStore, parse_json_with_comments, watch_config_file, }; use std::{ path::{Path, PathBuf}, @@ -29,7 +29,7 @@ use std::{ time::Duration, }; use task::{DebugTaskFile, TaskTemplates, VsCodeDebugTaskFile, VsCodeTaskFile}; -use util::{MergeFrom as _, ResultExt, serde::default_true}; +use util::{ResultExt, serde::default_true}; use worktree::{PathChange, UpdatedEntriesSet, Worktree, WorktreeId}; use crate::{ @@ -189,20 +189,7 @@ impl ContextServerSettings { } } -#[derive( - Clone, - Copy, - Debug, - Eq, - PartialEq, - Ord, - PartialOrd, - Serialize, - Deserialize, - JsonSchema, - SettingsUi, -)] -#[serde(rename_all = "snake_case")] +#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum DiagnosticSeverity { // No diagnostics are shown. Off, @@ -444,7 +431,7 @@ pub struct LspPullDiagnosticsSettings { } impl Settings for ProjectSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let project = &content.project.clone(); let diagnostics = content.diagnostics.as_ref().unwrap(); let lsp_pull_diagnostics = diagnostics.lsp_pull_diagnostics.as_ref().unwrap(); @@ -523,118 +510,6 @@ impl Settings for ProjectSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let project = &content.project; - self.context_servers.extend( - project - .context_servers - .clone() - .into_iter() - .map(|(key, value)| (key, value.into())), - ); - self.dap.extend( - project - .dap - .clone() - .into_iter() - .map(|(key, value)| (DebugAdapterName(key.into()), DapSettings::from(value))), - ); - if let Some(diagnostics) = content.diagnostics.as_ref() { - if let Some(inline) = &diagnostics.inline { - self.diagnostics.inline.enabled.merge_from(&inline.enabled); - self.diagnostics - .inline - .update_debounce_ms - .merge_from(&inline.update_debounce_ms); - self.diagnostics.inline.padding.merge_from(&inline.padding); - self.diagnostics - .inline - .min_column - .merge_from(&inline.min_column); - if let Some(max_severity) = inline.max_severity { - self.diagnostics.inline.max_severity = Some(max_severity.into()) - } - } - - self.diagnostics.button.merge_from(&diagnostics.button); - self.diagnostics - .include_warnings - .merge_from(&diagnostics.include_warnings); - if let Some(pull_diagnostics) = &diagnostics.lsp_pull_diagnostics { - self.diagnostics - .lsp_pull_diagnostics - .enabled - .merge_from(&pull_diagnostics.enabled); - self.diagnostics - .lsp_pull_diagnostics - .debounce_ms - .merge_from(&pull_diagnostics.debounce_ms); - } - } - if let Some(git) = content.git.as_ref() { - if let Some(branch_picker) = git.branch_picker.as_ref() { - self.git - .branch_picker - .show_author_name - .merge_from(&branch_picker.show_author_name); - } - if let Some(inline_blame) = git.inline_blame.as_ref() { - self.git - .inline_blame - .enabled - .merge_from(&inline_blame.enabled); - self.git - .inline_blame - .delay_ms - .merge_from(&inline_blame.delay_ms.map(std::time::Duration::from_millis)); - self.git - .inline_blame - .padding - .merge_from(&inline_blame.padding); - self.git - .inline_blame - .min_column - .merge_from(&inline_blame.min_column); - self.git - .inline_blame - .show_commit_summary - .merge_from(&inline_blame.show_commit_summary); - } - self.git.git_gutter.merge_from(&git.git_gutter); - self.git.hunk_style.merge_from(&git.hunk_style); - if let Some(debounce) = git.gutter_debounce { - self.git.gutter_debounce = Some(debounce); - } - } - self.global_lsp_settings.button.merge_from( - &content - .global_lsp_settings - .as_ref() - .and_then(|settings| settings.button), - ); - self.load_direnv - .merge_from(&content.project.load_direnv.clone()); - - for (key, value) in content.project.lsp.clone() { - self.lsp.insert(LanguageServerName(key.into()), value); - } - - if let Some(node) = content.node.as_ref() { - self.node - .ignore_system_version - .merge_from(&node.ignore_system_version); - if let Some(path) = node.path.clone() { - self.node.path = Some(path); - } - if let Some(npm_path) = node.npm_path.clone() { - self.node.npm_path = Some(npm_path); - } - } - self.session - .restore_unsaved_buffers - .merge_from(&content.session.and_then(|s| s.restore_unsaved_buffers)); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/project_panel/src/project_panel_settings.rs b/crates/project_panel/src/project_panel_settings.rs index b2646477697118e14abbbe6647a284d5a19a8866..0f5f2e5b53a7a856b9e6bff136a1b1cc5af3e293 100644 --- a/crates/project_panel/src/project_panel_settings.rs +++ b/crates/project_panel/src/project_panel_settings.rs @@ -10,7 +10,6 @@ use ui::{ px, scrollbars::{ScrollbarVisibility, ShowScrollbar}, }; -use util::MergeFrom; #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] pub struct ProjectPanelSettings { @@ -56,7 +55,7 @@ impl ScrollbarVisibility for ProjectPanelSettings { } impl Settings for ProjectPanelSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut ui::App) -> Self { let project_panel = content.project_panel.clone().unwrap(); Self { button: project_panel.button.unwrap(), @@ -76,12 +75,7 @@ impl Settings for ProjectPanelSettings { auto_fold_dirs: project_panel.auto_fold_dirs.unwrap(), starts_open: project_panel.starts_open.unwrap(), scrollbar: ScrollbarSettings { - show: project_panel - .scrollbar - .unwrap() - .show - .flatten() - .map(Into::into), + show: project_panel.scrollbar.unwrap().show.map(Into::into), }, show_diagnostics: project_panel.show_diagnostics.unwrap(), hide_root: project_panel.hide_root.unwrap(), @@ -89,47 +83,6 @@ impl Settings for ProjectPanelSettings { } } - fn refine(&mut self, content: &SettingsContent, _cx: &mut ui::App) { - let Some(project_panel) = content.project_panel.as_ref() else { - return; - }; - self.button.merge_from(&project_panel.button); - self.hide_gitignore - .merge_from(&project_panel.hide_gitignore); - self.default_width - .merge_from(&project_panel.default_width.map(px)); - self.dock.merge_from(&project_panel.dock); - self.entry_spacing.merge_from(&project_panel.entry_spacing); - self.file_icons.merge_from(&project_panel.file_icons); - self.folder_icons.merge_from(&project_panel.folder_icons); - self.git_status.merge_from(&project_panel.git_status); - self.indent_size.merge_from(&project_panel.indent_size); - self.sticky_scroll.merge_from(&project_panel.sticky_scroll); - self.auto_reveal_entries - .merge_from(&project_panel.auto_reveal_entries); - self.auto_fold_dirs - .merge_from(&project_panel.auto_fold_dirs); - self.starts_open.merge_from(&project_panel.starts_open); - self.show_diagnostics - .merge_from(&project_panel.show_diagnostics); - self.hide_root.merge_from(&project_panel.hide_root); - self.drag_and_drop.merge_from(&project_panel.drag_and_drop); - if let Some(show) = project_panel - .indent_guides - .as_ref() - .and_then(|indent| indent.show) - { - self.indent_guides.show = show; - } - if let Some(show) = project_panel - .scrollbar - .as_ref() - .and_then(|scrollbar| scrollbar.show) - { - self.scrollbar.show = show.map(Into::into) - } - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { if let Some(hide_gitignore) = vscode.read_bool("explorer.excludeGitIgnore") { current.project_panel.get_or_insert_default().hide_gitignore = Some(hide_gitignore); diff --git a/crates/recent_projects/src/remote_connections.rs b/crates/recent_projects/src/remote_connections.rs index d3888c9878840d78f43f77e8437311a237822a81..fba7b122dd7842edfcb5f66054040e378acbfc70 100644 --- a/crates/recent_projects/src/remote_connections.rs +++ b/crates/recent_projects/src/remote_connections.rs @@ -19,29 +19,28 @@ use remote::{ SshConnectionOptions, }; pub use settings::SshConnection; -use settings::{Settings, WslConnection}; +use settings::{ExtendingVec, Settings, WslConnection}; use theme::ThemeSettings; use ui::{ ActiveTheme, Color, CommonAnimationExt, Context, Icon, IconName, IconSize, InteractiveElement, IntoElement, Label, LabelCommon, Styled, Window, prelude::*, }; -use util::MergeFrom; use workspace::{AppState, ModalView, Workspace}; pub struct SshSettings { - pub ssh_connections: Vec, - pub wsl_connections: Vec, + pub ssh_connections: ExtendingVec, + pub wsl_connections: ExtendingVec, /// Whether to read ~/.ssh/config for ssh connection sources. pub read_ssh_config: bool, } impl SshSettings { pub fn ssh_connections(&self) -> impl Iterator + use<> { - self.ssh_connections.clone().into_iter() + self.ssh_connections.clone().0.into_iter() } pub fn wsl_connections(&self) -> impl Iterator + use<> { - self.wsl_connections.clone().into_iter() + self.wsl_connections.clone().0.into_iter() } pub fn fill_connection_options_from_settings(&self, options: &mut SshConnectionOptions) { @@ -104,25 +103,14 @@ impl From for Connection { } impl Settings for SshSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let remote = &content.remote; Self { - ssh_connections: remote.ssh_connections.clone().unwrap_or_default(), - wsl_connections: remote.wsl_connections.clone().unwrap_or_default(), + ssh_connections: remote.ssh_connections.clone().unwrap_or_default().into(), + wsl_connections: remote.wsl_connections.clone().unwrap_or_default().into(), read_ssh_config: remote.read_ssh_config.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - if let Some(ssh_connections) = content.remote.ssh_connections.clone() { - self.ssh_connections.extend(ssh_connections) - } - if let Some(wsl_connections) = content.remote.wsl_connections.clone() { - self.wsl_connections.extend(wsl_connections) - } - self.read_ssh_config - .merge_from(&content.remote.read_ssh_config); - } } pub struct RemoteConnectionPrompt { diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 7b19c887b0c36490a83ef7da35f7ecbd533fcb16..989a054fcad948c3acdea2ffcd462eee9bf2fcc6 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -1885,7 +1885,7 @@ impl RemoteServerProjects { let ssh_settings = SshSettings::get_global(cx); let mut should_rebuild = false; - let ssh_connections_changed = ssh_settings.ssh_connections.iter().ne(state + let ssh_connections_changed = ssh_settings.ssh_connections.0.iter().ne(state .servers .iter() .filter_map(|server| match server { @@ -1896,7 +1896,7 @@ impl RemoteServerProjects { _ => None, })); - let wsl_connections_changed = ssh_settings.wsl_connections.iter().ne(state + let wsl_connections_changed = ssh_settings.wsl_connections.0.iter().ne(state .servers .iter() .filter_map(|server| match server { diff --git a/crates/repl/src/jupyter_settings.rs b/crates/repl/src/jupyter_settings.rs index 8a468e9ec65feb8431222bd6b9a33c8bbd9efaf3..830e6032147cbc96bb46b240651167402db427f1 100644 --- a/crates/repl/src/jupyter_settings.rs +++ b/crates/repl/src/jupyter_settings.rs @@ -19,19 +19,10 @@ impl JupyterSettings { } impl Settings for JupyterSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let jupyter = content.editor.jupyter.clone().unwrap(); Self { kernel_selections: jupyter.kernel_selections.unwrap_or_default(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(jupyter) = content.editor.jupyter.as_ref() else { - return; - }; - if let Some(kernel_selections) = jupyter.kernel_selections.clone() { - self.kernel_selections.extend(kernel_selections) - } - } } diff --git a/crates/repl/src/repl_settings.rs b/crates/repl/src/repl_settings.rs index 1c9ceeecfc19bec40243193e9d4ea21a2e266e45..ee18c89d67a0f1da55acaee89cfaa0b03dc80f87 100644 --- a/crates/repl/src/repl_settings.rs +++ b/crates/repl/src/repl_settings.rs @@ -1,6 +1,5 @@ use gpui::App; use settings::Settings; -use util::MergeFrom; /// Settings for configuring REPL display and behavior. #[derive(Clone, Debug)] @@ -18,7 +17,7 @@ pub struct ReplSettings { } impl Settings for ReplSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let repl = content.repl.as_ref().unwrap(); Self { @@ -26,13 +25,4 @@ impl Settings for ReplSettings { max_columns: repl.max_columns.unwrap(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(repl) = content.repl.as_ref() else { - return; - }; - - self.max_columns.merge_from(&repl.max_columns); - self.max_lines.merge_from(&repl.max_lines); - } } diff --git a/crates/settings/Cargo.toml b/crates/settings/Cargo.toml index 062af3d23926b5bfdc0e1f7239859b6bbfd7efdd..4790df4d95f676b7e7170e86b6d05976cbd3c69a 100644 --- a/crates/settings/Cargo.toml +++ b/crates/settings/Cargo.toml @@ -30,7 +30,7 @@ rust-embed.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true -settings_ui_macros.workspace = true +settings_macros = { path = "../settings_macros" } serde_json_lenient.workspace = true serde_repr.workspace = true serde_path_to_error.workspace = true diff --git a/crates/settings/src/base_keymap_setting.rs b/crates/settings/src/base_keymap_setting.rs index 858a3e758ff7d0aa6ec045197316f027913b49d6..1b41dc0d4f7db6a907de9586d5c4ceb796d00165 100644 --- a/crates/settings/src/base_keymap_setting.rs +++ b/crates/settings/src/base_keymap_setting.rs @@ -2,21 +2,17 @@ use std::fmt::{Display, Formatter}; use crate::{ self as settings, - settings_content::{self, BaseKeymapContent, SettingsContent}, + settings_content::{BaseKeymapContent, SettingsContent}, }; use gpui::App; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use serde_with::skip_serializing_none; use settings::{Settings, VsCodeSettings}; -use settings_ui_macros::{SettingsKey, 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, SettingsUi, -)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)] pub enum BaseKeymap { #[default] VSCode, @@ -134,37 +130,11 @@ impl BaseKeymap { } } -#[derive( - 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)] -#[skip_serializing_none] -pub struct BaseKeymapSetting { - pub base_keymap: Option, -} - impl Settings for BaseKeymap { - fn from_defaults(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(s: &crate::settings_content::SettingsContent, _cx: &mut App) -> Self { s.base_keymap.unwrap().into() } - fn refine(&mut self, s: &settings_content::SettingsContent, _cx: &mut App) { - if let Some(base_keymap) = s.base_keymap { - *self = base_keymap.into(); - }; - } - fn import_from_vscode(_vscode: &VsCodeSettings, current: &mut SettingsContent) { current.base_keymap = Some(BaseKeymapContent::VSCode); } diff --git a/crates/settings/src/merge_from.rs b/crates/settings/src/merge_from.rs new file mode 100644 index 0000000000000000000000000000000000000000..11c0785bcb466e26de956475fb5bd4f9821c2790 --- /dev/null +++ b/crates/settings/src/merge_from.rs @@ -0,0 +1,164 @@ +use std::rc::Rc; + +/// Trait for recursively merging settings structures. +/// +/// This trait allows settings objects to be merged from optional sources, +/// where `None` values are ignored and `Some` values override existing values. +/// +/// HashMaps, structs and similar types are merged by combining their contents key-wise, +/// but all other types (including Vecs) are last-write-wins. +/// (Though see also ExtendingVec and SaturatingBool) +#[allow(unused)] +pub trait MergeFrom { + /// Merge from an optional source of the same type. + /// If `other` is `None`, no changes are made. + /// If `other` is `Some(value)`, fields from `value` are merged into `self`. + fn merge_from(&mut self, other: Option<&Self>); +} + +macro_rules! merge_from_overwrites { + ($($type:ty),+) => { + $( + impl MergeFrom for $type { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(value) = other { + *self = value.clone(); + } + } + } + )+ + } +} + +merge_from_overwrites!( + u16, + u32, + u64, + usize, + i16, + i32, + i64, + bool, + f64, + f32, + std::num::NonZeroUsize, + std::num::NonZeroU32, + String, + std::sync::Arc, + gpui::SharedString, + std::path::PathBuf, + gpui::Modifiers, + gpui::FontFeatures +); + +impl MergeFrom for Vec { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(other) = other { + *self = other.clone() + } + } +} + +// Implementations for collections that extend/merge their contents +impl MergeFrom for collections::HashMap +where + K: Clone + std::hash::Hash + Eq, + V: Clone + MergeFrom, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for (k, v) in other { + if let Some(existing) = self.get_mut(k) { + existing.merge_from(Some(v)); + } else { + self.insert(k.clone(), v.clone()); + } + } + } +} + +impl MergeFrom for collections::BTreeMap +where + K: Clone + std::hash::Hash + Eq + Ord, + V: Clone + MergeFrom, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for (k, v) in other { + if let Some(existing) = self.get_mut(k) { + existing.merge_from(Some(v)); + } else { + self.insert(k.clone(), v.clone()); + } + } + } +} + +impl MergeFrom for collections::IndexMap +where + K: std::hash::Hash + Eq + Clone, + // Q: ?Sized + std::hash::Hash + collections::Equivalent + Eq, + V: Clone + MergeFrom, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for (k, v) in other { + if let Some(existing) = self.get_mut(k) { + existing.merge_from(Some(v)); + } else { + self.insert(k.clone(), v.clone()); + } + } + } +} + +impl MergeFrom for collections::BTreeSet +where + T: Clone + Ord, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for item in other { + self.insert(item.clone()); + } + } +} + +impl MergeFrom for collections::HashSet +where + T: Clone + std::hash::Hash + Eq, +{ + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + for item in other { + self.insert(item.clone()); + } + } +} + +impl MergeFrom for serde_json::Value { + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + match (self, other) { + (serde_json::Value::Object(this), serde_json::Value::Object(other)) => { + for (k, v) in other { + if let Some(existing) = this.get_mut(k) { + existing.merge_from(other.get(k)); + } else { + this.insert(k.clone(), v.clone()); + } + } + } + (this, other) => *this = other.clone(), + } + } +} + +impl MergeFrom for Rc { + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + let mut this: T = self.as_ref().clone(); + this.merge_from(Some(other.as_ref())); + *self = Rc::new(this) + } +} diff --git a/crates/settings/src/settings.rs b/crates/settings/src/settings.rs index bb242aaac28e2910d72982eeb7e92c0ea8f6c896..4b53b8b6ea49bc45a059776e3f863d2dd2961651 100644 --- a/crates/settings/src/settings.rs +++ b/crates/settings/src/settings.rs @@ -1,6 +1,7 @@ mod base_keymap_setting; mod editable_setting_control; mod keymap_file; +pub mod merge_from; mod settings_content; mod settings_file; mod settings_json; @@ -27,8 +28,7 @@ pub use settings_store::{ InvalidSettingsError, LocalSettingsKind, Settings, SettingsKey, SettingsLocation, SettingsStore, }; pub use settings_ui_core::*; -// Re-export the derive macro -pub use settings_ui_macros::{SettingsKey, SettingsUi}; + pub use vscode_import::{VsCodeSettings, VsCodeSettingsSource}; #[derive(Clone, Debug, PartialEq)] diff --git a/crates/settings/src/settings_content.rs b/crates/settings/src/settings_content.rs index 542cc6c57b08389f85c71ab0ef8dfcdd1f78c189..2ef42d8ebd730343f749d3e2e48055a2d02819ad 100644 --- a/crates/settings/src/settings_content.rs +++ b/crates/settings/src/settings_content.rs @@ -22,15 +22,16 @@ use release_channel::ReleaseChannel; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::collections::BTreeSet; use std::env; use std::sync::Arc; pub use util::serde::default_true; -use crate::ActiveSettingsProfileName; +use crate::{ActiveSettingsProfileName, merge_from}; #[skip_serializing_none] -#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Default, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct SettingsContent { #[serde(flatten)] pub project: ProjectSettingsContent, @@ -153,7 +154,7 @@ pub struct SettingsContent { /// Whether to disable all AI features in Zed. /// /// Default: false - pub disable_ai: Option, + pub disable_ai: Option, /// Settings related to Vim mode in Zed. pub vim: Option, @@ -166,14 +167,14 @@ impl SettingsContent { } #[skip_serializing_none] -#[derive(Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ServerSettingsContent { #[serde(flatten)] pub project: ProjectSettingsContent, } #[skip_serializing_none] -#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Default, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct UserSettingsContent { #[serde(flatten)] pub content: Box, @@ -225,7 +226,9 @@ impl UserSettingsContent { /// 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, MergeFrom, PartialEq, Eq, Default, +)] pub enum BaseKeymapContent { #[default] VSCode, @@ -239,7 +242,7 @@ pub enum BaseKeymapContent { } #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct TitleBarSettingsContent { /// Controls when the title bar is visible: "always" | "never" | "hide_in_full_screen". /// @@ -275,7 +278,7 @@ pub struct TitleBarSettingsContent { pub show_menus: Option, } -#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] #[serde(rename_all = "snake_case")] pub enum TitleBarVisibility { Always, @@ -285,7 +288,7 @@ pub enum TitleBarVisibility { /// Configuration of audio in Zed. #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct AudioSettingsContent { /// Opt into the new audio system. #[serde(rename = "experimental.rodio_audio", default)] @@ -307,7 +310,7 @@ pub struct AudioSettingsContent { /// Control what info is collected by Zed. #[skip_serializing_none] -#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Debug, MergeFrom)] pub struct TelemetrySettingsContent { /// Send debug info like crash reports. /// @@ -320,7 +323,7 @@ pub struct TelemetrySettingsContent { } #[skip_serializing_none] -#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone)] +#[derive(Default, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Clone, MergeFrom)] pub struct DebuggerSettingsContent { /// Determines the stepping granularity. /// @@ -353,7 +356,9 @@ pub struct DebuggerSettingsContent { } /// The granularity of one 'step' in the stepping requests `next`, `stepIn`, `stepOut`, and `stepBack`. -#[derive(PartialEq, Eq, Debug, Hash, Clone, Copy, Deserialize, Serialize, JsonSchema)] +#[derive( + PartialEq, Eq, Debug, Hash, Clone, Copy, Deserialize, Serialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum SteppingGranularity { /// The step should allow the program to run until the current statement has finished executing. @@ -366,7 +371,7 @@ pub enum SteppingGranularity { Instruction, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum DockPosition { Left, @@ -376,7 +381,7 @@ pub enum DockPosition { /// Settings for slash commands. #[skip_serializing_none] -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct SlashCommandSettings { /// Settings for the `/cargo-workspace` slash command. pub cargo_workspace: Option, @@ -384,7 +389,7 @@ pub struct SlashCommandSettings { /// Settings for the `/cargo-workspace` slash command. #[skip_serializing_none] -#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct CargoWorkspaceCommandSettings { /// Whether `/cargo-workspace` is enabled. pub enabled: Option, @@ -392,7 +397,7 @@ pub struct CargoWorkspaceCommandSettings { /// Configuration of voice calls in Zed. #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct CallSettingsContent { /// Whether the microphone should be muted when joining a channel or a call. /// @@ -406,7 +411,7 @@ pub struct CallSettingsContent { } #[skip_serializing_none] -#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone, JsonSchema)] +#[derive(Deserialize, Serialize, PartialEq, Debug, Default, Clone, JsonSchema, MergeFrom)] pub struct ExtensionSettingsContent { /// The extensions that should be automatically installed by Zed. /// @@ -421,7 +426,7 @@ pub struct ExtensionSettingsContent { } #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct GitPanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -462,7 +467,9 @@ pub struct GitPanelSettingsContent { pub collapse_untracked_diff: Option, } -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Default, Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum StatusStyle { #[default] @@ -471,13 +478,13 @@ pub enum StatusStyle { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ScrollbarSettings { pub show: Option, } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct NotificationPanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -494,7 +501,7 @@ pub struct NotificationPanelSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct PanelSettingsContent { /// Whether to show the panel button in the status bar. /// @@ -511,7 +518,7 @@ pub struct PanelSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct MessageEditorSettings { /// Whether to automatically replace emoji shortcodes with emoji characters. /// For example: typing `:wave:` gets replaced with `👋`. @@ -521,7 +528,7 @@ pub struct MessageEditorSettings { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct FileFinderSettingsContent { /// Whether to show file icons in the file finder. /// @@ -549,10 +556,12 @@ pub struct FileFinderSettingsContent { /// /// Default: None /// todo() -> Change this type to an enum - pub include_ignored: Option>, + pub include_ignored: Option, } -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "lowercase")] pub enum FileFinderWidthContent { #[default] @@ -564,7 +573,7 @@ pub enum FileFinderWidthContent { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema)] +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Debug, JsonSchema, MergeFrom)] pub struct VimSettingsContent { pub default_mode: Option, pub toggle_relative_line_numbers: Option, @@ -575,7 +584,7 @@ pub struct VimSettingsContent { pub cursor_shape: Option, } -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)] +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)] #[serde(rename_all = "snake_case")] pub enum ModeContent { #[default] @@ -585,7 +594,7 @@ pub enum ModeContent { } /// Controls when to use system clipboard. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum UseSystemClipboard { /// Don't use system clipboard. @@ -598,7 +607,7 @@ pub enum UseSystemClipboard { /// The settings for cursor shape. #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] pub struct CursorShapeSettings { /// Cursor shape for the normal mode. /// @@ -620,7 +629,7 @@ pub struct CursorShapeSettings { /// Settings specific to journaling #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct JournalSettingsContent { /// The path of the directory where journal entries are stored. /// @@ -632,7 +641,7 @@ pub struct JournalSettingsContent { pub hour_format: Option, } -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub enum HourFormat { #[default] @@ -641,7 +650,7 @@ pub enum HourFormat { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct OutlinePanelSettingsContent { /// Whether to show the outline panel button in the status bar. /// @@ -695,14 +704,14 @@ pub struct OutlinePanelSettingsContent { pub expand_outlines_with_depth: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Copy, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Copy, PartialEq)] #[serde(rename_all = "snake_case")] pub enum DockSide { Left, Right, } -#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Deserialize, Serialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ShowIndentGuides { Always, @@ -710,13 +719,13 @@ pub enum ShowIndentGuides { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct IndentGuidesSettingsContent { /// When to show the scrollbar in the outline panel. pub show: Option, } -#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, Deserialize, Serialize)] +#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, MergeFrom, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum LineIndicatorFormat { Short, @@ -726,7 +735,7 @@ pub enum LineIndicatorFormat { /// The settings for the image viewer. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, Default, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] pub struct ImageViewerSettingsContent { /// The unit to use for displaying image file sizes. /// @@ -735,7 +744,7 @@ pub struct ImageViewerSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, Default, PartialEq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, Default, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ImageFileSizeUnit { /// Displays file size in binary units (e.g., KiB, MiB). @@ -746,7 +755,7 @@ pub enum ImageFileSizeUnit { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct RemoteSettingsContent { pub ssh_connections: Option>, pub wsl_connections: Option>, @@ -754,7 +763,7 @@ pub struct RemoteSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct SshConnection { pub host: SharedString, pub username: Option, @@ -774,7 +783,7 @@ pub struct SshConnection { pub port_forwards: Option>, } -#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, Debug)] +#[derive(Clone, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Debug)] pub struct WslConnection { pub distro_name: SharedString, pub user: Option, @@ -791,7 +800,7 @@ pub struct SshProject { } #[skip_serializing_none] -#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize, JsonSchema, MergeFrom)] pub struct SshPortForwardOption { #[serde(skip_serializing_if = "Option::is_none")] pub local_host: Option, @@ -803,7 +812,7 @@ pub struct SshPortForwardOption { /// Settings for configuring REPL display and behavior. #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ReplSettingsContent { /// Maximum number of lines to keep in REPL's scrollback buffer. /// Clamped with [4, 256] range. @@ -816,3 +825,42 @@ pub struct ReplSettingsContent { /// Default: 128 pub max_columns: Option, } + +#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct ExtendingVec(pub Vec); + +impl Into> for ExtendingVec { + fn into(self) -> Vec { + self.0 + } +} +impl From> for ExtendingVec { + fn from(vec: Vec) -> Self { + ExtendingVec(vec) + } +} + +impl merge_from::MergeFrom for ExtendingVec { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(other) = other { + self.0.extend_from_slice(other.0.as_slice()); + } + } +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +pub struct SaturatingBool(pub bool); + +impl From for SaturatingBool { + fn from(value: bool) -> Self { + SaturatingBool(value) + } +} + +impl merge_from::MergeFrom for SaturatingBool { + fn merge_from(&mut self, other: Option<&Self>) { + if let Some(other) = other { + self.0 |= other.0 + } + } +} diff --git a/crates/settings/src/settings_content/agent.rs b/crates/settings/src/settings_content/agent.rs index 0dd9a78343ec6737a7b98a8ef9c755783c1e6f33..88b27615aab5b2ddef1a55678897fff0beb114a1 100644 --- a/crates/settings/src/settings_content/agent.rs +++ b/crates/settings/src/settings_content/agent.rs @@ -3,12 +3,13 @@ use gpui::SharedString; use schemars::{JsonSchema, json_schema}; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::{borrow::Cow, path::PathBuf, sync::Arc}; use crate::DockPosition; #[skip_serializing_none] -#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, Debug, Default)] +#[derive(Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, Default)] pub struct AgentSettingsContent { /// Whether the Agent is enabled. /// @@ -168,7 +169,7 @@ impl AgentSettingsContent { } #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct AgentProfileContent { pub name: Arc, #[serde(default)] @@ -180,12 +181,12 @@ pub struct AgentProfileContent { } #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ContextServerPresetContent { pub tools: IndexMap, bool>, } -#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum DefaultAgentView { #[default] @@ -193,7 +194,7 @@ pub enum DefaultAgentView { TextThread, } -#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Copy, Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub enum NotifyWhenAgentWaiting { #[default] @@ -203,13 +204,13 @@ pub enum NotifyWhenAgentWaiting { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct LanguageModelSelection { pub provider: LanguageModelProviderSetting, pub model: String, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] #[serde(rename_all = "snake_case")] pub enum CompletionMode { #[default] @@ -219,14 +220,14 @@ pub enum CompletionMode { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct LanguageModelParameters { pub provider: Option, pub model: Option, pub temperature: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, MergeFrom)] pub struct LanguageModelProviderSetting(pub String); impl JsonSchema for LanguageModelProviderSetting { @@ -277,7 +278,7 @@ impl From<&str> for LanguageModelProviderSetting { } #[skip_serializing_none] -#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, Debug)] +#[derive(Default, PartialEq, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug)] pub struct AllAgentServersSettings { pub gemini: Option, pub claude: Option, @@ -288,7 +289,7 @@ pub struct AllAgentServersSettings { } #[skip_serializing_none] -#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +#[derive(Default, Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct BuiltinAgentServerSettings { /// Absolute path to a binary to be used when launching this agent. /// @@ -320,7 +321,7 @@ pub struct BuiltinAgentServerSettings { } #[skip_serializing_none] -#[derive(Deserialize, Serialize, Clone, JsonSchema, Debug, PartialEq)] +#[derive(Deserialize, Serialize, Clone, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct CustomAgentServerSettings { #[serde(rename = "command")] pub path: PathBuf, diff --git a/crates/settings/src/settings_content/editor.rs b/crates/settings/src/settings_content/editor.rs index d5984d4213ea053589e51abc91b5ff9f1e7268f1..a443cf3faccb8755c6743acbe8ee11b0471a3e7f 100644 --- a/crates/settings/src/settings_content/editor.rs +++ b/crates/settings/src/settings_content/editor.rs @@ -4,11 +4,12 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use crate::{DiagnosticSeverityContent, ShowScrollbar}; #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct EditorSettingsContent { /// Whether the cursor blinks in the editor. /// @@ -194,7 +195,7 @@ pub struct EditorSettingsContent { // Status bar related settings #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct StatusBarContent { /// Whether to display the active language button in the status bar. /// @@ -208,7 +209,7 @@ pub struct StatusBarContent { // Toolbar related settings #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ToolbarContent { /// Whether to display breadcrumbs in the editor toolbar. /// @@ -235,7 +236,7 @@ pub struct ToolbarContent { /// Scrollbar related settings #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] pub struct ScrollbarContent { /// When to show the scrollbar in the editor. /// @@ -271,7 +272,7 @@ pub struct ScrollbarContent { /// Minimap related settings #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct MinimapContent { /// When to show the minimap in the editor. /// @@ -296,7 +297,7 @@ pub struct MinimapContent { /// How to highlight the current line in the minimap. /// /// Default: inherits editor line highlights setting - pub current_line_highlight: Option>, + pub current_line_highlight: Option, /// Maximum number of columns to display in the minimap. /// @@ -306,7 +307,7 @@ pub struct MinimapContent { /// Forcefully enable or disable the scrollbar for each axis #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Default)] pub struct ScrollbarAxesContent { /// When false, forcefully disables the horizontal scrollbar. Otherwise, obey other settings. /// @@ -321,7 +322,7 @@ pub struct ScrollbarAxesContent { /// Gutter related settings #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct GutterContent { /// Whether to show line numbers in the gutter. /// @@ -346,7 +347,9 @@ pub struct GutterContent { } /// How to render LSP `textDocument/documentColor` colors in the editor. -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum DocumentColorsRenderMode { /// Do not query and render document colors. @@ -360,7 +363,7 @@ pub enum DocumentColorsRenderMode { Background, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum CurrentLineHighlight { // Don't highlight the current line. @@ -374,7 +377,7 @@ pub enum CurrentLineHighlight { } /// When to populate a new search's query based on the text under the cursor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum SeedQuerySetting { /// Always populate the search query with the word under the cursor. @@ -386,7 +389,9 @@ pub enum SeedQuerySetting { } /// What to do when multibuffer is double clicked in some of its excerpts (parts of singleton buffers). -#[derive(Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Default, Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum DoubleClickInMultibuffer { /// Behave as a regular buffer and select the whole word. @@ -400,7 +405,9 @@ pub enum DoubleClickInMultibuffer { /// When to show the minimap thumb. /// /// Default: always -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum MinimapThumb { /// Show the minimap thumb only when the mouse is hovering over the minimap. @@ -413,7 +420,9 @@ pub enum MinimapThumb { /// Defines the border style for the minimap's scrollbar thumb. /// /// Default: left_open -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum MinimapThumbBorder { /// Displays a border on all sides of the thumb. @@ -432,7 +441,7 @@ pub enum MinimapThumbBorder { /// Which diagnostic indicators to show in the scrollbar. /// /// Default: all -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "lowercase")] pub enum ScrollbarDiagnostics { /// Show all diagnostic levels: hint, information, warnings, error. @@ -450,7 +459,7 @@ pub enum ScrollbarDiagnostics { /// The key to use for adding multiple cursors /// /// Default: alt -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum MultiCursorModifier { Alt, @@ -461,7 +470,7 @@ pub enum MultiCursorModifier { /// Whether the editor will scroll beyond the last line. /// /// Default: one_page -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ScrollBeyondLastLine { /// The editor will not scroll beyond the last line. @@ -475,7 +484,9 @@ pub enum ScrollBeyondLastLine { } /// The shape of a selection cursor. -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum CursorShape { /// A vertical bar @@ -490,7 +501,9 @@ pub enum CursorShape { } /// What to do when go to definition yields no results. -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum GoToDefinitionFallback { /// Disables the fallback. @@ -503,7 +516,9 @@ pub enum GoToDefinitionFallback { /// Determines when the mouse cursor should be hidden in an editor or input box. /// /// Default: on_typing_and_movement -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum HideMouseMode { /// Never hide the mouse cursor @@ -518,7 +533,9 @@ pub enum HideMouseMode { /// Determines how snippets are sorted relative to other completion items. /// /// Default: inline -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum SnippetSortOrder { /// Place snippets at the top of the completion list @@ -534,7 +551,7 @@ pub enum SnippetSortOrder { /// Default options for buffer and project search items. #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct SearchSettingsContent { /// Whether to show the project search button in the status bar. pub button: Option, @@ -545,7 +562,7 @@ pub struct SearchSettingsContent { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct JupyterContent { /// Whether the Jupyter feature is enabled. @@ -561,7 +578,7 @@ pub struct JupyterContent { /// Whether to allow drag and drop text selection in buffer. #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct DragAndDropSelectionContent { /// When true, enables drag and drop text selection in buffer. /// @@ -577,7 +594,9 @@ pub struct DragAndDropSelectionContent { /// When to show the minimap in the editor. /// /// Default: never -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum ShowMinimap { /// Follow the visibility of the scrollbar. @@ -592,7 +611,9 @@ pub enum ShowMinimap { /// Where to show the minimap in the editor. /// /// Default: all_editors -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum DisplayIn { /// Show on all open editors. diff --git a/crates/settings/src/settings_content/language.rs b/crates/settings/src/settings_content/language.rs index de8cf378d1e25f7e84047d788816572ffd97d25c..ef435d638359825128729d0c024cde8e5c5613c8 100644 --- a/crates/settings/src/settings_content/language.rs +++ b/crates/settings/src/settings_content/language.rs @@ -8,10 +8,10 @@ use serde::{ de::{self, IntoDeserializer, MapAccess, SeqAccess, Visitor}, }; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::sync::Arc; -use util::schemars::replace_subschema; -use crate::ParameterizedJsonSchema; +use crate::{ExtendingVec, merge_from}; #[skip_serializing_none] #[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] @@ -31,12 +31,50 @@ pub struct AllLanguageSettingsContent { /// Settings for associating file extensions and filenames /// with languages. #[serde(default)] - pub file_types: HashMap, Vec>, + pub file_types: HashMap, ExtendingVec>, +} + +fn merge_option(this: &mut Option, other: Option<&T>) { + let Some(other) = other else { return }; + if let Some(this) = this { + this.merge_from(Some(other)); + } else { + this.replace(other.clone()); + } +} + +impl merge_from::MergeFrom for AllLanguageSettingsContent { + fn merge_from(&mut self, other: Option<&Self>) { + let Some(other) = other else { return }; + self.file_types.merge_from(Some(&other.file_types)); + merge_option(&mut self.features, other.features.as_ref()); + merge_option(&mut self.edit_predictions, other.edit_predictions.as_ref()); + + // A user's global settings override the default global settings and + // all default language-specific settings. + // + self.defaults.merge_from(Some(&other.defaults)); + for language_settings in self.languages.0.values_mut() { + language_settings.merge_from(Some(&other.defaults)); + } + + // A user's language-specific settings override default language-specific settings. + for (language_name, user_language_settings) in &other.languages.0 { + if let Some(existing) = self.languages.0.get_mut(language_name) { + existing.merge_from(Some(&user_language_settings)); + } else { + let mut new_settings = self.defaults.clone(); + new_settings.merge_from(Some(&user_language_settings)); + + self.languages.0.insert(language_name.clone(), new_settings); + } + } + } } /// The settings for enabling/disabling features. #[skip_serializing_none] -#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct FeaturesContent { /// Determines which edit prediction provider to use. @@ -44,7 +82,9 @@ pub struct FeaturesContent { } /// The provider that supplies edit predictions. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum EditPredictionProvider { None, @@ -56,7 +96,7 @@ pub enum EditPredictionProvider { /// The contents of the edit prediction settings. #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct EditPredictionSettingsContent { /// A list of globs representing files that edit predictions should be disabled for. /// This list adds to a pre-existing, sensible default set of globs. @@ -73,7 +113,7 @@ pub struct EditPredictionSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct CopilotSettingsContent { /// HTTP/HTTPS proxy to use for Copilot. /// @@ -90,7 +130,9 @@ pub struct CopilotSettingsContent { } /// The mode in which edit predictions should be displayed. -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum EditPredictionsMode { /// If provider supports it, display inline when holding modifier key (e.g., alt). @@ -104,7 +146,7 @@ pub enum EditPredictionsMode { } /// Controls the soft-wrapping behavior in the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum SoftWrap { /// Prefer a single line generally, unless an overly long line is encountered. @@ -122,7 +164,7 @@ pub enum SoftWrap { /// The settings for a particular language. #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageSettingsContent { /// How many columns a tab should occupy. /// @@ -289,7 +331,7 @@ pub struct LanguageSettingsContent { } /// Controls how whitespace should be displayedin the editor. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ShowWhitespaceSetting { /// Draw whitespace only for the selected text. @@ -310,7 +352,7 @@ pub enum ShowWhitespaceSetting { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct WhitespaceMap { pub space: Option, pub tab: Option, @@ -331,7 +373,7 @@ impl WhitespaceMap { } /// The behavior of `editor::Rewrap`. -#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum RewrapBehavior { /// Only rewrap within comments. @@ -344,7 +386,7 @@ pub enum RewrapBehavior { } #[skip_serializing_none] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct JsxTagAutoCloseSettingsContent { /// Enables or disables auto-closing of JSX tags. pub enabled: Option, @@ -352,7 +394,7 @@ pub struct JsxTagAutoCloseSettingsContent { /// The settings for inlay hints. #[skip_serializing_none] -#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct InlayHintSettingsContent { /// Global switch to toggle hints on and off. /// @@ -434,7 +476,7 @@ impl InlayHintKind { /// Controls how completions are processed for this language. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub struct CompletionSettingsContent { /// Controls how words are completed. @@ -462,7 +504,7 @@ pub struct CompletionSettingsContent { pub lsp_insert_mode: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum LspInsertMode { /// Replaces text before the cursor, using the `insert` range described in the LSP specification. @@ -478,7 +520,7 @@ pub enum LspInsertMode { } /// Controls how document's words are completed. -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum WordsCompletionMode { /// Always fetch document's words for completions along with LSP completions. @@ -495,7 +537,7 @@ pub enum WordsCompletionMode { /// and configure default Prettier, used when no project-level Prettier installation is found. /// Prettier formatting is disabled by default. #[skip_serializing_none] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct PrettierSettingsContent { /// Enables or disables formatting with Prettier for a given language. pub allowed: Option, @@ -515,7 +557,7 @@ pub struct PrettierSettingsContent { } /// Controls the behavior of formatting files when they are saved. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, MergeFrom)] pub enum FormatOnSave { /// Files should be formatted on save. On, @@ -614,7 +656,7 @@ impl<'de> Deserialize<'de> for FormatOnSave { } /// Controls which formatter should be used when formatting code. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, MergeFrom)] pub enum SelectedFormatter { /// Format files using Zed's Prettier integration (if applicable), /// or falling back to formatting via language server. @@ -710,7 +752,7 @@ impl<'de> Deserialize<'de> for SelectedFormatter { } /// Controls which formatters should be used when formatting code. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(untagged)] pub enum FormatterList { Single(Formatter), @@ -733,7 +775,7 @@ impl AsRef<[Formatter]> for FormatterList { } /// Controls which formatter should be used when formatting code. If there are multiple formatters, they are executed in the order of declaration. -#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Default, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum Formatter { /// Format code using the current language server. @@ -754,7 +796,7 @@ pub enum Formatter { /// The settings for indent guides. #[skip_serializing_none] -#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Default, Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct IndentGuideSettingsContent { /// Whether to display indent guides in the editor. /// @@ -780,7 +822,7 @@ pub struct IndentGuideSettingsContent { /// The task settings for a particular language. #[skip_serializing_none] -#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema)] +#[derive(Debug, Clone, Deserialize, PartialEq, Serialize, JsonSchema, MergeFrom)] pub struct LanguageTaskSettingsContent { /// Extra task variables to set for a particular language. #[serde(default)] @@ -796,37 +838,15 @@ pub struct LanguageTaskSettingsContent { pub prefer_lsp: Option, } -/// Map from language name to settings. Its `ParameterizedJsonSchema` allows only known language -/// names in the keys. +/// Map from language name to settings. #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageToSettingsMap(pub HashMap); -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - let language_settings_content_ref = generator - .subschema_for::() - .to_value(); - replace_subschema::(generator, || json_schema!({ - "type": "object", - "properties": params - .language_names - .iter() - .map(|name| { - ( - name.clone(), - language_settings_content_ref.clone(), - ) - }) - .collect::>() - })) - } - } -} - /// Determines how indent guides are colored. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive( + Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum IndentGuideColoring { /// Do not render any lines for indent guides. @@ -839,7 +859,9 @@ pub enum IndentGuideColoring { } /// Determines how indent guide backgrounds are colored. -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive( + Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum IndentGuideBackgroundColoring { /// Do not render any background for indent guides. diff --git a/crates/settings/src/settings_content/language_model.rs b/crates/settings/src/settings_content/language_model.rs index cad06d5fff78d1fa4ad9cd8830ab24a438d3c9ea..8e20821cfd687f6464fe52c3b75b89fde2907ec3 100644 --- a/crates/settings/src/settings_content/language_model.rs +++ b/crates/settings/src/settings_content/language_model.rs @@ -2,11 +2,12 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use std::sync::Arc; #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AllLanguageModelSettingsContent { pub anthropic: Option, pub bedrock: Option, @@ -25,14 +26,14 @@ pub struct AllLanguageModelSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AnthropicSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct AnthropicAvailableModel { /// The model's name in the Anthropic API. e.g. claude-3-5-sonnet-latest, claude-3-opus-20240229, etc pub name: String, @@ -53,7 +54,7 @@ pub struct AnthropicAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct AmazonBedrockSettingsContent { pub available_models: Option>, pub endpoint_url: Option, @@ -63,7 +64,7 @@ pub struct AmazonBedrockSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct BedrockAvailableModel { pub name: String, pub display_name: Option, @@ -74,7 +75,7 @@ pub struct BedrockAvailableModel { pub mode: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub enum BedrockAuthMethodContent { #[serde(rename = "named_profile")] NamedProfile, @@ -86,14 +87,14 @@ pub enum BedrockAuthMethodContent { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OllamaSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OllamaAvailableModel { /// The model name in the Ollama API (e.g. "llama3.2:latest") pub name: String, @@ -111,7 +112,7 @@ pub struct OllamaAvailableModel { pub supports_thinking: Option, } -#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema)] +#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq, JsonSchema, MergeFrom)] #[serde(untagged)] pub enum KeepAlive { /// Keep model alive for N seconds @@ -134,14 +135,14 @@ impl Default for KeepAlive { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct LmStudioSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LmStudioAvailableModel { pub name: String, pub display_name: Option, @@ -151,14 +152,14 @@ pub struct LmStudioAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct DeepseekSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct DeepseekAvailableModel { pub name: String, pub display_name: Option, @@ -167,14 +168,14 @@ pub struct DeepseekAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct MistralSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct MistralAvailableModel { pub name: String, pub display_name: Option, @@ -187,14 +188,14 @@ pub struct MistralAvailableModel { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenAiSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiAvailableModel { pub name: String, pub display_name: Option, @@ -204,7 +205,7 @@ pub struct OpenAiAvailableModel { pub reasoning_effort: Option, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, JsonSchema)] +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum OpenAiReasoningEffort { Minimal, @@ -214,14 +215,14 @@ pub enum OpenAiReasoningEffort { } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleSettingsContent { pub api_url: String, pub available_models: Vec, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleAvailableModel { pub name: String, pub display_name: Option, @@ -233,7 +234,7 @@ pub struct OpenAiCompatibleAvailableModel { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenAiCompatibleModelCapabilities { pub tools: bool, pub images: bool, @@ -253,14 +254,14 @@ impl Default for OpenAiCompatibleModelCapabilities { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct VercelSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct VercelAvailableModel { pub name: String, pub display_name: Option, @@ -270,14 +271,14 @@ pub struct VercelAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct GoogleSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GoogleAvailableModel { pub name: String, pub display_name: Option, @@ -286,14 +287,14 @@ pub struct GoogleAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct XAiSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct XaiAvailableModel { pub name: String, pub display_name: Option, @@ -303,13 +304,13 @@ pub struct XaiAvailableModel { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct ZedDotDevSettingsContent { pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ZedDotDevAvailableModel { /// The provider of the language model. pub provider: ZedDotDevAvailableProvider, @@ -336,7 +337,7 @@ pub struct ZedDotDevAvailableModel { pub mode: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum ZedDotDevAvailableProvider { Anthropic, @@ -345,14 +346,14 @@ pub enum ZedDotDevAvailableProvider { } #[skip_serializing_none] -#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] pub struct OpenRouterSettingsContent { pub api_url: Option, pub available_models: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenRouterAvailableModel { pub name: String, pub display_name: Option, @@ -366,7 +367,7 @@ pub struct OpenRouterAvailableModel { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct OpenRouterProvider { order: Option>, #[serde(default = "default_true")] @@ -381,7 +382,7 @@ pub struct OpenRouterProvider { sort: Option, } -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum DataCollection { Allow, @@ -400,14 +401,16 @@ fn default_true() -> bool { /// Configuration for caching language model messages. #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct LanguageModelCacheConfiguration { pub max_cache_anchors: usize, pub should_speculate: bool, pub min_total_token: u64, } -#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive( + Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(tag = "type", rename_all = "lowercase")] pub enum ModelMode { #[default] diff --git a/crates/settings/src/settings_content/project.rs b/crates/settings/src/settings_content/project.rs index 2c154dd8ea337ae165cb1e76872640a8af334fe3..b44f77fcf171e754313542641ad0f6453a730ed8 100644 --- a/crates/settings/src/settings_content/project.rs +++ b/crates/settings/src/settings_content/project.rs @@ -4,12 +4,13 @@ use collections::{BTreeMap, HashMap}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use util::serde::default_true; -use crate::{AllLanguageSettingsContent, SlashCommandSettings}; +use crate::{AllLanguageSettingsContent, ExtendingVec, SlashCommandSettings}; #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ProjectSettingsContent { #[serde(flatten)] pub all_languages: AllLanguageSettingsContent, @@ -43,11 +44,11 @@ pub struct ProjectSettingsContent { pub slash_commands: Option, /// The list of custom Git hosting providers. - pub git_hosting_providers: Option>, + pub git_hosting_providers: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct WorktreeSettingsContent { /// The displayed name of this project. If not set, the root directory name /// will be displayed. @@ -81,11 +82,11 @@ pub struct WorktreeSettingsContent { /// Treat the files matching these globs as `.env` files. /// Default: ["**/.env*", "**/*.pem", "**/*.key", "**/*.cert", "**/*.crt", "**/secrets.yml"] - pub private_files: Option>, + pub private_files: Option>, } #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash)] #[serde(rename_all = "snake_case")] pub struct LspSettings { pub binary: Option, @@ -112,7 +113,9 @@ impl Default for LspSettings { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[derive( + Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash, +)] pub struct BinarySettings { pub path: Option, pub arguments: Option>, @@ -121,7 +124,9 @@ pub struct BinarySettings { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)] +#[derive( + Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, Hash, +)] pub struct FetchSettings { // Whether to consider pre-releases for fetching pub pre_release: Option, @@ -129,7 +134,7 @@ pub struct FetchSettings { /// Common language server settings. #[skip_serializing_none] -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GlobalLspSettingsContent { /// Whether to show the LSP servers button in the status bar. /// @@ -138,7 +143,7 @@ pub struct GlobalLspSettingsContent { } #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct DapSettingsContent { pub binary: Option, @@ -147,7 +152,9 @@ pub struct DapSettingsContent { } #[skip_serializing_none] -#[derive(Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema)] +#[derive( + Default, Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, +)] pub struct SessionSettingsContent { /// Whether or not to restore unsaved buffers on restart. /// @@ -158,7 +165,7 @@ pub struct SessionSettingsContent { pub restore_unsaved_buffers: Option, } -#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, Debug)] +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom, Debug)] #[serde(tag = "source", rename_all = "snake_case")] pub enum ContextServerSettingsContent { Custom { @@ -198,7 +205,7 @@ impl ContextServerSettingsContent { } #[skip_serializing_none] -#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema)] +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq, JsonSchema, MergeFrom)] pub struct ContextServerCommand { #[serde(rename = "command")] pub path: PathBuf, @@ -234,7 +241,7 @@ impl std::fmt::Debug for ContextServerCommand { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GitSettings { /// Whether or not to show the git gutter. /// @@ -259,7 +266,7 @@ pub struct GitSettings { pub hunk_style: Option, } -#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum GitGutterSetting { /// Show git gutter in tracked files. @@ -270,7 +277,7 @@ pub enum GitGutterSetting { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct InlineBlameSettings { /// Whether or not to show git blame data inline in @@ -299,7 +306,7 @@ pub struct InlineBlameSettings { } #[skip_serializing_none] -#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct BranchPickerSettingsContent { /// Whether to show author name as part of the commit information. @@ -308,7 +315,7 @@ pub struct BranchPickerSettingsContent { pub show_author_name: Option, } -#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum GitHunkStyleSetting { /// Show unstaged hunks with a filled background and staged hunks hollow. @@ -319,7 +326,7 @@ pub enum GitHunkStyleSetting { } #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct DiagnosticsSettingsContent { /// Whether to show the project diagnostics button in the status bar. pub button: Option, @@ -335,7 +342,7 @@ pub struct DiagnosticsSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct LspPullDiagnosticsSettingsContent { /// Whether to pull for diagnostics or not. /// @@ -349,7 +356,7 @@ pub struct LspPullDiagnosticsSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom, Eq)] pub struct InlineDiagnosticsSettingsContent { /// Whether or not to show inline diagnostics /// @@ -376,7 +383,7 @@ pub struct InlineDiagnosticsSettingsContent { } #[skip_serializing_none] -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct NodeBinarySettings { /// The path to the Node binary. pub path: Option, @@ -386,7 +393,7 @@ pub struct NodeBinarySettings { pub ignore_system_version: Option, } -#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum DirenvSettings { /// Load direnv configuration through a shell hook @@ -397,7 +404,17 @@ pub enum DirenvSettings { } #[derive( - Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd, Serialize, Deserialize, JsonSchema, + Clone, + Copy, + Debug, + Eq, + PartialEq, + Ord, + PartialOrd, + Serialize, + Deserialize, + JsonSchema, + MergeFrom, )] #[serde(rename_all = "snake_case")] pub enum DiagnosticSeverityContent { @@ -412,7 +429,7 @@ pub enum DiagnosticSeverityContent { /// A custom Git hosting provider. #[skip_serializing_none] -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct GitHostingProviderConfig { /// The type of the provider. /// @@ -426,7 +443,7 @@ pub struct GitHostingProviderConfig { pub name: String, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum GitHostingProviderKind { Github, diff --git a/crates/settings/src/settings_content/terminal.rs b/crates/settings/src/settings_content/terminal.rs index e4d76a049e03438dc351b2e1bcf628cfa0e5f5e1..017e89102dc2580e4924488809531806d6d662e8 100644 --- a/crates/settings/src/settings_content/terminal.rs +++ b/crates/settings/src/settings_content/terminal.rs @@ -5,11 +5,12 @@ use gpui::{AbsoluteLength, FontFeatures, SharedString, px}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use crate::FontFamilyName; #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct TerminalSettingsContent { /// What shell to use when opening a terminal. /// @@ -127,7 +128,7 @@ pub struct TerminalSettingsContent { } /// Shell configuration to open the terminal with. -#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum Shell { /// Use the system's default terminal configuration in /etc/passwd @@ -146,7 +147,7 @@ pub enum Shell { }, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum WorkingDirectory { /// Use the current file's project directory. Will Fallback to the @@ -163,15 +164,15 @@ pub enum WorkingDirectory { } #[skip_serializing_none] -#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ScrollbarSettingsContent { /// When to show the scrollbar in the terminal. /// /// Default: inherits editor scrollbar settings - pub show: Option>, + pub show: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub enum TerminalLineHeight { /// Use a line height that's comfortable for reading, 1.618 @@ -198,7 +199,7 @@ impl TerminalLineHeight { /// When to show the scrollbar. /// /// Default: auto -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum ShowScrollbar { /// Show the scrollbar if there's important information or @@ -212,7 +213,9 @@ pub enum ShowScrollbar { Never, } -#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive( + Clone, Copy, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum CursorShapeContent { /// Cursor is a block like `█`. @@ -226,7 +229,7 @@ pub enum CursorShapeContent { Hollow, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum TerminalBlink { /// Never blink the cursor, ignoring the terminal mode. @@ -238,7 +241,7 @@ pub enum TerminalBlink { On, } -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum AlternateScroll { On, @@ -247,7 +250,7 @@ pub enum AlternateScroll { // Toolbar related settings #[skip_serializing_none] -#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct TerminalToolbarContent { /// Whether to display the terminal title in breadcrumbs inside the terminal pane. /// Only shown if the terminal title is not empty. @@ -259,7 +262,7 @@ pub struct TerminalToolbarContent { pub breadcrumbs: Option, } -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum VenvSettings { #[default] @@ -297,7 +300,7 @@ impl VenvSettings { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum TerminalDockPosition { Left, @@ -305,7 +308,7 @@ pub enum TerminalDockPosition { Right, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ActivateScript { #[default] diff --git a/crates/settings/src/settings_content/theme.rs b/crates/settings/src/settings_content/theme.rs index 886448d696fa975749a1fcefe615694a903f8d29..bc6eed96ad12ef5f1358e7a21a4fe2db8a6a9d10 100644 --- a/crates/settings/src/settings_content/theme.rs +++ b/crates/settings/src/settings_content/theme.rs @@ -4,6 +4,7 @@ use schemars::{JsonSchema, JsonSchema_repr}; use serde::{Deserialize, Deserializer, Serialize}; use serde_json::Value; use serde_repr::{Deserialize_repr, Serialize_repr}; +use settings_macros::MergeFrom; use std::sync::Arc; use serde_with::skip_serializing_none; @@ -11,7 +12,7 @@ use serde_with::skip_serializing_none; /// Settings for rendering text in UI and text buffers. #[skip_serializing_none] -#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ThemeSettingsContent { /// The default font size for text in the UI. #[serde(default)] @@ -53,7 +54,7 @@ pub struct ThemeSettingsContent { pub buffer_font_features: Option, /// The font size for the agent panel. Falls back to the UI font size if unset. #[serde(default)] - pub agent_font_size: Option>, + pub agent_font_size: Option, /// The name of the Zed theme to use. #[serde(default)] pub theme: Option, @@ -93,7 +94,7 @@ fn default_font_fallbacks() -> Option { } /// Represents the selection of a theme, which can be either static or dynamic. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(untagged)] pub enum ThemeSelection { /// A static theme selection, represented by a single theme name. @@ -111,7 +112,7 @@ pub enum ThemeSelection { } /// Represents the selection of an icon theme, which can be either static or dynamic. -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(untagged)] pub enum IconThemeSelection { /// A static icon theme selection, represented by a single icon theme name. @@ -134,7 +135,9 @@ pub enum IconThemeSelection { /// `Light` and `Dark` will select their respective themes. /// /// `System` will select the theme based on the system's appearance. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)] +#[derive( + Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, MergeFrom, +)] #[serde(rename_all = "snake_case")] pub enum ThemeMode { /// Use the specified `light` theme. @@ -163,6 +166,7 @@ pub enum ThemeMode { Serialize, Deserialize, JsonSchema, + MergeFrom, )] #[serde(rename_all = "snake_case")] pub enum UiDensity { @@ -190,15 +194,14 @@ impl UiDensity { } } -/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at -/// runtime. +/// Font family name. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct FontFamilyName(pub Arc); /// The buffer's line height. -#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom, Default)] #[serde(rename_all = "snake_case")] pub enum BufferLineHeight { /// A less dense line height. @@ -226,7 +229,7 @@ where /// The content of a serialized theme. #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct ThemeStyleContent { #[serde(default, rename = "background.appearance")] @@ -249,31 +252,30 @@ pub struct ThemeStyleContent { pub syntax: IndexMap, } -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct AccentContent(pub Option); -#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] pub struct PlayerColorContent { pub cursor: Option, pub background: Option, pub selection: Option, } -/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime. +/// Theme name. #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct ThemeName(pub Arc); -/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at -/// runtime. +/// Icon Theme Name #[skip_serializing_none] -#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] #[serde(transparent)] pub struct IconThemeName(pub Arc); #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct ThemeColorsContent { /// Border color. Used for most borders, is usually a high contrast color. @@ -778,7 +780,7 @@ pub struct ThemeColorsContent { } #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct HighlightStyleContent { pub color: Option, @@ -812,7 +814,7 @@ where } #[skip_serializing_none] -#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(default)] pub struct StatusColorsContent { /// Indicates some kind of conflict, like a file changed on disk while it was open, or @@ -958,7 +960,7 @@ pub struct StatusColorsContent { } /// The background appearance of the window. -#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema)] +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum WindowBackgroundContent { Opaque, @@ -976,7 +978,7 @@ impl Into for WindowBackgroundContent { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub enum FontStyleContent { Normal, @@ -994,7 +996,9 @@ impl From for FontStyle { } } -#[derive(Debug, Clone, Copy, Serialize_repr, Deserialize_repr, JsonSchema_repr, PartialEq)] +#[derive( + Debug, Clone, Copy, Serialize_repr, Deserialize_repr, JsonSchema_repr, PartialEq, MergeFrom, +)] #[repr(u16)] pub enum FontWeightContent { Thin = 100, diff --git a/crates/settings/src/settings_content/workspace.rs b/crates/settings/src/settings_content/workspace.rs index aaa5817336058fad2e4300e70143e2100c9b72c7..9d210a73da1a92768e4e5cbb5e9804ce9d292ce5 100644 --- a/crates/settings/src/settings_content/workspace.rs +++ b/crates/settings/src/settings_content/workspace.rs @@ -4,11 +4,12 @@ use collections::HashMap; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use serde_with::skip_serializing_none; +use settings_macros::MergeFrom; use crate::{DockPosition, DockSide, ScrollbarSettingsContent, ShowIndentGuides}; #[skip_serializing_none] -#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct WorkspaceSettingsContent { /// Active pane styling settings. pub active_pane_modifiers: Option, @@ -108,7 +109,7 @@ pub struct WorkspaceSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] pub struct ItemSettingsContent { /// Whether to show the Git file status on a tab item. /// @@ -138,7 +139,7 @@ pub struct ItemSettingsContent { } #[skip_serializing_none] -#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, JsonSchema, MergeFrom)] 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. @@ -155,7 +156,7 @@ pub struct PreviewTabsSettingsContent { pub enable_preview_from_code_navigation: Option, } -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum ClosePosition { Left, @@ -163,7 +164,7 @@ pub enum ClosePosition { Right, } -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "lowercase")] pub enum ShowCloseButton { Always, @@ -172,7 +173,9 @@ pub enum ShowCloseButton { Hidden, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum ShowDiagnostics { #[default] @@ -181,7 +184,7 @@ pub enum ShowDiagnostics { All, } -#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, Debug, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum ActivateOnClose { #[default] @@ -191,7 +194,7 @@ pub enum ActivateOnClose { } #[skip_serializing_none] -#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema)] +#[derive(Copy, Clone, PartialEq, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub struct ActivePanelModifiers { /// Size of the border surrounding the active pane. @@ -209,7 +212,7 @@ pub struct ActivePanelModifiers { pub inactive_opacity: Option, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema)] +#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, PartialEq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum BottomDockLayout { /// Contained between the left and right docks @@ -223,7 +226,7 @@ pub enum BottomDockLayout { RightAligned, } -#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] #[serde(rename_all = "snake_case")] pub enum CloseWindowWhenNoItems { /// Match platform conventions by default, so "on" on macOS and "off" everywhere else @@ -245,7 +248,9 @@ impl CloseWindowWhenNoItems { } } -#[derive(Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive( + Copy, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, +)] #[serde(rename_all = "snake_case")] pub enum RestoreOnStartupBehavior { /// Always start with an empty editor @@ -258,7 +263,7 @@ pub enum RestoreOnStartupBehavior { } #[skip_serializing_none] -#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug, PartialEq)] +#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug, PartialEq)] pub struct TabBarSettingsContent { /// Whether or not to show the tab bar in the editor. /// @@ -274,7 +279,7 @@ pub struct TabBarSettingsContent { pub show_tab_bar_buttons: Option, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum AutosaveSetting { /// Disable autosave. @@ -298,14 +303,14 @@ impl AutosaveSetting { } } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum PaneSplitDirectionHorizontal { Up, Down, } -#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema, MergeFrom)] #[serde(rename_all = "snake_case")] pub enum PaneSplitDirectionVertical { Left, @@ -313,7 +318,7 @@ pub enum PaneSplitDirectionVertical { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq)] #[serde(rename_all = "snake_case")] pub struct CenteredLayoutSettings { /// The relative width of the left padding of the central pane from the @@ -328,7 +333,7 @@ pub struct CenteredLayoutSettings { pub right_padding: Option, } -#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Debug)] +#[derive(Copy, Clone, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Debug)] #[serde(rename_all = "snake_case")] pub enum OnLastWindowClosed { /// Match platform conventions by default, so don't quit on macOS, and quit on other platforms @@ -348,7 +353,7 @@ impl OnLastWindowClosed { } #[skip_serializing_none] -#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, Debug)] +#[derive(Clone, PartialEq, Default, Serialize, Deserialize, JsonSchema, MergeFrom, Debug)] pub struct ProjectPanelSettingsContent { /// Whether to show the project panel button in the status bar. /// @@ -423,7 +428,9 @@ pub struct ProjectPanelSettingsContent { pub drag_and_drop: Option, } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive( + Copy, Clone, Debug, Default, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq, +)] #[serde(rename_all = "snake_case")] pub enum ProjectPanelEntrySpacing { /// Comfortable spacing of entries. @@ -434,7 +441,7 @@ pub enum ProjectPanelEntrySpacing { } #[skip_serializing_none] -#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] +#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, MergeFrom, PartialEq, Eq)] pub struct ProjectPanelIndentGuidesSettings { pub show: Option, } diff --git a/crates/settings/src/settings_json.rs b/crates/settings/src/settings_json.rs index 762842d851b73a661baf93a7060f26fe5714c872..555a48e9f0972d708eaf9aaaaaf467852ccf7dd6 100644 --- a/crates/settings/src/settings_json.rs +++ b/crates/settings/src/settings_json.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use gpui::App; +use gpui::SharedString; use serde::{Serialize, de::DeserializeOwned}; use serde_json::Value; use std::{ops::Range, sync::LazyLock}; @@ -10,16 +10,10 @@ use util::RangeExt; pub struct SettingsJsonSchemaParams<'a> { pub language_names: &'a [String], pub font_names: &'a [String], + pub theme_names: &'a [SharedString], + pub icon_theme_names: &'a [SharedString], } -/// Value registered which specifies JSON schemas that are generated at runtime. -pub struct ParameterizedJsonSchema { - pub add_and_get_ref: - fn(&mut schemars::SchemaGenerator, &SettingsJsonSchemaParams, &App) -> schemars::Schema, -} - -inventory::collect!(ParameterizedJsonSchema); - pub fn update_value_in_json_text<'a>( text: &mut String, key_path: &mut Vec<&'a str>, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index acbff18bc333515c81d994e8629e3dff21fb4146..fe2a5cfdfc6493cf3ef374a66c389022748e088b 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -10,7 +10,7 @@ use futures::{ use gpui::{App, AsyncApp, BorrowAppContext, Global, SharedString, Task, UpdateGlobal}; use paths::{EDITORCONFIG_NAME, local_settings_file_relative_path, task_file_name}; -use schemars::JsonSchema; +use schemars::{JsonSchema, json_schema}; use serde_json::Value; use smallvec::SmallVec; use std::{ @@ -18,16 +18,23 @@ use std::{ fmt::Debug, ops::Range, path::{Path, PathBuf}, + rc::Rc, str::{self, FromStr}, sync::Arc, }; -use util::{ResultExt as _, schemars::DefaultDenyUnknownFields}; +use util::{ + ResultExt as _, + schemars::{DefaultDenyUnknownFields, replace_subschema}, +}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{ - ActiveSettingsProfileName, ParameterizedJsonSchema, SettingsJsonSchemaParams, SettingsUiEntry, - VsCodeSettings, WorktreeId, parse_json_with_comments, replace_value_in_json_text, + ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent, + LanguageToSettingsMap, SettingsJsonSchemaParams, SettingsUiEntry, ThemeName, VsCodeSettings, + WorktreeId, + merge_from::MergeFrom, + parse_json_with_comments, replace_value_in_json_text, settings_content::{ ExtensionsSettingsContent, ProjectSettingsContent, ServerSettingsContent, SettingsContent, UserSettingsContent, @@ -58,15 +65,10 @@ pub trait Settings: 'static + Send + Sync + Sized { const PRESERVED_KEYS: Option<&'static [&'static str]> = None; /// Read the value from default.json. + /// /// This function *should* panic if default values are missing, /// and you should add a default to default.json for documentation. - fn from_defaults(content: &SettingsContent, cx: &mut App) -> Self; - - /// Update the value based on the content from the current file. - /// - /// This function *should not* panic if there are problems, as the - /// content of user-provided settings files may be incomplete or invalid. - fn refine(&mut self, content: &SettingsContent, cx: &mut App); + fn from_settings(content: &SettingsContent, cx: &mut App) -> Self; fn missing_default() -> anyhow::Error { anyhow::anyhow!("missing default for: {}", std::any::type_name::()) @@ -140,12 +142,15 @@ pub struct SettingsLocation<'a> { /// A set of strongly-typed setting values defined via multiple config files. pub struct SettingsStore { setting_values: HashMap>, - default_settings: Box, + default_settings: Rc, user_settings: Option, global_settings: Option>, extension_settings: Option>, server_settings: Option>, + + merged_settings: Rc, + local_settings: BTreeMap<(WorktreeId, Arc), SettingsContent>, raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc), (String, Option)>, @@ -193,8 +198,7 @@ struct SettingValue { trait AnySettingValue: 'static + Send + Sync { fn setting_type_name(&self) -> &'static str; - fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box; - fn refine(&self, value: &mut dyn Any, s: &[&SettingsContent], cx: &mut App); + fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box; fn value_for_path(&self, path: Option) -> &dyn Any; fn all_local_values(&self) -> Vec<(WorktreeId, Arc, &dyn Any)>; @@ -210,14 +214,17 @@ trait AnySettingValue: 'static + Send + Sync { impl SettingsStore { pub fn new(cx: &App, default_settings: &str) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); - let default_settings = parse_json_with_comments(default_settings).unwrap(); + let default_settings: Rc = + parse_json_with_comments(default_settings).unwrap(); Self { setting_values: Default::default(), - default_settings, + default_settings: default_settings.clone(), global_settings: None, server_settings: None, user_settings: None, extension_settings: None, + + merged_settings: default_settings, local_settings: BTreeMap::default(), raw_editorconfig_settings: BTreeMap::default(), setting_file_updates_tx, @@ -257,38 +264,7 @@ impl SettingsStore { global_value: None, local_values: Vec::new(), })); - - let mut refinements = Vec::default(); - - if let Some(extension_settings) = self.extension_settings.as_deref() { - refinements.push(extension_settings) - } - - if let Some(global_settings) = self.global_settings.as_deref() { - refinements.push(global_settings) - } - - if let Some(user_settings) = self.user_settings.as_ref() { - refinements.push(&user_settings.content); - if let Some(release_channel) = user_settings.for_release_channel() { - refinements.push(release_channel) - } - if let Some(os) = user_settings.for_os() { - refinements.push(os) - } - if let Some(profile) = user_settings.for_profile(cx) { - refinements.push(profile) - } - } - - if let Some(server_settings) = self.server_settings.as_ref() { - refinements.push(server_settings) - } - let mut value = T::from_defaults(&self.default_settings, cx); - for refinement in refinements { - value.refine(refinement, cx) - } - + let value = T::from_settings(&self.merged_settings, cx); setting_value.set_global_value(Box::new(value)); } @@ -831,19 +807,56 @@ impl SettingsStore { }) } - pub fn json_schema(&self, schema_params: &SettingsJsonSchemaParams, cx: &App) -> Value { + pub fn json_schema(&self, params: &SettingsJsonSchemaParams) -> Value { let mut generator = schemars::generate::SchemaSettings::draft2019_09() .with_transform(DefaultDenyUnknownFields) .into_generator(); - let schema = UserSettingsContent::json_schema(&mut generator); + UserSettingsContent::json_schema(&mut generator); + + let language_settings_content_ref = generator + .subschema_for::() + .to_value(); + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "object", + "properties": params + .language_names + .iter() + .map(|name| { + ( + name.clone(), + language_settings_content_ref.clone(), + ) + }) + .collect::>() + }) + }); - // add schemas which are determined at runtime - for parameterized_json_schema in inventory::iter::() { - (parameterized_json_schema.add_and_get_ref)(&mut generator, schema_params, cx); - } + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "string", + "enum": params.font_names, + }) + }); + + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "string", + "enum": params.theme_names, + }) + }); - schema.to_value() + replace_subschema::(&mut generator, || { + json_schema!({ + "type": "string", + "enum": params.icon_theme_names, + }) + }); + + generator + .root_schema_for::() + .to_value() } fn recompute_values( @@ -852,74 +865,62 @@ impl SettingsStore { cx: &mut App, ) -> std::result::Result<(), InvalidSettingsError> { // Reload the global and local values for every setting. - let mut project_settings_stack = Vec::<&SettingsContent>::new(); + let mut project_settings_stack = Vec::::new(); let mut paths_stack = Vec::>::new(); - let mut refinements = Vec::default(); - - if let Some(extension_settings) = self.extension_settings.as_deref() { - refinements.push(extension_settings) - } - - if let Some(global_settings) = self.global_settings.as_deref() { - refinements.push(global_settings) - } - - if let Some(user_settings) = self.user_settings.as_ref() { - refinements.push(&user_settings.content); - if let Some(release_channel) = user_settings.for_release_channel() { - refinements.push(release_channel) + if changed_local_path.is_none() { + let mut merged = self.default_settings.as_ref().clone(); + merged.merge_from(self.extension_settings.as_deref()); + merged.merge_from(self.global_settings.as_deref()); + if let Some(user_settings) = self.user_settings.as_ref() { + merged.merge_from(Some(&user_settings.content)); + merged.merge_from(user_settings.for_release_channel()); + merged.merge_from(user_settings.for_os()); + merged.merge_from(user_settings.for_profile(cx)); } - if let Some(os) = user_settings.for_os() { - refinements.push(os) - } - if let Some(profile) = user_settings.for_profile(cx) { - refinements.push(profile) - } - } - - if let Some(server_settings) = self.server_settings.as_ref() { - refinements.push(server_settings) - } + merged.merge_from(self.server_settings.as_deref()); + self.merged_settings = Rc::new(merged); - for setting_value in self.setting_values.values_mut() { - // If the global settings file changed, reload the global value for the field. - if changed_local_path.is_none() { - let mut value = setting_value.from_default(&self.default_settings, cx); - setting_value.refine(value.as_mut(), &refinements, cx); + for setting_value in self.setting_values.values_mut() { + let value = setting_value.from_settings(&self.merged_settings, cx); setting_value.set_global_value(value); } + } - // Reload the local values for the setting. - paths_stack.clear(); - project_settings_stack.clear(); - for ((root_id, directory_path), local_settings) in &self.local_settings { - // Build a stack of all of the local values for that setting. - while let Some(prev_entry) = paths_stack.last() { - if let Some((prev_root_id, prev_path)) = prev_entry - && (root_id != prev_root_id || !directory_path.starts_with(prev_path)) - { - paths_stack.pop(); - project_settings_stack.pop(); - continue; - } - break; + for ((root_id, directory_path), local_settings) in &self.local_settings { + // Build a stack of all of the local values for that setting. + while let Some(prev_entry) = paths_stack.last() { + if let Some((prev_root_id, prev_path)) = prev_entry + && (root_id != prev_root_id || !directory_path.starts_with(prev_path)) + { + paths_stack.pop(); + project_settings_stack.pop(); + continue; } + break; + } - paths_stack.push(Some((*root_id, directory_path.as_ref()))); - project_settings_stack.push(local_settings); + paths_stack.push(Some((*root_id, directory_path.as_ref()))); + let mut merged_local_settings = if let Some(deepest) = project_settings_stack.last() { + (*deepest).clone() + } else { + self.merged_settings.as_ref().clone() + }; + merged_local_settings.merge_from(Some(local_settings)); - // If a local settings file changed, then avoid recomputing local - // settings for any path outside of that directory. - if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| { - *root_id != changed_root_id || !directory_path.starts_with(changed_local_path) - }) { - continue; - } + project_settings_stack.push(merged_local_settings); + + // If a local settings file changed, then avoid recomputing local + // settings for any path outside of that directory. + if changed_local_path.is_some_and(|(changed_root_id, changed_local_path)| { + *root_id != changed_root_id || !directory_path.starts_with(changed_local_path) + }) { + continue; + } - let mut value = setting_value.from_default(&self.default_settings, cx); - setting_value.refine(value.as_mut(), &refinements, cx); - setting_value.refine(value.as_mut(), &project_settings_stack, cx); + for setting_value in self.setting_values.values_mut() { + let value = + setting_value.from_settings(&project_settings_stack.last().unwrap(), cx); setting_value.set_local_value(*root_id, directory_path.clone(), value); } } @@ -1001,15 +1002,8 @@ impl Debug for SettingsStore { } impl AnySettingValue for SettingValue { - fn from_default(&self, s: &SettingsContent, cx: &mut App) -> Box { - Box::new(T::from_defaults(s, cx)) as _ - } - - fn refine(&self, value: &mut dyn Any, refinements: &[&SettingsContent], cx: &mut App) { - let value = value.downcast_mut::().unwrap(); - for refinement in refinements { - value.refine(refinement, cx) - } + fn from_settings(&self, s: &SettingsContent, cx: &mut App) -> Box { + Box::new(T::from_settings(s, cx)) as _ } fn setting_type_name(&self) -> &'static str { @@ -1072,7 +1066,6 @@ mod tests { use super::*; use unindent::Unindent; - use util::MergeFrom; #[derive(Debug, PartialEq)] struct AutoUpdateSetting { @@ -1080,19 +1073,11 @@ mod tests { } impl Settings for AutoUpdateSetting { - fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _: &mut App) -> Self { AutoUpdateSetting { auto_update: content.auto_update.unwrap(), } } - - fn refine(&mut self, content: &SettingsContent, _: &mut App) { - if let Some(auto_update) = content.auto_update { - self.auto_update = auto_update; - } - } - - fn import_from_vscode(_: &VsCodeSettings, _: &mut SettingsContent) {} } #[derive(Debug, PartialEq)] @@ -1102,7 +1087,7 @@ mod tests { } impl Settings for TitleBarSettings { - fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _: &mut App) -> Self { let content = content.title_bar.clone().unwrap(); TitleBarSettings { show: content.show.unwrap(), @@ -1110,13 +1095,6 @@ mod tests { } } - fn refine(&mut self, content: &SettingsContent, _: &mut App) { - let Some(content) = content.title_bar.as_ref() else { - return; - }; - self.show.merge_from(&content.show) - } - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { let mut show = None; @@ -1138,7 +1116,7 @@ mod tests { } impl Settings for DefaultLanguageSettings { - fn from_defaults(content: &SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _: &mut App) -> Self { let content = &content.project.all_languages.defaults; DefaultLanguageSettings { tab_size: content.tab_size.unwrap(), @@ -1146,13 +1124,6 @@ mod tests { } } - fn refine(&mut self, content: &SettingsContent, _: &mut App) { - let content = &content.project.all_languages.defaults; - self.tab_size.merge_from(&content.tab_size); - self.preferred_line_length - .merge_from(&content.preferred_line_length); - } - fn import_from_vscode(vscode: &VsCodeSettings, content: &mut SettingsContent) { let content = &mut content.project.all_languages.defaults; diff --git a/crates/settings_ui_macros/Cargo.toml b/crates/settings_macros/Cargo.toml similarity index 73% rename from crates/settings_ui_macros/Cargo.toml rename to crates/settings_macros/Cargo.toml index ece6269f5999ad3ed7e08e34d3ff4c7cb97c9f63..06ce1d01e5fa9d25b5c0d3c742a2d325ec996e39 100644 --- a/crates/settings_ui_macros/Cargo.toml +++ b/crates/settings_macros/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "settings_ui_macros" +name = "settings_macros" version = "0.1.0" edition.workspace = true publish.workspace = true license = "GPL-3.0-or-later" [lib] -path = "src/settings_ui_macros.rs" +path = "src/settings_macros.rs" proc-macro = true [lints] @@ -16,8 +16,6 @@ workspace = true default = [] [dependencies] -heck.workspace = true -proc-macro2.workspace = true quote.workspace = true syn.workspace = true workspace-hack.workspace = true diff --git a/crates/settings_ui_macros/LICENSE-GPL b/crates/settings_macros/LICENSE-GPL similarity index 100% rename from crates/settings_ui_macros/LICENSE-GPL rename to crates/settings_macros/LICENSE-GPL diff --git a/crates/settings_macros/src/settings_macros.rs b/crates/settings_macros/src/settings_macros.rs new file mode 100644 index 0000000000000000000000000000000000000000..33c136b1f2b3e4bec3528d4dff632e05119bc516 --- /dev/null +++ b/crates/settings_macros/src/settings_macros.rs @@ -0,0 +1,132 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{Data, DeriveInput, Fields, Type, parse_macro_input}; + +/// Derives the `MergeFrom` trait for a struct. +/// +/// This macro automatically implements `MergeFrom` by calling `merge_from` +/// on all fields in the struct. For `Option` fields, it merges by taking +/// the `other` value when `self` is `None`. For other types, it recursively +/// calls `merge_from` on the field. +/// +/// # Example +/// +/// ```ignore +/// #[derive(Clone, MergeFrom)] +/// struct MySettings { +/// field1: Option, +/// field2: SomeOtherSettings, +/// } +/// ``` +#[proc_macro_derive(MergeFrom)] +pub fn derive_merge_from(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let merge_body = match &input.data { + Data::Struct(data_struct) => match &data_struct.fields { + Fields::Named(fields) => { + let field_merges = fields.named.iter().map(|field| { + let field_name = &field.ident; + let field_type = &field.ty; + + if is_option_type(field_type) { + // For Option fields, merge by taking the other value if self is None + quote! { + if let Some(other_value) = other.#field_name.as_ref() { + if self.#field_name.is_none() { + self.#field_name = Some(other_value.clone()); + } else if let Some(self_value) = self.#field_name.as_mut() { + self_value.merge_from(Some(other_value)); + } + } + } + } else { + // For non-Option fields, recursively call merge_from + quote! { + self.#field_name.merge_from(Some(&other.#field_name)); + } + } + }); + + quote! { + if let Some(other) = other { + #(#field_merges)* + } + } + } + Fields::Unnamed(fields) => { + let field_merges = fields.unnamed.iter().enumerate().map(|(i, field)| { + let field_index = syn::Index::from(i); + let field_type = &field.ty; + + if is_option_type(field_type) { + // For Option fields, merge by taking the other value if self is None + quote! { + if let Some(other_value) = other.#field_index.as_ref() { + if self.#field_index.is_none() { + self.#field_index = Some(other_value.clone()); + } else if let Some(self_value) = self.#field_index.as_mut() { + self_value.merge_from(Some(other_value)); + } + } + } + } else { + // For non-Option fields, recursively call merge_from + quote! { + self.#field_index.merge_from(Some(&other.#field_index)); + } + } + }); + + quote! { + if let Some(other) = other { + #(#field_merges)* + } + } + } + Fields::Unit => { + quote! { + // No fields to merge for unit structs + } + } + }, + Data::Enum(_) => { + quote! { + if let Some(other) = other { + *self = other.clone(); + } + } + } + Data::Union(_) => { + panic!("MergeFrom cannot be derived for unions"); + } + }; + + let expanded = quote! { + impl #impl_generics crate::merge_from::MergeFrom for #name #ty_generics #where_clause { + fn merge_from(&mut self, other: ::core::option::Option<&Self>) { + use crate::merge_from::MergeFrom as _; + #merge_body + } + } + }; + + TokenStream::from(expanded) +} + +/// Check if a type is `Option` +fn is_option_type(ty: &Type) -> bool { + match ty { + Type::Path(type_path) => { + if let Some(segment) = type_path.path.segments.last() { + segment.ident == "Option" + } else { + false + } + } + _ => false, + } +} diff --git a/crates/settings_ui_macros/src/settings_ui_macros.rs b/crates/settings_ui_macros/src/settings_ui_macros.rs deleted file mode 100644 index 971adbfe4dd2356a8e19185767a121810cadd991..0000000000000000000000000000000000000000 --- a/crates/settings_ui_macros/src/settings_ui_macros.rs +++ /dev/null @@ -1,612 +0,0 @@ -use heck::{ToSnakeCase as _, ToTitleCase as _}; -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_ui_macros::SettingsUi; -/// -/// #[derive(SettingsUi)] -/// #[settings_ui(group = "Standard")] -/// struct MySettings { -/// enabled: bool, -/// count: usize, -/// } -/// ``` -#[proc_macro_derive(SettingsUi, attributes(settings_ui))] -pub fn derive_settings_ui(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let name = &input.ident; - - // Handle generic parameters if present - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - - let mut group_name = Option::::None; - let mut path_name = Option::::None; - - for attr in &input.attrs { - if attr.path().is_ident("settings_ui") { - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("group") { - if group_name.is_some() { - return Err(meta.error("Only one 'group' path can be specified")); - } - meta.input.parse::()?; - let lit: LitStr = meta.input.parse()?; - group_name = Some(lit.value()); - } else if meta.path.is_ident("path") { - // 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, either with `path` in `settings_ui` or with `settings_key`")); - } - meta.input.parse::()?; - let lit: LitStr = meta.input.parse()?; - path_name = Some(lit.value()); - } else if meta.path.is_ident("render") { - // Just consume the tokens even if we don't use them here - meta.input.parse::()?; - let _lit: LitStr = meta.input.parse()?; - } - 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; - } - } - - let doc_str = parse_documentation_from_attrs(&input.attrs); - - let ui_item_fn_body = generate_ui_item_body(group_name.as_ref(), &input); - - // todo(settings_ui): make group name optional, repurpose group as tag indicating item is group, and have "title" tag for custom title - let title = group_name.unwrap_or(input.ident.to_string().to_title_case()); - - let ui_entry_fn_body = map_ui_item_to_entry( - path_name.as_deref(), - &title, - doc_str.as_deref(), - quote! { Self }, - ); - - let expanded = quote! { - impl #impl_generics settings::SettingsUi for #name #ty_generics #where_clause { - fn settings_ui_item() -> settings::SettingsUiItem { - #ui_item_fn_body - } - - fn settings_ui_entry() -> settings::SettingsUiEntry { - #ui_entry_fn_body - } - } - }; - - proc_macro::TokenStream::from(expanded) -} - -fn extract_type_from_option(ty: TokenStream) -> TokenStream { - match option_inner_type(ty.clone()) { - Some(inner_type) => inner_type, - None => ty, - } -} - -fn option_inner_type(ty: TokenStream) -> Option { - let ty = syn::parse2::(ty).ok()?; - let syn::Type::Path(path) = ty else { - return None; - }; - let segment = path.path.segments.last()?; - if segment.ident != "Option" { - return None; - } - let syn::PathArguments::AngleBracketed(args) = &segment.arguments else { - return None; - }; - let arg = args.args.first()?; - let syn::GenericArgument::Type(ty) = arg else { - return None; - }; - return Some(ty.to_token_stream()); -} - -fn map_ui_item_to_entry( - path: Option<&str>, - title: &str, - doc_str: Option<&str>, - ty: TokenStream, -) -> TokenStream { - // todo(settings_ui): does quote! just work with options? - let path = path.map_or_else(|| quote! {None}, |path| quote! {Some(#path)}); - let doc_str = doc_str.map_or_else(|| quote! {None}, |doc_str| quote! {Some(#doc_str)}); - let item = ui_item_from_type(ty); - quote! { - settings::SettingsUiEntry { - title: #title, - path: #path, - item: #item, - documentation: #doc_str, - } - } -} - -fn ui_item_from_type(ty: TokenStream) -> TokenStream { - let ty = extract_type_from_option(ty); - return trait_method_call(ty, quote! {settings::SettingsUi}, quote! {settings_ui_item}); -} - -fn trait_method_call( - ty: TokenStream, - trait_name: TokenStream, - method_name: TokenStream, -) -> TokenStream { - // doing the makes the error message better: - // -> "#ty Doesn't implement settings::SettingsUi" instead of "no item "settings_ui_item" for #ty" - // and ensures safety against name conflicts - // - // todo(settings_ui): Turn `Vec` into `Vec::` here as well - quote! { - <#ty as #trait_name>::#method_name() - } -} - -fn generate_ui_item_body(group_name: Option<&String>, input: &syn::DeriveInput) -> TokenStream { - match (group_name, &input.data) { - (_, Data::Union(_)) => unimplemented!("Derive SettingsUi for Unions"), - (None, Data::Struct(_)) => quote! { - settings::SettingsUiItem::None - }, - (Some(_), Data::Struct(data_struct)) => { - let parent_serde_attrs = parse_serde_attributes(&input.attrs); - item_group_from_fields(&data_struct.fields, &parent_serde_attrs) - } - (None, Data::Enum(data_enum)) => { - let serde_attrs = parse_serde_attributes(&input.attrs); - let render_as = parse_render_as(&input.attrs); - let length = data_enum.variants.len(); - - let mut variants = Vec::with_capacity(length); - let mut labels = Vec::with_capacity(length); - - for variant in &data_enum.variants { - // todo(settings_ui): Can #[serde(rename = )] be on enum variants? - let ident = variant.ident.clone().to_string(); - let variant_name = serde_attrs.rename_all.apply(&ident); - let title = variant_name.to_title_case(); - - variants.push(variant_name); - labels.push(title); - } - - let is_not_union = data_enum.variants.iter().all(|v| v.fields.is_empty()); - if is_not_union { - return match render_as { - RenderAs::ToggleGroup if length > 6 => { - panic!("Can't set toggle group with more than six entries"); - } - RenderAs::ToggleGroup => { - quote! { - settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::ToggleGroup{ variants: &[#(#variants),*], labels: &[#(#labels),*] }) - } - } - RenderAs::Default => { - quote! { - settings::SettingsUiItem::Single(settings::SettingsUiItemSingle::DropDown{ variants: &[#(#variants),*], labels: &[#(#labels),*] }) - } - } - }; - } - // else: Union! - let enum_name = &input.ident; - - let options = data_enum.variants.iter().map(|variant| { - if variant.fields.is_empty() { - return quote! {None}; - } - let name = &variant.ident; - let item = item_group_from_fields(&variant.fields, &serde_attrs); - // todo(settings_ui): documentation - return quote! { - Some(settings::SettingsUiEntry { - path: None, - title: stringify!(#name), - documentation: None, - item: #item, - }) - }; - }); - let defaults = data_enum.variants.iter().map(|variant| { - let variant_name = &variant.ident; - if variant.fields.is_empty() { - quote! { - serde_json::to_value(#enum_name::#variant_name).expect("Failed to serialize default value for #enum_name::#variant_name") - } - } else { - let fields = variant.fields.iter().enumerate().map(|(index, field)| { - let field_name = field.ident.as_ref().map_or_else(|| syn::Index::from(index).into_token_stream(), |ident| ident.to_token_stream()); - let field_type_is_option = option_inner_type(field.ty.to_token_stream()).is_some(); - let field_default = if field_type_is_option { - quote! { - None - } - } else { - quote! { - ::std::default::Default::default() - } - }; - - quote!{ - #field_name: #field_default - } - }); - quote! { - serde_json::to_value(#enum_name::#variant_name { - #(#fields),* - }).expect("Failed to serialize default value for #enum_name::#variant_name") - } - } - }); - // todo(settings_ui): Identify #[default] attr and use it for index, defaulting to 0 - let default_variant_index: usize = 0; - let determine_option_fn = { - let match_arms = data_enum - .variants - .iter() - .enumerate() - .map(|(index, variant)| { - let variant_name = &variant.ident; - quote! { - Ok(#variant_name {..}) => #index - } - }); - quote! { - |value: &serde_json::Value, _cx: &gpui::App| -> usize { - use #enum_name::*; - match serde_json::from_value::<#enum_name>(value.clone()) { - #(#match_arms),*, - Err(_) => #default_variant_index, - } - } - } - }; - // todo(settings_ui) should probably always use toggle group for unions, dropdown makes less sense - return quote! { - settings::SettingsUiItem::Union(settings::SettingsUiItemUnion { - defaults: Box::new([#(#defaults),*]), - labels: &[#(#labels),*], - options: Box::new([#(#options),*]), - determine_option: #determine_option_fn, - }) - }; - // panic!("Unhandled"); - } - // todo(settings_ui) discriminated unions - (_, Data::Enum(_)) => quote! { - settings::SettingsUiItem::None - }, - } -} - -fn item_group_from_fields(fields: &syn::Fields, parent_serde_attrs: &SerdeOptions) -> TokenStream { - let group_items = 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| { - let field_serde_attrs = parse_serde_attributes(&field.attrs); - let name = field.ident.as_ref().map(ToString::to_string); - let title = name.as_ref().map_or_else( - || "todo(settings_ui): Titles for tuple fields".to_string(), - |name| name.to_title_case(), - ); - let doc_str = parse_documentation_from_attrs(&field.attrs); - - ( - title, - doc_str, - name.filter(|_| !field_serde_attrs.flatten).map(|name| { - parent_serde_attrs.apply_rename_to_field(&field_serde_attrs, &name) - }), - field.ty.to_token_stream(), - ) - }) - // todo(settings_ui): Re-format field name as nice title, and support setting different title with attr - .map(|(title, doc_str, path, ty)| { - map_ui_item_to_entry(path.as_deref(), &title, doc_str.as_deref(), ty) - }); - - quote! { - settings::SettingsUiItem::Group(settings::SettingsUiItemGroup{ items: vec![#(#group_items),*] }) - } -} - -struct SerdeOptions { - rename_all: SerdeRenameAll, - rename: Option, - flatten: bool, - untagged: bool, - _alias: Option, // todo(settings_ui) -} - -#[derive(PartialEq)] -enum SerdeRenameAll { - Lowercase, - SnakeCase, - None, -} - -impl SerdeRenameAll { - fn apply(&self, name: &str) -> String { - match self { - SerdeRenameAll::Lowercase => name.to_lowercase(), - SerdeRenameAll::SnakeCase => name.to_snake_case(), - SerdeRenameAll::None => name.to_string(), - } - } -} - -impl SerdeOptions { - fn apply_rename_to_field(&self, field_options: &Self, name: &str) -> String { - // field renames take precedence over struct rename all cases - if let Some(rename) = &field_options.rename { - return rename.clone(); - } - return self.rename_all.apply(name); - } -} - -enum RenderAs { - ToggleGroup, - Default, -} - -fn parse_render_as(attrs: &[syn::Attribute]) -> RenderAs { - let mut render_as = RenderAs::Default; - - for attr in attrs { - if !attr.path().is_ident("settings_ui") { - continue; - } - - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("render") { - meta.input.parse::()?; - let lit = meta.input.parse::()?.value(); - - if lit == "toggle_group" { - render_as = RenderAs::ToggleGroup; - } else { - return Err(meta.error(format!("invalid `render` attribute: {}", lit))); - } - } - Ok(()) - }) - .unwrap(); - } - - render_as -} - -fn parse_serde_attributes(attrs: &[syn::Attribute]) -> SerdeOptions { - let mut options = SerdeOptions { - rename_all: SerdeRenameAll::None, - rename: None, - flatten: false, - untagged: false, - _alias: None, - }; - - for attr in attrs { - if !attr.path().is_ident("serde") { - continue; - } - attr.parse_nested_meta(|meta| { - if meta.path.is_ident("rename_all") { - meta.input.parse::()?; - let lit = meta.input.parse::()?.value(); - - if options.rename_all != SerdeRenameAll::None { - return Err(meta.error("duplicate `rename_all` attribute")); - } else if lit == "lowercase" { - options.rename_all = SerdeRenameAll::Lowercase; - } else if lit == "snake_case" { - options.rename_all = SerdeRenameAll::SnakeCase; - } else { - return Err(meta.error(format!("invalid `rename_all` attribute: {}", lit))); - } - // todo(settings_ui): Other options? - } else if meta.path.is_ident("flatten") { - options.flatten = true; - } else if meta.path.is_ident("rename") { - if options.rename.is_some() { - return Err(meta.error("Can only have one rename attribute")); - } - - meta.input.parse::()?; - let lit = meta.input.parse::()?.value(); - options.rename = Some(lit); - } else if meta.path.is_ident("untagged") { - options.untagged = true; - } - Ok(()) - }) - .unwrap(); - } - - return options; -} - -fn parse_documentation_from_attrs(attrs: &[syn::Attribute]) -> Option { - let mut doc_str = Option::::None; - for attr in attrs { - if attr.path().is_ident("doc") { - // /// ... - // becomes - // #[doc = "..."] - use syn::{Expr::Lit, ExprLit, Lit::Str, Meta, MetaNameValue}; - if let Meta::NameValue(MetaNameValue { - value: - Lit(ExprLit { - lit: Str(ref lit_str), - .. - }), - .. - }) = attr.meta - { - let doc = lit_str.value(); - let doc_str = doc_str.get_or_insert_default(); - doc_str.push_str(doc.trim()); - doc_str.push('\n'); - } - } - } - return doc_str; -} - -struct SettingsKey { - key: Option, - fallback_key: Option, -} - -fn parse_setting_key_attr(attr: &syn::Attribute) -> Option { - 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::()?; - 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::()?; - 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::::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 = settings_key.or(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); - } -} diff --git a/crates/terminal/src/terminal_settings.rs b/crates/terminal/src/terminal_settings.rs index 9aa88c65a79032e96c9fe2951b2b37434be6927b..91a65f386fdb21556ea7fac8c2c3d26187f162c8 100644 --- a/crates/terminal/src/terminal_settings.rs +++ b/crates/terminal/src/terminal_settings.rs @@ -13,7 +13,6 @@ use settings::{ }; use task::Shell; use theme::FontFamilyName; -use util::MergeFrom; #[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] pub struct Toolbar { @@ -73,7 +72,7 @@ fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell { } impl settings::Settings for TerminalSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let content = content.terminal.clone().unwrap(); TerminalSettings { shell: settings_shell_to_task_shell(content.shell.unwrap()), @@ -108,80 +107,12 @@ impl settings::Settings for TerminalSettings { breadcrumbs: content.toolbar.unwrap().breadcrumbs.unwrap(), }, scrollbar: ScrollbarSettings { - show: content.scrollbar.unwrap().show.flatten(), + show: content.scrollbar.unwrap().show, }, minimum_contrast: content.minimum_contrast.unwrap(), } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(content) = &content.terminal else { - return; - }; - self.shell - .merge_from(&content.shell.clone().map(settings_shell_to_task_shell)); - self.working_directory - .merge_from(&content.working_directory); - if let Some(font_size) = content.font_size.map(px) { - self.font_size = Some(font_size) - } - if let Some(font_family) = content.font_family.clone() { - self.font_family = Some(font_family); - } - if let Some(fallbacks) = content.font_fallbacks.clone() { - self.font_fallbacks = Some(FontFallbacks::from_fonts( - fallbacks - .into_iter() - .map(|family| family.0.to_string()) - .collect(), - )) - } - if let Some(font_features) = content.font_features.clone() { - self.font_features = Some(font_features) - } - if let Some(font_weight) = content.font_weight { - self.font_weight = Some(FontWeight(font_weight)); - } - self.line_height.merge_from(&content.line_height); - if let Some(env) = &content.env { - for (key, value) in env { - self.env.insert(key.clone(), value.clone()); - } - } - if let Some(cursor_shape) = content.cursor_shape { - self.cursor_shape = Some(cursor_shape.into()) - } - self.blinking.merge_from(&content.blinking); - self.alternate_scroll.merge_from(&content.alternate_scroll); - self.option_as_meta.merge_from(&content.option_as_meta); - self.copy_on_select.merge_from(&content.copy_on_select); - self.keep_selection_on_copy - .merge_from(&content.keep_selection_on_copy); - self.button.merge_from(&content.button); - self.dock.merge_from(&content.dock); - self.default_width - .merge_from(&content.default_width.map(px)); - self.default_height - .merge_from(&content.default_height.map(px)); - self.detect_venv.merge_from(&content.detect_venv); - if let Some(max_scroll_history_lines) = content.max_scroll_history_lines { - self.max_scroll_history_lines = Some(max_scroll_history_lines) - } - self.toolbar.breadcrumbs.merge_from( - &content - .toolbar - .as_ref() - .and_then(|toolbar| toolbar.breadcrumbs), - ); - self.scrollbar.show.merge_from( - &content - .scrollbar - .as_ref() - .and_then(|scrollbar| scrollbar.show), - ); - self.minimum_contrast.merge_from(&content.minimum_contrast); - } - fn import_from_vscode(vscode: &settings::VsCodeSettings, content: &mut SettingsContent) { let mut default = TerminalSettingsContent::default(); let current = content.terminal.as_mut().unwrap_or(&mut default); diff --git a/crates/theme/Cargo.toml b/crates/theme/Cargo.toml index 8c51b991ccc8580d2be3d38273a7c11118b37d52..9f84b523af48e5e2645ea7b850f526db2c2ebb7f 100644 --- a/crates/theme/Cargo.toml +++ b/crates/theme/Cargo.toml @@ -24,7 +24,6 @@ fs.workspace = true futures.workspace = true gpui.workspace = true indexmap.workspace = true -inventory.workspace = true log.workspace = true palette = { workspace = true, default-features = false, features = ["std"] } parking_lot.workspace = true diff --git a/crates/theme/src/settings.rs b/crates/theme/src/settings.rs index 2efaa4f20c316952a300e669e8b93eee8e5bad39..e8ae1eed3cd7ca49ec946645160b98732be83884 100644 --- a/crates/theme/src/settings.rs +++ b/crates/theme/src/settings.rs @@ -7,17 +7,16 @@ use crate::{ use collections::HashMap; use derive_more::{Deref, DerefMut}; use gpui::{ - App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, SharedString, - Subscription, Window, px, + App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, Subscription, Window, + px, }; use refineable::Refineable; -use schemars::{JsonSchema, json_schema}; +use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub use settings::{FontFamilyName, IconThemeName, ThemeMode, ThemeName}; -use settings::{ParameterizedJsonSchema, Settings, SettingsContent}; +use settings::{Settings, SettingsContent}; use std::sync::Arc; -use util::schemars::replace_subschema; -use util::{MergeFrom, ResultExt as _}; +use util::ResultExt as _; const MIN_FONT_SIZE: Pixels = px(6.0); const MAX_FONT_SIZE: Pixels = px(100.0); @@ -270,45 +269,6 @@ pub struct AgentFontSize(Pixels); impl Global for AgentFontSize {} -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, _params, cx| { - replace_subschema::(generator, || json_schema!({ - "type": "string", - "enum": ThemeRegistry::global(cx).list_names(), - })) - } - } -} - -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, _params, cx| { - replace_subschema::(generator, || json_schema!({ - "type": "string", - "enum": ThemeRegistry::global(cx) - .list_icon_themes() - .into_iter() - .map(|icon_theme| icon_theme.name) - .collect::>(), - })) - } - } -} - -inventory::submit! { - ParameterizedJsonSchema { - add_and_get_ref: |generator, params, _cx| { - replace_subschema::(generator, || { - json_schema!({ - "type": "string", - "enum": params.font_names, - }) - }) - } - } -} - /// Represents the selection of a theme, which can be either static or dynamic. #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)] #[serde(untagged)] @@ -807,20 +767,20 @@ pub fn font_fallbacks_from_settings( } impl settings::Settings for ThemeSettings { - fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, cx: &mut App) -> Self { let content = &content.theme; // todo(settings_refactor). This should *not* require cx... let themes = ThemeRegistry::default_global(cx); let system_appearance = SystemAppearance::default_global(cx); let theme_selection: ThemeSelection = content.theme.clone().unwrap().into(); let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into(); - Self { - ui_font_size: content.ui_font_size.unwrap().into(), + let mut this = Self { + ui_font_size: clamp_font_size(content.ui_font_size.unwrap().into()), ui_font: Font { family: content.ui_font_family.as_ref().unwrap().0.clone().into(), features: content.ui_font_features.clone().unwrap(), fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()), - weight: content.ui_font_weight.map(FontWeight).unwrap(), + weight: clamp_font_weight(content.ui_font_weight.unwrap()), style: Default::default(), }, buffer_font: Font { @@ -833,12 +793,12 @@ impl settings::Settings for ThemeSettings { .into(), features: content.buffer_font_features.clone().unwrap(), fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()), - weight: content.buffer_font_weight.map(FontWeight).unwrap(), + weight: clamp_font_weight(content.buffer_font_weight.unwrap()), style: FontStyle::default(), }, - buffer_font_size: content.buffer_font_size.unwrap().into(), + buffer_font_size: clamp_font_size(content.buffer_font_size.unwrap().into()), buffer_line_height: content.buffer_line_height.unwrap().into(), - agent_font_size: content.agent_font_size.flatten().map(Into::into), + agent_font_size: content.agent_font_size.map(Into::into), active_theme: themes .get(theme_selection.theme(*system_appearance)) .or(themes.get(&zed_default_dark().name)) @@ -852,110 +812,10 @@ impl settings::Settings for ThemeSettings { .unwrap(), icon_theme_selection: Some(icon_theme_selection), ui_density: content.ui_density.unwrap_or_default().into(), - unnecessary_code_fade: content.unnecessary_code_fade.unwrap(), - } - } - - fn refine(&mut self, content: &SettingsContent, cx: &mut App) { - let value = &content.theme; - - let themes = ThemeRegistry::default_global(cx); - let system_appearance = SystemAppearance::default_global(cx); - - self.ui_density - .merge_from(&value.ui_density.map(Into::into)); - - if let Some(value) = value.buffer_font_family.clone() { - self.buffer_font.family = value.0.into(); - } - if let Some(value) = value.buffer_font_features.clone() { - self.buffer_font.features = value; - } - if let Some(value) = value.buffer_font_fallbacks.clone() { - self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value)); - } - if let Some(value) = value.buffer_font_weight { - self.buffer_font.weight = clamp_font_weight(value); - } - - if let Some(value) = value.ui_font_family.clone() { - self.ui_font.family = value.0.into(); - } - if let Some(value) = value.ui_font_features.clone() { - self.ui_font.features = value; - } - if let Some(value) = value.ui_font_fallbacks.clone() { - self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value)); - } - if let Some(value) = value.ui_font_weight { - self.ui_font.weight = clamp_font_weight(value); - } - - if let Some(value) = &value.theme { - self.theme_selection = Some(value.clone().into()); - - let theme_name = self - .theme_selection - .as_ref() - .unwrap() - .theme(*system_appearance); - - match themes.get(theme_name) { - Ok(theme) => { - self.active_theme = theme; - } - Err(err @ ThemeNotFoundError(_)) => { - if themes.extensions_loaded() { - log::error!("{err}"); - } - } - } - } - - self.experimental_theme_overrides - .clone_from(&value.experimental_theme_overrides); - self.theme_overrides.clone_from(&value.theme_overrides); - - self.apply_theme_overrides(); - - if let Some(value) = &value.icon_theme { - self.icon_theme_selection = Some(value.clone().into()); - - let icon_theme_name = self - .icon_theme_selection - .as_ref() - .unwrap() - .icon_theme(*system_appearance); - - match themes.get_icon_theme(icon_theme_name) { - Ok(icon_theme) => { - self.active_icon_theme = icon_theme; - } - Err(err @ IconThemeNotFoundError(_)) => { - if themes.extensions_loaded() { - log::error!("{err}"); - } - } - } - } - - self.ui_font_size - .merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size)); - self.buffer_font_size - .merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size)); - self.agent_font_size.merge_from( - &value - .agent_font_size - .map(|value| value.map(Into::into).map(clamp_font_size)), - ); - - self.buffer_line_height - .merge_from(&value.buffer_line_height.map(Into::into)); - - // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely. - self.unnecessary_code_fade - .merge_from(&value.unnecessary_code_fade); - self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9); + unnecessary_code_fade: content.unnecessary_code_fade.unwrap().clamp(0.0, 0.9), + }; + this.apply_theme_overrides(); + this } fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) { diff --git a/crates/title_bar/src/title_bar_settings.rs b/crates/title_bar/src/title_bar_settings.rs index 670d085756c5e27c45f8640e2d053a7f6929aead..41d74de611dd8b99f82b6a1a723c85c27b4a2d19 100644 --- a/crates/title_bar/src/title_bar_settings.rs +++ b/crates/title_bar/src/title_bar_settings.rs @@ -1,7 +1,6 @@ pub use settings::TitleBarVisibility; use settings::{Settings, SettingsContent}; use ui::App; -use util::MergeFrom; #[derive(Copy, Clone, Debug)] pub struct TitleBarSettings { @@ -16,7 +15,7 @@ pub struct TitleBarSettings { } impl Settings for TitleBarSettings { - fn from_defaults(s: &SettingsContent, _: &mut App) -> Self { + fn from_settings(s: &SettingsContent, _: &mut App) -> Self { let content = s.title_bar.clone().unwrap(); TitleBarSettings { show: content.show.unwrap(), @@ -29,24 +28,4 @@ impl Settings for TitleBarSettings { show_menus: content.show_menus.unwrap(), } } - - fn refine(&mut self, s: &SettingsContent, _: &mut App) { - let Some(content) = &s.title_bar else { - return; - }; - - self.show.merge_from(&content.show); - self.show_branch_icon.merge_from(&content.show_branch_icon); - self.show_onboarding_banner - .merge_from(&content.show_onboarding_banner); - self.show_user_picture - .merge_from(&content.show_user_picture); - self.show_branch_name.merge_from(&content.show_branch_name); - self.show_project_items - .merge_from(&content.show_project_items); - self.show_sign_in.merge_from(&content.show_sign_in); - self.show_menus.merge_from(&content.show_menus); - } - - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut SettingsContent) {} } diff --git a/crates/util/src/schemars.rs b/crates/util/src/schemars.rs index 22e0570cdb85efa82904153eda619b84b430eb61..9314eda4ac4d5003d7186c3115137e2e54c66794 100644 --- a/crates/util/src/schemars.rs +++ b/crates/util/src/schemars.rs @@ -18,9 +18,8 @@ pub fn replace_subschema( let schema_name = T::schema_name(); let definitions = generator.definitions_mut(); assert!(!definitions.contains_key(&format!("{schema_name}2"))); - if definitions.contains_key(schema_name.as_ref()) { - definitions.insert(schema_name.to_string(), schema().to_value()); - } + assert!(definitions.contains_key(schema_name.as_ref())); + definitions.insert(schema_name.to_string(), schema().to_value()); schemars::Schema::new_ref(format!("{DEFS_PATH}{schema_name}")) } diff --git a/crates/util/src/util.rs b/crates/util/src/util.rs index 3f3fcd0412e636c60669a6972b3c42bc0c0d7cef..ee18093784e4b0f2b78db9240d918685b6f01b6f 100644 --- a/crates/util/src/util.rs +++ b/crates/util/src/util.rs @@ -1390,21 +1390,3 @@ Line 3"# assert_eq!(result[1], (10..15, "world")); // '🦀' is 4 bytes } } - -pub fn refine(dest: &mut T, src: &Option) { - if let Some(src) = src { - *dest = src.clone() - } -} - -pub trait MergeFrom: Sized + Clone { - fn merge_from(&mut self, src: &Option); -} - -impl MergeFrom for T { - fn merge_from(&mut self, src: &Option) { - if let Some(src) = src { - *self = src.clone(); - } - } -} diff --git a/crates/vim/src/vim.rs b/crates/vim/src/vim.rs index 309806be02a1e283770276c26ad544af2bcedcba..9e7fb4a564751335db7ba6fe2afe61563ea0f161 100644 --- a/crates/vim/src/vim.rs +++ b/crates/vim/src/vim.rs @@ -45,7 +45,6 @@ use std::{mem, ops::Range, sync::Arc}; use surrounds::SurroundsType; use theme::ThemeSettings; use ui::{IntoElement, SharedString, px}; -use util::MergeFrom; use vim_mode_setting::HelixModeSetting; use vim_mode_setting::VimModeSetting; use workspace::{self, Pane, Workspace}; @@ -1845,7 +1844,7 @@ impl From for Mode { } impl Settings for VimSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let vim = content.vim.clone().unwrap(); Self { default_mode: vim.default_mode.unwrap().into(), @@ -1857,29 +1856,4 @@ impl Settings for VimSettings { cursor_shape: vim.cursor_shape.unwrap().into(), } } - - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(vim) = content.vim.as_ref() else { - return; - }; - self.default_mode - .merge_from(&vim.default_mode.map(Into::into)); - self.toggle_relative_line_numbers - .merge_from(&vim.toggle_relative_line_numbers); - self.use_system_clipboard - .merge_from(&vim.use_system_clipboard); - self.use_smartcase_find.merge_from(&vim.use_smartcase_find); - self.custom_digraphs.merge_from(&vim.custom_digraphs); - self.highlight_on_yank_duration - .merge_from(&vim.highlight_on_yank_duration); - self.cursor_shape - .merge_from(&vim.cursor_shape.map(Into::into)); - } - - fn import_from_vscode( - _vscode: &settings::VsCodeSettings, - _current: &mut settings::SettingsContent, - ) { - // TODO: translate vim extension settings - } } diff --git a/crates/vim_mode_setting/src/vim_mode_setting.rs b/crates/vim_mode_setting/src/vim_mode_setting.rs index 7c0a21e443fe908191bf3cff9d557a1bf38971b9..c82109f6b1f653c29942430fbf1fc557c09270fd 100644 --- a/crates/vim_mode_setting/src/vim_mode_setting.rs +++ b/crates/vim_mode_setting/src/vim_mode_setting.rs @@ -16,16 +16,10 @@ pub fn init(cx: &mut App) { pub struct VimModeSetting(pub bool); impl Settings for VimModeSetting { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { Self(content.vim_mode.unwrap()) } - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - if let Some(vim_mode) = content.vim_mode { - self.0 = vim_mode; - } - } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _content: &mut SettingsContent) { // TODO: could possibly check if any of the `vim.` keys are set? } @@ -34,15 +28,9 @@ impl Settings for VimModeSetting { pub struct HelixModeSetting(pub bool); impl Settings for HelixModeSetting { - fn from_defaults(content: &SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &SettingsContent, _cx: &mut App) -> Self { Self(content.helix_mode.unwrap()) } - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - if let Some(helix_mode) = content.helix_mode { - self.0 = helix_mode; - } - } - fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut SettingsContent) {} } diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index da4e557776072e33bd691c38d73b0c6623ac5a4c..83cadf5c6aa8c32630fb88cde7c75185b690437a 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -30,7 +30,7 @@ use std::{ }; use theme::Theme; use ui::{Color, Icon, IntoElement, Label, LabelCommon}; -use util::{MergeFrom as _, ResultExt}; +use util::ResultExt; pub const LEADER_UPDATE_THROTTLE: Duration = Duration::from_millis(200); @@ -65,7 +65,7 @@ pub struct PreviewTabsSettings { } impl Settings for ItemSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let tabs = content.tabs.as_ref().unwrap(); Self { git_status: tabs.git_status.unwrap(), @@ -77,18 +77,6 @@ impl Settings for ItemSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(tabs) = content.tabs.as_ref() else { - return; - }; - self.git_status.merge_from(&tabs.git_status); - self.close_position.merge_from(&tabs.close_position); - self.activate_on_close.merge_from(&tabs.activate_on_close); - self.file_icons.merge_from(&tabs.file_icons); - self.show_diagnostics.merge_from(&tabs.show_diagnostics); - self.show_close_button.merge_from(&tabs.show_close_button); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, @@ -125,7 +113,7 @@ impl Settings for ItemSettings { } impl Settings for PreviewTabsSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let preview_tabs = content.preview_tabs.as_ref().unwrap(); Self { enabled: preview_tabs.enabled.unwrap(), @@ -136,18 +124,6 @@ impl Settings for PreviewTabsSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(preview_tabs) = content.preview_tabs.as_ref() else { - return; - }; - - self.enabled.merge_from(&preview_tabs.enabled); - self.enable_preview_from_file_finder - .merge_from(&preview_tabs.enable_preview_from_file_finder); - self.enable_preview_from_code_navigation - .merge_from(&preview_tabs.enable_preview_from_code_navigation); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/workspace/src/workspace_settings.rs b/crates/workspace/src/workspace_settings.rs index 30bebb7c18b1f03e5bf59425026aea5a0f4bd552..dfef0bd77ded79b4a1c3037f1506f64d4c0fe54c 100644 --- a/crates/workspace/src/workspace_settings.rs +++ b/crates/workspace/src/workspace_settings.rs @@ -10,7 +10,6 @@ pub use settings::{ BottomDockLayout, PaneSplitDirectionHorizontal, PaneSplitDirectionVertical, RestoreOnStartupBehavior, }; -use util::MergeFrom as _; pub struct WorkspaceSettings { pub active_pane_modifiers: ActivePanelModifiers, @@ -63,7 +62,7 @@ pub struct TabBarSettings { } impl Settings for WorkspaceSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let workspace = &content.workspace; Self { active_pane_modifiers: ActivePanelModifiers { @@ -111,65 +110,6 @@ impl Settings for WorkspaceSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let workspace = &content.workspace; - if let Some(border_size) = workspace - .active_pane_modifiers - .and_then(|modifier| modifier.border_size) - { - self.active_pane_modifiers.border_size = Some(border_size); - } - - if let Some(inactive_opacity) = workspace - .active_pane_modifiers - .and_then(|modifier| modifier.inactive_opacity) - { - self.active_pane_modifiers.inactive_opacity = Some(inactive_opacity); - } - - self.bottom_dock_layout - .merge_from(&workspace.bottom_dock_layout); - self.pane_split_direction_horizontal - .merge_from(&workspace.pane_split_direction_horizontal); - self.pane_split_direction_vertical - .merge_from(&workspace.pane_split_direction_vertical); - self.centered_layout.merge_from(&workspace.centered_layout); - self.confirm_quit.merge_from(&workspace.confirm_quit); - self.show_call_status_icon - .merge_from(&workspace.show_call_status_icon); - self.autosave.merge_from(&workspace.autosave); - self.restore_on_startup - .merge_from(&workspace.restore_on_startup); - self.restore_on_file_reopen - .merge_from(&workspace.restore_on_file_reopen); - self.drop_target_size - .merge_from(&workspace.drop_target_size); - self.use_system_path_prompts - .merge_from(&workspace.use_system_path_prompts); - self.use_system_prompts - .merge_from(&workspace.use_system_prompts); - self.command_aliases - .extend(workspace.command_aliases.clone()); - if let Some(max_tabs) = workspace.max_tabs { - self.max_tabs = Some(max_tabs); - } - self.when_closing_with_no_tabs - .merge_from(&workspace.when_closing_with_no_tabs); - self.on_last_window_closed - .merge_from(&workspace.on_last_window_closed); - self.resize_all_panels_in_dock.merge_from( - &workspace - .resize_all_panels_in_dock - .as_ref() - .map(|resize| resize.clone().into_iter().map(Into::into).collect()), - ); - self.close_on_file_delete - .merge_from(&workspace.close_on_file_delete); - self.use_system_window_tabs - .merge_from(&workspace.use_system_window_tabs); - self.zoomed_padding.merge_from(&workspace.zoomed_padding); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, @@ -257,7 +197,7 @@ impl Settings for WorkspaceSettings { } impl Settings for TabBarSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let tab_bar = content.tab_bar.clone().unwrap(); TabBarSettings { show: tab_bar.show.unwrap(), @@ -266,17 +206,6 @@ impl Settings for TabBarSettings { } } - fn refine(&mut self, content: &settings::SettingsContent, _cx: &mut App) { - let Some(tab_bar) = &content.tab_bar else { - return; - }; - self.show.merge_from(&tab_bar.show); - self.show_nav_history_buttons - .merge_from(&tab_bar.show_nav_history_buttons); - self.show_tab_bar_buttons - .merge_from(&tab_bar.show_tab_bar_buttons); - } - fn import_from_vscode( vscode: &settings::VsCodeSettings, current: &mut settings::SettingsContent, diff --git a/crates/worktree/src/worktree_settings.rs b/crates/worktree/src/worktree_settings.rs index 5093988ac2ac66682aba9447a6347ecfc54ecf76..2d4246236e8c696661c1ed07544c1d73058fb1bc 100644 --- a/crates/worktree/src/worktree_settings.rs +++ b/crates/worktree/src/worktree_settings.rs @@ -31,11 +31,11 @@ impl WorktreeSettings { } impl Settings for WorktreeSettings { - fn from_defaults(content: &settings::SettingsContent, _cx: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _cx: &mut App) -> Self { let worktree = content.project.worktree.clone(); let file_scan_exclusions = worktree.file_scan_exclusions.unwrap(); let file_scan_inclusions = worktree.file_scan_inclusions.unwrap(); - let private_files = worktree.private_files.unwrap(); + let private_files = worktree.private_files.unwrap().0; let parsed_file_scan_inclusions: Vec = file_scan_inclusions .iter() .flat_map(|glob| { @@ -49,54 +49,16 @@ impl Settings for WorktreeSettings { Self { project_name: None, file_scan_exclusions: path_matchers(file_scan_exclusions, "file_scan_exclusions") - .unwrap(), + .log_err() + .unwrap_or_default(), file_scan_inclusions: path_matchers( parsed_file_scan_inclusions, "file_scan_inclusions", ) .unwrap(), - private_files: path_matchers(private_files, "private_files").unwrap(), - } - } - - fn refine(&mut self, content: &SettingsContent, _cx: &mut App) { - let worktree = &content.project.worktree; - - if let Some(project_name) = worktree.project_name.clone() { - self.project_name = Some(project_name); - } - - if let Some(mut private_files) = worktree.private_files.clone() { - let sources = self.private_files.sources(); - private_files.extend_from_slice(sources); - if let Some(matchers) = path_matchers(private_files, "private_files").log_err() { - self.private_files = matchers; - } - } - - if let Some(file_scan_exclusions) = worktree.file_scan_exclusions.clone() { - if let Some(matchers) = - path_matchers(file_scan_exclusions, "file_scan_exclusions").log_err() - { - self.file_scan_exclusions = matchers - } - } - - if let Some(file_scan_inclusions) = worktree.file_scan_inclusions.clone() { - let parsed_file_scan_inclusions: Vec = file_scan_inclusions - .iter() - .flat_map(|glob| { - Path::new(glob) - .ancestors() - .map(|a| a.to_string_lossy().into()) - }) - .filter(|p: &String| !p.is_empty()) - .collect(); - if let Some(matchers) = - path_matchers(parsed_file_scan_inclusions, "file_scan_inclusions").log_err() - { - self.file_scan_inclusions = matchers - } + private_files: path_matchers(private_files, "private_files") + .log_err() + .unwrap_or_default(), } } diff --git a/crates/zlog_settings/src/zlog_settings.rs b/crates/zlog_settings/src/zlog_settings.rs index 0d464b872067683e7fad8db6f4049eff92d5ed8b..cb564fcff3f024d37f0126c8870b436e449a0e1d 100644 --- a/crates/zlog_settings/src/zlog_settings.rs +++ b/crates/zlog_settings/src/zlog_settings.rs @@ -24,17 +24,11 @@ pub struct ZlogSettings { } impl Settings for ZlogSettings { - fn from_defaults(content: &settings::SettingsContent, _: &mut App) -> Self { + fn from_settings(content: &settings::SettingsContent, _: &mut App) -> Self { ZlogSettings { scopes: content.log.clone().unwrap(), } } - fn refine(&mut self, content: &settings::SettingsContent, _: &mut App) { - if let Some(log) = &content.log { - self.scopes.extend(log.clone()); - } - } - fn import_from_vscode(_: &settings::VsCodeSettings, _: &mut settings::SettingsContent) {} }