diff --git a/assets/settings/default.json b/assets/settings/default.json index 5c950e2469..c5ee98abf0 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -590,6 +590,13 @@ // } // "file_types": {}, + // The extensions that Zed should automatically install on startup. + // + // If you don't want any of these extensions, add this field to your settings + // and change the value to `false`. + "auto_install_extensions": { + "html": true + }, // Different settings for specific languages. "languages": { "C++": { diff --git a/crates/extension/src/extension_settings.rs b/crates/extension/src/extension_settings.rs index 42ee34930c..c11a6622b8 100644 --- a/crates/extension/src/extension_settings.rs +++ b/crates/extension/src/extension_settings.rs @@ -5,14 +5,29 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; use std::sync::Arc; +use util::merge_non_null_json_value_into; #[derive(Deserialize, Serialize, Debug, Default, Clone, JsonSchema)] pub struct ExtensionSettings { + /// The extensions that should be automatically installed by Zed. + /// + /// This is used to make functionality provided by extensions (e.g., language support) + /// available out-of-the-box. + #[serde(default)] + pub auto_install_extensions: HashMap, bool>, #[serde(default)] pub auto_update_extensions: HashMap, bool>, } impl ExtensionSettings { + /// Returns whether the given extension should be auto-installed. + pub fn should_auto_install(&self, extension_id: &str) -> bool { + self.auto_install_extensions + .get(extension_id) + .copied() + .unwrap_or(true) + } + pub fn should_auto_update(&self, extension_id: &str) -> bool { self.auto_update_extensions .get(extension_id) @@ -27,6 +42,10 @@ impl Settings for ExtensionSettings { type FileContent = Self; fn load(sources: SettingsSources, _cx: &mut AppContext) -> Result { - Ok(sources.user.cloned().unwrap_or_default()) + let mut merged = serde_json::Value::Null; + for value in [sources.default].into_iter().chain(sources.user) { + merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); + } + Ok(serde_json::from_value(merged)?) } } diff --git a/crates/extension/src/extension_store.rs b/crates/extension/src/extension_store.rs index a4bdcb215e..f6bd040c53 100644 --- a/crates/extension/src/extension_store.rs +++ b/crates/extension/src/extension_store.rs @@ -291,6 +291,8 @@ impl ExtensionStore { if let Some(future) = reload_future { future.await; } + this.update(&mut cx, |this, cx| this.auto_install_extensions(cx)) + .ok(); this.update(&mut cx, |this, cx| this.check_for_updates(cx)) .ok(); }) @@ -480,6 +482,38 @@ impl ExtensionStore { self.fetch_extensions_from_api(&format!("/extensions/{extension_id}"), &[], cx) } + /// Installs any extensions that should be included with Zed by default. + /// + /// This can be used to make certain functionality provided by extensions + /// available out-of-the-box. + pub fn auto_install_extensions(&mut self, cx: &mut ModelContext) { + let extension_settings = ExtensionSettings::get_global(cx); + + let extensions_to_install = extension_settings + .auto_install_extensions + .keys() + .filter(|extension_id| extension_settings.should_auto_install(extension_id)) + .filter(|extension_id| { + let is_already_installed = self + .extension_index + .extensions + .contains_key(extension_id.as_ref()); + !is_already_installed + }) + .cloned() + .collect::>(); + + cx.spawn(move |this, mut cx| async move { + for extension_id in extensions_to_install { + this.update(&mut cx, |this, cx| { + this.install_latest_extension(extension_id.clone(), cx); + }) + .ok(); + } + }) + .detach(); + } + pub fn check_for_updates(&mut self, cx: &mut ModelContext) { let task = self.fetch_extensions_with_update_available(cx); cx.spawn(move |this, mut cx| async move {