use anyhow::{Context as _, Result};use collections::{BTreeMap, HashMap, btree_map, hash_map};use fs::Fs;use futures::{ FutureExt, StreamExt, channel::{mpsc, oneshot}, future::LocalBoxFuture,};use gpui::{ App, AppContext, AsyncApp, BorrowAppContext, Entity, Global, SharedString, Task, UpdateGlobal,}; use paths::{local_settings_file_relative_path, task_file_name};use schemars::{JsonSchema, json_schema};use serde_json::Value;use settings_content::ParseStatus;use std::{ any::{Any, TypeId, type_name}, fmt::Debug, ops::Range, path::{Path, PathBuf}, rc::Rc, str, sync::Arc,};use util::{ ResultExt as _, rel_path::RelPath, schemars::{AllowTrailingCommas, DefaultDenyUnknownFields, replace_subschema},}; use crate::editorconfig_store::EditorconfigStore; use crate::{ ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent, LanguageToSettingsMap, LspSettings, LspSettingsMap, SemanticTokenRules, ThemeName, UserSettingsContentExt, VsCodeSettings, WorktreeId, settings_content::{ ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent, RootUserSettings, SettingsContent, UserSettingsContent, merge_from::MergeFrom, },}; use settings_json::{infer_json_indent_size, update_value_in_json_text}; pub const LSP_SETTINGS_SCHEMA_URL_PREFIX: &str = "zed://schemas/settings/lsp/"; pub trait SettingsKey: 'static + Send + Sync { /// The name of a key within the JSON file from which this setting should /// be deserialized. If this is `None`, then the setting will be deserialized /// from the root object. const KEY: Option<&'static str>; const FALLBACK_KEY: Option<&'static str> = None;} /// A value that can be defined as a user setting.////// Settings can be loaded from a combination of multiple JSON files.pub trait Settings: 'static + Send + Sync + Sized { /// The name of the keys in the [`FileContent`](Self::FileContent) that should /// always be written to a settings file, even if their value matches the default /// value. /// /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag /// is a "version" field that should always be persisted, even if the current /// user settings match the current version of the settings. 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_settings(content: &SettingsContent) -> Self; #[track_caller] fn register(cx: &mut App) where Self: Sized, { SettingsStore::update_global(cx, |store, _| { store.register_setting::<Self>(); }); } #[track_caller] fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self where Self: Sized, { cx.global::<SettingsStore>().get(path) } #[track_caller] fn get_global(cx: &App) -> &Self where Self: Sized, { cx.global::<SettingsStore>().get(None) } #[track_caller] fn try_get(cx: &App) -> Option<&Self> where Self: Sized, { if cx.has_global::<SettingsStore>() { cx.global::<SettingsStore>().try_get(None) } else { None } } #[track_caller] fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R> where Self: Sized, { cx.try_read_global(|s: &SettingsStore, _| f(s.get(None))) } #[track_caller] fn override_global(settings: Self, cx: &mut App) where Self: Sized, { cx.global_mut::<SettingsStore>().override_global(settings) }} pub struct RegisteredSetting { pub settings_value: fn() -> Box<dyn AnySettingValue>, pub from_settings: fn(&SettingsContent) -> Box<dyn Any>, pub id: fn() -> TypeId,} inventory::collect!(RegisteredSetting); #[derive(Clone, Copy, Debug)]pub struct SettingsLocation<'a> { pub worktree_id: WorktreeId, pub path: &'a RelPath,} pub struct SettingsStore { setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>, default_settings: Rc<SettingsContent>, user_settings: Option<UserSettingsContent>, global_settings: Option<Box<SettingsContent>>, extension_settings: Option<Box<SettingsContent>>, server_settings: Option<Box<SettingsContent>>, language_semantic_token_rules: HashMap<SharedString, SemanticTokenRules>, merged_settings: Rc<SettingsContent>, local_settings: BTreeMap<(WorktreeId, Arc<RelPath>), SettingsContent>, pub editorconfig_store: Entity<EditorconfigStore>, _setting_file_updates: Task<()>, setting_file_updates_tx: mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>, file_errors: BTreeMap<SettingsFile, SettingsParseResult>,} #[derive(Clone, PartialEq, Eq, Debug)]pub enum SettingsFile { Default, Global, User, Server, /// Represents project settings in ssh projects as well as local projects Project((WorktreeId, Arc<RelPath>)),} impl PartialOrd for SettingsFile { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }} /// Sorted in order of precedenceimpl Ord for SettingsFile { fn cmp(&self, other: &Self) -> std::cmp::Ordering { use SettingsFile::*; use std::cmp::Ordering; match (self, other) { (User, User) => Ordering::Equal, (Server, Server) => Ordering::Equal, (Default, Default) => Ordering::Equal, (Project((id1, rel_path1)), Project((id2, rel_path2))) => id1 .cmp(id2) .then_with(|| rel_path1.cmp(rel_path2).reverse()), (Project(_), _) => Ordering::Less, (_, Project(_)) => Ordering::Greater, (Server, _) => Ordering::Less, (_, Server) => Ordering::Greater, (User, _) => Ordering::Less, (_, User) => Ordering::Greater, (Global, _) => Ordering::Less, (_, Global) => Ordering::Greater, } }} #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]pub enum LocalSettingsKind { Settings, Tasks, Editorconfig, Debug,} #[derive(Clone, Debug, PartialEq, Eq, Hash)]pub enum LocalSettingsPath { InWorktree(Arc<RelPath>), OutsideWorktree(Arc<Path>),} impl LocalSettingsPath { pub fn is_outside_worktree(&self) -> bool { matches!(self, Self::OutsideWorktree(_)) } pub fn to_proto(&self) -> String { match self { Self::InWorktree(path) => path.to_proto(), Self::OutsideWorktree(path) => path.to_string_lossy().to_string(), } } pub fn from_proto(path: &str, is_outside_worktree: bool) -> anyhow::Result<Self> { if is_outside_worktree { Ok(Self::OutsideWorktree(PathBuf::from(path).into())) } else { Ok(Self::InWorktree(RelPath::from_proto(path)?)) } }} impl Global for SettingsStore {} #[derive(Default)]pub struct DefaultSemanticTokenRules(pub SemanticTokenRules); impl gpui::Global for DefaultSemanticTokenRules {} #[doc(hidden)]#[derive(Debug)]pub struct SettingValue<T> { #[doc(hidden)] pub global_value: Option<T>, #[doc(hidden)] pub local_values: Vec<(WorktreeId, Arc<RelPath>, T)>,} #[doc(hidden)]pub trait AnySettingValue: 'static + Send + Sync { fn setting_type_name(&self) -> &'static str; fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any>; fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any; fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)>; fn set_global_value(&mut self, value: Box<dyn Any>); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>); fn clear_local_values(&mut self, root_id: WorktreeId);} /// Parameters that are used when generating some JSON schemas at runtime.#[derive(Default)]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], pub lsp_adapter_names: &'a [String],} impl SettingsStore { pub fn new(cx: &mut App, default_settings: &str) -> Self { Self::new_with_semantic_tokens(cx, default_settings) } pub fn new_with_semantic_tokens(cx: &mut App, default_settings: &str) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); let default_settings: SettingsContent = SettingsContent::parse_json_with_comments(default_settings).unwrap(); if !cx.has_global::<DefaultSemanticTokenRules>() { cx.set_global::<DefaultSemanticTokenRules>( crate::parse_json_with_comments::<SemanticTokenRules>( &crate::default_semantic_token_rules(), ) .map(DefaultSemanticTokenRules) .unwrap_or_default(), ); } let default_settings: Rc<SettingsContent> = default_settings.into(); let mut this = Self { setting_values: Default::default(), default_settings: default_settings.clone(), global_settings: None, server_settings: None, user_settings: None, extension_settings: None, language_semantic_token_rules: HashMap::default(), merged_settings: default_settings, local_settings: BTreeMap::default(), editorconfig_store: cx.new(|_| EditorconfigStore::default()), setting_file_updates_tx, _setting_file_updates: cx.spawn(async move |cx| { while let Some(setting_file_update) = setting_file_updates_rx.next().await { (setting_file_update)(cx.clone()).await.log_err(); } }), file_errors: BTreeMap::default(), }; this.load_settings_types(); this } pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription { cx.observe_global::<ActiveSettingsProfileName>(|cx| { Self::update_global(cx, |store, cx| { store.recompute_values(None, cx); }); }) } pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R where C: BorrowAppContext, { cx.update_global(f) } /// Add a new type of setting to the store. pub fn register_setting<T: Settings>(&mut self) { self.register_setting_internal(&RegisteredSetting { settings_value: || { Box::new(SettingValue::<T> { global_value: None, local_values: Vec::new(), }) }, from_settings: |content| Box::new(T::from_settings(content)), id: || TypeId::of::<T>(), }); } fn load_settings_types(&mut self) { for registered_setting in inventory::iter::<RegisteredSetting>() { self.register_setting_internal(registered_setting); } } fn register_setting_internal(&mut self, registered_setting: &RegisteredSetting) { let entry = self.setting_values.entry((registered_setting.id)()); if matches!(entry, hash_map::Entry::Occupied(_)) { return; } let setting_value = entry.or_insert((registered_setting.settings_value)()); let value = (registered_setting.from_settings)(&self.merged_settings); setting_value.set_global_value(value); } pub fn merged_settings(&self) -> &SettingsContent { &self.merged_settings } /// Get the value of a setting. /// /// Panics if the given setting type has not been registered, or if there is no /// value for this setting. pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T { self.setting_values .get(&TypeId::of::<T>()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())) .value_for_path(path) .downcast_ref::<T>() .expect("no default value for setting type") } /// Get the value of a setting. /// /// Does not panic pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> { self.setting_values .get(&TypeId::of::<T>()) .map(|value| value.value_for_path(path)) .and_then(|value| value.downcast_ref::<T>()) } /// Get all values from project specific settings pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<RelPath>, &T)> { self.setting_values .get(&TypeId::of::<T>()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())) .all_local_values() .into_iter() .map(|(id, path, any)| { ( id, path, any.downcast_ref::<T>() .expect("wrong value type for setting"), ) }) .collect() } /// Override the global value for a setting. /// /// The given value will be overwritten if the user settings file changes. pub fn override_global<T: Settings>(&mut self, value: T) { self.setting_values .get_mut(&TypeId::of::<T>()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())) .set_global_value(Box::new(value)) } /// Get the user's settings content. /// /// For user-facing functionality use the typed setting interface. /// (e.g. ProjectSettings::get_global(cx)) pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> { self.user_settings.as_ref() } /// Get the default settings content as a raw JSON value. pub fn raw_default_settings(&self) -> &SettingsContent { &self.default_settings } /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> { self.user_settings .iter() .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str())) } #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut App) -> Self { Self::new(cx, &crate::test_settings()) } /// Updates the value of a setting in the user's global configuration. /// /// This is only for tests. Normally, settings are only loaded from /// JSON files. #[cfg(any(test, feature = "test-support"))] pub fn update_user_settings( &mut self, cx: &mut App, update: impl FnOnce(&mut SettingsContent), ) { let mut content = self.user_settings.clone().unwrap_or_default().content; update(&mut content); fn trail(this: &mut SettingsStore, content: Box<SettingsContent>, cx: &mut App) { let new_text = serde_json::to_string(&UserSettingsContent { content, ..Default::default() }) .unwrap(); _ = this.set_user_settings(&new_text, cx); } trail(self, content, cx); } pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> { match fs.load(paths::settings_file()).await { result @ Ok(_) => result, Err(err) => { if let Some(e) = err.downcast_ref::<std::io::Error>() && e.kind() == std::io::ErrorKind::NotFound { return Ok(crate::initial_user_settings_content().to_string()); } Err(err) } } } fn update_settings_file_inner( &self, fs: Arc<dyn Fs>, update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>, ) -> oneshot::Receiver<Result<()>> { let (tx, rx) = oneshot::channel::<Result<()>>(); self.setting_file_updates_tx .unbounded_send(Box::new(move |cx: AsyncApp| { async move { let res = async move { let old_text = Self::load_settings(&fs).await?;Lines 1–500 of 2658. Load more
use anyhow::{Context as _, Result}; use collections::{BTreeMap, HashMap, btree_map, hash_map}; use fs::Fs; use futures::{ FutureExt, StreamExt, channel::{mpsc, oneshot}, future::LocalBoxFuture, }; use gpui::{ App, AppContext, AsyncApp, BorrowAppContext, Entity, Global, SharedString, Task, UpdateGlobal, }; use paths::{local_settings_file_relative_path, task_file_name}; use schemars::{JsonSchema, json_schema}; use serde_json::Value; use settings_content::ParseStatus; use std::{ any::{Any, TypeId, type_name}, fmt::Debug, ops::Range, path::{Path, PathBuf}, rc::Rc, str, sync::Arc, }; use util::{ ResultExt as _, rel_path::RelPath, schemars::{AllowTrailingCommas, DefaultDenyUnknownFields, replace_subschema}, }; use crate::editorconfig_store::EditorconfigStore; use crate::{ ActiveSettingsProfileName, FontFamilyName, IconThemeName, LanguageSettingsContent, LanguageToSettingsMap, LspSettings, LspSettingsMap, SemanticTokenRules, ThemeName, UserSettingsContentExt, VsCodeSettings, WorktreeId, settings_content::{ ExtensionsSettingsContent, ProfileBase, ProjectSettingsContent, RootUserSettings, SettingsContent, UserSettingsContent, merge_from::MergeFrom, }, }; use settings_json::{infer_json_indent_size, update_value_in_json_text}; pub const LSP_SETTINGS_SCHEMA_URL_PREFIX: &str = "zed://schemas/settings/lsp/"; pub trait SettingsKey: 'static + Send + Sync { /// The name of a key within the JSON file from which this setting should /// be deserialized. If this is `None`, then the setting will be deserialized /// from the root object. const KEY: Option<&'static str>; const FALLBACK_KEY: Option<&'static str> = None; } /// A value that can be defined as a user setting. /// /// Settings can be loaded from a combination of multiple JSON files. pub trait Settings: 'static + Send + Sync + Sized { /// The name of the keys in the [`FileContent`](Self::FileContent) that should /// always be written to a settings file, even if their value matches the default /// value. /// /// This is useful for tagged [`FileContent`](Self::FileContent)s where the tag /// is a "version" field that should always be persisted, even if the current /// user settings match the current version of the settings. 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_settings(content: &SettingsContent) -> Self; #[track_caller] fn register(cx: &mut App) where Self: Sized, { SettingsStore::update_global(cx, |store, _| { store.register_setting::<Self>(); }); } #[track_caller] fn get<'a>(path: Option<SettingsLocation>, cx: &'a App) -> &'a Self where Self: Sized, { cx.global::<SettingsStore>().get(path) } #[track_caller] fn get_global(cx: &App) -> &Self where Self: Sized, { cx.global::<SettingsStore>().get(None) } #[track_caller] fn try_get(cx: &App) -> Option<&Self> where Self: Sized, { if cx.has_global::<SettingsStore>() { cx.global::<SettingsStore>().try_get(None) } else { None } } #[track_caller] fn try_read_global<R>(cx: &AsyncApp, f: impl FnOnce(&Self) -> R) -> Option<R> where Self: Sized, { cx.try_read_global(|s: &SettingsStore, _| f(s.get(None))) } #[track_caller] fn override_global(settings: Self, cx: &mut App) where Self: Sized, { cx.global_mut::<SettingsStore>().override_global(settings) } } pub struct RegisteredSetting { pub settings_value: fn() -> Box<dyn AnySettingValue>, pub from_settings: fn(&SettingsContent) -> Box<dyn Any>, pub id: fn() -> TypeId, } inventory::collect!(RegisteredSetting); #[derive(Clone, Copy, Debug)] pub struct SettingsLocation<'a> { pub worktree_id: WorktreeId, pub path: &'a RelPath, } pub struct SettingsStore { setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>, default_settings: Rc<SettingsContent>, user_settings: Option<UserSettingsContent>, global_settings: Option<Box<SettingsContent>>, extension_settings: Option<Box<SettingsContent>>, server_settings: Option<Box<SettingsContent>>, language_semantic_token_rules: HashMap<SharedString, SemanticTokenRules>, merged_settings: Rc<SettingsContent>, local_settings: BTreeMap<(WorktreeId, Arc<RelPath>), SettingsContent>, pub editorconfig_store: Entity<EditorconfigStore>, _setting_file_updates: Task<()>, setting_file_updates_tx: mpsc::UnboundedSender<Box<dyn FnOnce(AsyncApp) -> LocalBoxFuture<'static, Result<()>>>>, file_errors: BTreeMap<SettingsFile, SettingsParseResult>, } #[derive(Clone, PartialEq, Eq, Debug)] pub enum SettingsFile { Default, Global, User, Server, /// Represents project settings in ssh projects as well as local projects Project((WorktreeId, Arc<RelPath>)), } impl PartialOrd for SettingsFile { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) } } /// Sorted in order of precedence impl Ord for SettingsFile { fn cmp(&self, other: &Self) -> std::cmp::Ordering { use SettingsFile::*; use std::cmp::Ordering; match (self, other) { (User, User) => Ordering::Equal, (Server, Server) => Ordering::Equal, (Default, Default) => Ordering::Equal, (Project((id1, rel_path1)), Project((id2, rel_path2))) => id1 .cmp(id2) .then_with(|| rel_path1.cmp(rel_path2).reverse()), (Project(_), _) => Ordering::Less, (_, Project(_)) => Ordering::Greater, (Server, _) => Ordering::Less, (_, Server) => Ordering::Greater, (User, _) => Ordering::Less, (_, User) => Ordering::Greater, (Global, _) => Ordering::Less, (_, Global) => Ordering::Greater, } } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum LocalSettingsKind { Settings, Tasks, Editorconfig, Debug, } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum LocalSettingsPath { InWorktree(Arc<RelPath>), OutsideWorktree(Arc<Path>), } impl LocalSettingsPath { pub fn is_outside_worktree(&self) -> bool { matches!(self, Self::OutsideWorktree(_)) } pub fn to_proto(&self) -> String { match self { Self::InWorktree(path) => path.to_proto(), Self::OutsideWorktree(path) => path.to_string_lossy().to_string(), } } pub fn from_proto(path: &str, is_outside_worktree: bool) -> anyhow::Result<Self> { if is_outside_worktree { Ok(Self::OutsideWorktree(PathBuf::from(path).into())) } else { Ok(Self::InWorktree(RelPath::from_proto(path)?)) } } } impl Global for SettingsStore {} #[derive(Default)] pub struct DefaultSemanticTokenRules(pub SemanticTokenRules); impl gpui::Global for DefaultSemanticTokenRules {} #[doc(hidden)] #[derive(Debug)] pub struct SettingValue<T> { #[doc(hidden)] pub global_value: Option<T>, #[doc(hidden)] pub local_values: Vec<(WorktreeId, Arc<RelPath>, T)>, } #[doc(hidden)] pub trait AnySettingValue: 'static + Send + Sync { fn setting_type_name(&self) -> &'static str; fn from_settings(&self, s: &SettingsContent) -> Box<dyn Any>; fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any; fn all_local_values(&self) -> Vec<(WorktreeId, Arc<RelPath>, &dyn Any)>; fn set_global_value(&mut self, value: Box<dyn Any>); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<RelPath>, value: Box<dyn Any>); fn clear_local_values(&mut self, root_id: WorktreeId); } /// Parameters that are used when generating some JSON schemas at runtime. #[derive(Default)] 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], pub lsp_adapter_names: &'a [String], } impl SettingsStore { pub fn new(cx: &mut App, default_settings: &str) -> Self { Self::new_with_semantic_tokens(cx, default_settings) } pub fn new_with_semantic_tokens(cx: &mut App, default_settings: &str) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); let default_settings: SettingsContent = SettingsContent::parse_json_with_comments(default_settings).unwrap(); if !cx.has_global::<DefaultSemanticTokenRules>() { cx.set_global::<DefaultSemanticTokenRules>( crate::parse_json_with_comments::<SemanticTokenRules>( &crate::default_semantic_token_rules(), ) .map(DefaultSemanticTokenRules) .unwrap_or_default(), ); } let default_settings: Rc<SettingsContent> = default_settings.into(); let mut this = Self { setting_values: Default::default(), default_settings: default_settings.clone(), global_settings: None, server_settings: None, user_settings: None, extension_settings: None, language_semantic_token_rules: HashMap::default(), merged_settings: default_settings, local_settings: BTreeMap::default(), editorconfig_store: cx.new(|_| EditorconfigStore::default()), setting_file_updates_tx, _setting_file_updates: cx.spawn(async move |cx| { while let Some(setting_file_update) = setting_file_updates_rx.next().await { (setting_file_update)(cx.clone()).await.log_err(); } }), file_errors: BTreeMap::default(), }; this.load_settings_types(); this } pub fn observe_active_settings_profile_name(cx: &mut App) -> gpui::Subscription { cx.observe_global::<ActiveSettingsProfileName>(|cx| { Self::update_global(cx, |store, cx| { store.recompute_values(None, cx); }); }) } pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R where C: BorrowAppContext, { cx.update_global(f) } /// Add a new type of setting to the store. pub fn register_setting<T: Settings>(&mut self) { self.register_setting_internal(&RegisteredSetting { settings_value: || { Box::new(SettingValue::<T> { global_value: None, local_values: Vec::new(), }) }, from_settings: |content| Box::new(T::from_settings(content)), id: || TypeId::of::<T>(), }); } fn load_settings_types(&mut self) { for registered_setting in inventory::iter::<RegisteredSetting>() { self.register_setting_internal(registered_setting); } } fn register_setting_internal(&mut self, registered_setting: &RegisteredSetting) { let entry = self.setting_values.entry((registered_setting.id)()); if matches!(entry, hash_map::Entry::Occupied(_)) { return; } let setting_value = entry.or_insert((registered_setting.settings_value)()); let value = (registered_setting.from_settings)(&self.merged_settings); setting_value.set_global_value(value); } pub fn merged_settings(&self) -> &SettingsContent { &self.merged_settings } /// Get the value of a setting. /// /// Panics if the given setting type has not been registered, or if there is no /// value for this setting. pub fn get<T: Settings>(&self, path: Option<SettingsLocation>) -> &T { self.setting_values .get(&TypeId::of::<T>()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())) .value_for_path(path) .downcast_ref::<T>() .expect("no default value for setting type") } /// Get the value of a setting. /// /// Does not panic pub fn try_get<T: Settings>(&self, path: Option<SettingsLocation>) -> Option<&T> { self.setting_values .get(&TypeId::of::<T>()) .map(|value| value.value_for_path(path)) .and_then(|value| value.downcast_ref::<T>()) } /// Get all values from project specific settings pub fn get_all_locals<T: Settings>(&self) -> Vec<(WorktreeId, Arc<RelPath>, &T)> { self.setting_values .get(&TypeId::of::<T>()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())) .all_local_values() .into_iter() .map(|(id, path, any)| { ( id, path, any.downcast_ref::<T>() .expect("wrong value type for setting"), ) }) .collect() } /// Override the global value for a setting. /// /// The given value will be overwritten if the user settings file changes. pub fn override_global<T: Settings>(&mut self, value: T) { self.setting_values .get_mut(&TypeId::of::<T>()) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())) .set_global_value(Box::new(value)) } /// Get the user's settings content. /// /// For user-facing functionality use the typed setting interface. /// (e.g. ProjectSettings::get_global(cx)) pub fn raw_user_settings(&self) -> Option<&UserSettingsContent> { self.user_settings.as_ref() } /// Get the default settings content as a raw JSON value. pub fn raw_default_settings(&self) -> &SettingsContent { &self.default_settings } /// Get the configured settings profile names. pub fn configured_settings_profiles(&self) -> impl Iterator<Item = &str> { self.user_settings .iter() .flat_map(|settings| settings.profiles.keys().map(|k| k.as_str())) } #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut App) -> Self { Self::new(cx, &crate::test_settings()) } /// Updates the value of a setting in the user's global configuration. /// /// This is only for tests. Normally, settings are only loaded from /// JSON files. #[cfg(any(test, feature = "test-support"))] pub fn update_user_settings( &mut self, cx: &mut App, update: impl FnOnce(&mut SettingsContent), ) { let mut content = self.user_settings.clone().unwrap_or_default().content; update(&mut content); fn trail(this: &mut SettingsStore, content: Box<SettingsContent>, cx: &mut App) { let new_text = serde_json::to_string(&UserSettingsContent { content, ..Default::default() }) .unwrap(); _ = this.set_user_settings(&new_text, cx); } trail(self, content, cx); } pub async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> { match fs.load(paths::settings_file()).await { result @ Ok(_) => result, Err(err) => { if let Some(e) = err.downcast_ref::<std::io::Error>() && e.kind() == std::io::ErrorKind::NotFound { return Ok(crate::initial_user_settings_content().to_string()); } Err(err) } } } fn update_settings_file_inner( &self, fs: Arc<dyn Fs>, update: impl 'static + Send + FnOnce(String, AsyncApp) -> Result<String>, ) -> oneshot::Receiver<Result<()>> { let (tx, rx) = oneshot::channel::<Result<()>>(); self.setting_file_updates_tx .unbounded_send(Box::new(move |cx: AsyncApp| { async move { let res = async move { let old_text = Self::load_settings(&fs).await?;