use anyhow::{anyhow, Context, Result};use collections::{btree_map, hash_map, BTreeMap, HashMap};use ec4rs::{ConfigParser, PropertiesSource, Section};use fs::Fs;use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal};use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME};use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};use serde::{de::DeserializeOwned, Deserialize as _, Serialize};use smallvec::SmallVec;use std::{ any::{type_name, Any, TypeId}, fmt::Debug, ops::Range, path::{Path, PathBuf}, str::{self, FromStr}, sync::{Arc, LazyLock},};use tree_sitter::Query;use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{SettingsJsonSchemaParams, WorktreeId}; /// A value that can be defined as a user setting.////// Settings can be loaded from a combination of multiple JSON files.pub trait Settings: 'static + Send + Sync { /// The name of 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>; /// The name of the keys in the [`FileContent`] that should always be written to /// a settings file, even if their value matches the default value. /// /// This is useful for tagged [`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; /// The type that is stored in an individual JSON file. type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema; /// The logic for combining together values from one or more JSON files into the /// final value for this setting. fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self> where Self: Sized; fn json_schema( generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams, _: &AppContext, ) -> RootSchema { generator.root_schema_for::<Self::FileContent>() } fn missing_default() -> anyhow::Error { anyhow::anyhow!("missing default") } #[track_caller] fn register(cx: &mut AppContext) where Self: Sized, { SettingsStore::update_global(cx, |store, cx| { store.register_setting::<Self>(cx); }); } #[track_caller] fn get<'a>(path: Option<SettingsLocation>, cx: &'a AppContext) -> &'a Self where Self: Sized, { cx.global::<SettingsStore>().get(path) } #[track_caller] fn get_global(cx: &AppContext) -> &Self where Self: Sized, { cx.global::<SettingsStore>().get(None) } #[track_caller] fn try_read_global<R>(cx: &AsyncAppContext, 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 AppContext) where Self: Sized, { cx.global_mut::<SettingsStore>().override_global(settings) }} #[derive(Clone, Copy, Debug)]pub struct SettingsSources<'a, T> { /// The default Zed settings. pub default: &'a T, /// Settings provided by extensions. pub extensions: Option<&'a T>, /// The user settings. pub user: Option<&'a T>, /// The user settings for the current release channel. pub release_channel: Option<&'a T>, /// The server's settings. pub server: Option<&'a T>, /// The project settings, ordered from least specific to most specific. pub project: &'a [&'a T],} impl<'a, T: Serialize> SettingsSources<'a, T> { /// Returns an iterator over the default settings as well as all settings customizations. pub fn defaults_and_customizations(&self) -> impl Iterator<Item = &T> { [self.default].into_iter().chain(self.customizations()) } /// Returns an iterator over all of the settings customizations. pub fn customizations(&self) -> impl Iterator<Item = &T> { self.extensions .into_iter() .chain(self.user) .chain(self.release_channel) .chain(self.server) .chain(self.project.iter().copied()) } /// Returns the settings after performing a JSON merge of the provided customizations. /// /// Customizations later in the iterator win out over the earlier ones. pub fn json_merge_with<O: DeserializeOwned>( customizations: impl Iterator<Item = &'a T>, ) -> Result<O> { let mut merged = serde_json::Value::Null; for value in customizations { merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); } Ok(serde_json::from_value(merged)?) } /// Returns the settings after performing a JSON merge of the customizations into the /// default settings. /// /// More-specific customizations win out over the less-specific ones. pub fn json_merge<O: DeserializeOwned>(&'a self) -> Result<O> { Self::json_merge_with(self.defaults_and_customizations()) }} #[derive(Clone, Copy, Debug)]pub struct SettingsLocation<'a> { pub worktree_id: WorktreeId, pub path: &'a Path,} /// A set of strongly-typed setting values defined via multiple config files.pub struct SettingsStore { setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>, raw_default_settings: serde_json::Value, raw_user_settings: serde_json::Value, raw_server_settings: Option<serde_json::Value>, raw_extension_settings: serde_json::Value, raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), serde_json::Value>, raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>, tab_size_callback: Option<( TypeId, Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>, )>, _setting_file_updates: Task<()>, setting_file_updates_tx: mpsc::UnboundedSender< Box<dyn FnOnce(AsyncAppContext) -> LocalBoxFuture<'static, Result<()>>>, >,} #[derive(Clone)]pub struct Editorconfig { pub is_root: bool, pub sections: SmallVec<[Section; 5]>,} impl FromStr for Editorconfig { type Err = anyhow::Error; fn from_str(contents: &str) -> Result<Self, Self::Err> { let parser = ConfigParser::new_buffered(contents.as_bytes()) .context("creating editorconfig parser")?; let is_root = parser.is_root; let sections = parser .collect::<Result<SmallVec<_>, _>>() .context("parsing editorconfig sections")?; Ok(Self { is_root, sections }) }} #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]pub enum LocalSettingsKind { Settings, Tasks, Editorconfig,} impl Global for SettingsStore {} #[derive(Debug)]struct SettingValue<T> { global_value: Option<T>, local_values: Vec<(WorktreeId, Arc<Path>, T)>,} trait AnySettingValue: 'static + Send + Sync { fn key(&self) -> Option<&'static str>; fn setting_type_name(&self) -> &'static str; fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>; fn load_setting( &self, sources: SettingsSources<DeserializedSetting>, cx: &mut AppContext, ) -> Result<Box<dyn Any>>; fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any; fn set_global_value(&mut self, value: Box<dyn Any>); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>); fn json_schema( &self, generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams, cx: &AppContext, ) -> RootSchema;} struct DeserializedSetting(Box<dyn Any>); impl SettingsStore { pub fn new(cx: &AppContext) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); Self { setting_values: Default::default(), raw_default_settings: serde_json::json!({}), raw_user_settings: serde_json::json!({}), raw_server_settings: None, raw_extension_settings: serde_json::json!({}), raw_local_settings: Default::default(), raw_editorconfig_settings: BTreeMap::default(), tab_size_callback: Default::default(), setting_file_updates_tx, _setting_file_updates: cx.spawn(|cx| async move { while let Some(setting_file_update) = setting_file_updates_rx.next().await { (setting_file_update)(cx.clone()).await.log_err(); } }), } } 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, cx: &mut AppContext) { let setting_type_id = TypeId::of::<T>(); let entry = self.setting_values.entry(setting_type_id); if matches!(entry, hash_map::Entry::Occupied(_)) { return; } let setting_value = entry.or_insert(Box::new(SettingValue::<T> { global_value: None, local_values: Vec::new(), })); if let Some(default_settings) = setting_value .deserialize_setting(&self.raw_default_settings) .log_err() { let user_value = setting_value .deserialize_setting(&self.raw_user_settings) .log_err(); let mut release_channel_value = None; if let Some(release_settings) = &self .raw_user_settings .get(release_channel::RELEASE_CHANNEL.dev_name()) { release_channel_value = setting_value .deserialize_setting(release_settings) .log_err(); } let server_value = self .raw_server_settings .as_ref() .and_then(|server_setting| { setting_value.deserialize_setting(server_setting).log_err() }); let extension_value = setting_value .deserialize_setting(&self.raw_extension_settings) .log_err(); if let Some(setting) = setting_value .load_setting( SettingsSources { default: &default_settings, extensions: extension_value.as_ref(), user: user_value.as_ref(), release_channel: release_channel_value.as_ref(), server: server_value.as_ref(), project: &[], }, cx, ) .context("A default setting must be added to the `default.json` file") .log_err() { setting_value.set_global_value(setting); } } } /// 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") } /// 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 as a raw JSON value. /// /// For user-facing functionality use the typed setting interface. /// (e.g. ProjectSettings::get_global(cx)) pub fn raw_user_settings(&self) -> &serde_json::Value { &self.raw_user_settings } #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Self { let mut this = Self::new(cx); this.set_default_settings(&crate::test_settings(), cx) .unwrap(); this.set_user_settings("{}", cx).unwrap(); this } /// 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<T: Settings>( &mut self, cx: &mut AppContext, update: impl FnOnce(&mut T::FileContent), ) { let old_text = serde_json::to_string(&self.raw_user_settings).unwrap(); let new_text = self.new_text_for_update::<T>(old_text, update); self.set_user_settings(&new_text, cx).unwrap(); } 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>() { if e.kind() == std::io::ErrorKind::NotFound { return Ok(crate::initial_user_settings_content().to_string()); } } Err(err) } } } pub fn update_settings_file<T: Settings>( &self, fs: Arc<dyn Fs>, update: impl 'static + Send + FnOnce(&mut T::FileContent, &AppContext), ) { self.setting_file_updates_tx .unbounded_send(Box::new(move |cx: AsyncAppContext| { async move { let old_text = Self::load_settings(&fs).await?; let new_text = cx.read_global(|store: &SettingsStore, cx| { store.new_text_for_update::<T>(old_text, |content| update(content, cx)) })?; let initial_path = paths::settings_file().as_path(); if fs.is_file(initial_path).await { let resolved_path = fs.canonicalize(initial_path).await.with_context(|| { format!("Failed to canonicalize settings path {:?}", initial_path) })?; fs.atomic_write(resolved_path.clone(), new_text) .await .with_context(|| { format!("Failed to write settings to file {:?}", resolved_path) })?; } else { fs.atomic_write(initial_path.to_path_buf(), new_text) .await .with_context(|| { format!("Failed to write settings to file {:?}", initial_path) })?; } anyhow::Ok(()) } .boxed_local() })) .ok(); } /// Updates the value of a setting in a JSON file, returning the new text /// for that JSON file. pub fn new_text_for_update<T: Settings>( &self, old_text: String, update: impl FnOnce(&mut T::FileContent), ) -> String { let edits = self.edits_for_update::<T>(&old_text, update); let mut new_text = old_text; for (range, replacement) in edits.into_iter() { new_text.replace_range(range, &replacement); } new_text } /// Updates the value of a setting in a JSON file, returning a list /// of edits to apply to the JSON file. pub fn edits_for_update<T: Settings>( &self, text: &str, update: impl FnOnce(&mut T::FileContent), ) -> Vec<(Range<usize>, String)> { let setting_type_id = TypeId::of::<T>(); let preserved_keys = T::PRESERVED_KEYS.unwrap_or_default(); let setting = self .setting_values .get(&setting_type_id) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())); let raw_settings = parse_json_with_comments::<serde_json::Value>(text).unwrap_or_default(); let old_content = match setting.deserialize_setting(&raw_settings) { Ok(content) => content.0.downcast::<T::FileContent>().unwrap(), Err(_) => Box::<<T as Settings>::FileContent>::default(), }; let mut new_content = old_content.clone(); update(&mut new_content); let old_value = serde_json::to_value(&old_content).unwrap(); let new_value = serde_json::to_value(new_content).unwrap(); let mut key_path = Vec::new(); if let Some(key) = T::KEY { key_path.push(key); } let mut edits = Vec::new(); let tab_size = self.json_tab_size(); let mut text = text.to_string(); update_value_in_json_text( &mut text, &mut key_path, tab_size, &old_value, &new_value, preserved_keys, &mut edits, ); editsLines 1–500 of 1774. Load more
use anyhow::{anyhow, Context, Result}; use collections::{btree_map, hash_map, BTreeMap, HashMap}; use ec4rs::{ConfigParser, PropertiesSource, Section}; use fs::Fs; use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt}; use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal}; use paths::{local_settings_file_relative_path, EDITORCONFIG_NAME}; use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema}; use serde::{de::DeserializeOwned, Deserialize as _, Serialize}; use smallvec::SmallVec; use std::{ any::{type_name, Any, TypeId}, fmt::Debug, ops::Range, path::{Path, PathBuf}, str::{self, FromStr}, sync::{Arc, LazyLock}, }; use tree_sitter::Query; use util::{merge_non_null_json_value_into, RangeExt, ResultExt as _}; pub type EditorconfigProperties = ec4rs::Properties; use crate::{SettingsJsonSchemaParams, WorktreeId}; /// A value that can be defined as a user setting. /// /// Settings can be loaded from a combination of multiple JSON files. pub trait Settings: 'static + Send + Sync { /// The name of 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>; /// The name of the keys in the [`FileContent`] that should always be written to /// a settings file, even if their value matches the default value. /// /// This is useful for tagged [`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; /// The type that is stored in an individual JSON file. type FileContent: Clone + Default + Serialize + DeserializeOwned + JsonSchema; /// The logic for combining together values from one or more JSON files into the /// final value for this setting. fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self> where Self: Sized; fn json_schema( generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams, _: &AppContext, ) -> RootSchema { generator.root_schema_for::<Self::FileContent>() } fn missing_default() -> anyhow::Error { anyhow::anyhow!("missing default") } #[track_caller] fn register(cx: &mut AppContext) where Self: Sized, { SettingsStore::update_global(cx, |store, cx| { store.register_setting::<Self>(cx); }); } #[track_caller] fn get<'a>(path: Option<SettingsLocation>, cx: &'a AppContext) -> &'a Self where Self: Sized, { cx.global::<SettingsStore>().get(path) } #[track_caller] fn get_global(cx: &AppContext) -> &Self where Self: Sized, { cx.global::<SettingsStore>().get(None) } #[track_caller] fn try_read_global<R>(cx: &AsyncAppContext, 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 AppContext) where Self: Sized, { cx.global_mut::<SettingsStore>().override_global(settings) } } #[derive(Clone, Copy, Debug)] pub struct SettingsSources<'a, T> { /// The default Zed settings. pub default: &'a T, /// Settings provided by extensions. pub extensions: Option<&'a T>, /// The user settings. pub user: Option<&'a T>, /// The user settings for the current release channel. pub release_channel: Option<&'a T>, /// The server's settings. pub server: Option<&'a T>, /// The project settings, ordered from least specific to most specific. pub project: &'a [&'a T], } impl<'a, T: Serialize> SettingsSources<'a, T> { /// Returns an iterator over the default settings as well as all settings customizations. pub fn defaults_and_customizations(&self) -> impl Iterator<Item = &T> { [self.default].into_iter().chain(self.customizations()) } /// Returns an iterator over all of the settings customizations. pub fn customizations(&self) -> impl Iterator<Item = &T> { self.extensions .into_iter() .chain(self.user) .chain(self.release_channel) .chain(self.server) .chain(self.project.iter().copied()) } /// Returns the settings after performing a JSON merge of the provided customizations. /// /// Customizations later in the iterator win out over the earlier ones. pub fn json_merge_with<O: DeserializeOwned>( customizations: impl Iterator<Item = &'a T>, ) -> Result<O> { let mut merged = serde_json::Value::Null; for value in customizations { merge_non_null_json_value_into(serde_json::to_value(value).unwrap(), &mut merged); } Ok(serde_json::from_value(merged)?) } /// Returns the settings after performing a JSON merge of the customizations into the /// default settings. /// /// More-specific customizations win out over the less-specific ones. pub fn json_merge<O: DeserializeOwned>(&'a self) -> Result<O> { Self::json_merge_with(self.defaults_and_customizations()) } } #[derive(Clone, Copy, Debug)] pub struct SettingsLocation<'a> { pub worktree_id: WorktreeId, pub path: &'a Path, } /// A set of strongly-typed setting values defined via multiple config files. pub struct SettingsStore { setting_values: HashMap<TypeId, Box<dyn AnySettingValue>>, raw_default_settings: serde_json::Value, raw_user_settings: serde_json::Value, raw_server_settings: Option<serde_json::Value>, raw_extension_settings: serde_json::Value, raw_local_settings: BTreeMap<(WorktreeId, Arc<Path>), serde_json::Value>, raw_editorconfig_settings: BTreeMap<(WorktreeId, Arc<Path>), (String, Option<Editorconfig>)>, tab_size_callback: Option<( TypeId, Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>, )>, _setting_file_updates: Task<()>, setting_file_updates_tx: mpsc::UnboundedSender< Box<dyn FnOnce(AsyncAppContext) -> LocalBoxFuture<'static, Result<()>>>, >, } #[derive(Clone)] pub struct Editorconfig { pub is_root: bool, pub sections: SmallVec<[Section; 5]>, } impl FromStr for Editorconfig { type Err = anyhow::Error; fn from_str(contents: &str) -> Result<Self, Self::Err> { let parser = ConfigParser::new_buffered(contents.as_bytes()) .context("creating editorconfig parser")?; let is_root = parser.is_root; let sections = parser .collect::<Result<SmallVec<_>, _>>() .context("parsing editorconfig sections")?; Ok(Self { is_root, sections }) } } #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum LocalSettingsKind { Settings, Tasks, Editorconfig, } impl Global for SettingsStore {} #[derive(Debug)] struct SettingValue<T> { global_value: Option<T>, local_values: Vec<(WorktreeId, Arc<Path>, T)>, } trait AnySettingValue: 'static + Send + Sync { fn key(&self) -> Option<&'static str>; fn setting_type_name(&self) -> &'static str; fn deserialize_setting(&self, json: &serde_json::Value) -> Result<DeserializedSetting>; fn load_setting( &self, sources: SettingsSources<DeserializedSetting>, cx: &mut AppContext, ) -> Result<Box<dyn Any>>; fn value_for_path(&self, path: Option<SettingsLocation>) -> &dyn Any; fn set_global_value(&mut self, value: Box<dyn Any>); fn set_local_value(&mut self, root_id: WorktreeId, path: Arc<Path>, value: Box<dyn Any>); fn json_schema( &self, generator: &mut SchemaGenerator, _: &SettingsJsonSchemaParams, cx: &AppContext, ) -> RootSchema; } struct DeserializedSetting(Box<dyn Any>); impl SettingsStore { pub fn new(cx: &AppContext) -> Self { let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded(); Self { setting_values: Default::default(), raw_default_settings: serde_json::json!({}), raw_user_settings: serde_json::json!({}), raw_server_settings: None, raw_extension_settings: serde_json::json!({}), raw_local_settings: Default::default(), raw_editorconfig_settings: BTreeMap::default(), tab_size_callback: Default::default(), setting_file_updates_tx, _setting_file_updates: cx.spawn(|cx| async move { while let Some(setting_file_update) = setting_file_updates_rx.next().await { (setting_file_update)(cx.clone()).await.log_err(); } }), } } 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, cx: &mut AppContext) { let setting_type_id = TypeId::of::<T>(); let entry = self.setting_values.entry(setting_type_id); if matches!(entry, hash_map::Entry::Occupied(_)) { return; } let setting_value = entry.or_insert(Box::new(SettingValue::<T> { global_value: None, local_values: Vec::new(), })); if let Some(default_settings) = setting_value .deserialize_setting(&self.raw_default_settings) .log_err() { let user_value = setting_value .deserialize_setting(&self.raw_user_settings) .log_err(); let mut release_channel_value = None; if let Some(release_settings) = &self .raw_user_settings .get(release_channel::RELEASE_CHANNEL.dev_name()) { release_channel_value = setting_value .deserialize_setting(release_settings) .log_err(); } let server_value = self .raw_server_settings .as_ref() .and_then(|server_setting| { setting_value.deserialize_setting(server_setting).log_err() }); let extension_value = setting_value .deserialize_setting(&self.raw_extension_settings) .log_err(); if let Some(setting) = setting_value .load_setting( SettingsSources { default: &default_settings, extensions: extension_value.as_ref(), user: user_value.as_ref(), release_channel: release_channel_value.as_ref(), server: server_value.as_ref(), project: &[], }, cx, ) .context("A default setting must be added to the `default.json` file") .log_err() { setting_value.set_global_value(setting); } } } /// 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") } /// 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 as a raw JSON value. /// /// For user-facing functionality use the typed setting interface. /// (e.g. ProjectSettings::get_global(cx)) pub fn raw_user_settings(&self) -> &serde_json::Value { &self.raw_user_settings } #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &mut AppContext) -> Self { let mut this = Self::new(cx); this.set_default_settings(&crate::test_settings(), cx) .unwrap(); this.set_user_settings("{}", cx).unwrap(); this } /// 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<T: Settings>( &mut self, cx: &mut AppContext, update: impl FnOnce(&mut T::FileContent), ) { let old_text = serde_json::to_string(&self.raw_user_settings).unwrap(); let new_text = self.new_text_for_update::<T>(old_text, update); self.set_user_settings(&new_text, cx).unwrap(); } 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>() { if e.kind() == std::io::ErrorKind::NotFound { return Ok(crate::initial_user_settings_content().to_string()); } } Err(err) } } } pub fn update_settings_file<T: Settings>( &self, fs: Arc<dyn Fs>, update: impl 'static + Send + FnOnce(&mut T::FileContent, &AppContext), ) { self.setting_file_updates_tx .unbounded_send(Box::new(move |cx: AsyncAppContext| { async move { let old_text = Self::load_settings(&fs).await?; let new_text = cx.read_global(|store: &SettingsStore, cx| { store.new_text_for_update::<T>(old_text, |content| update(content, cx)) })?; let initial_path = paths::settings_file().as_path(); if fs.is_file(initial_path).await { let resolved_path = fs.canonicalize(initial_path).await.with_context(|| { format!("Failed to canonicalize settings path {:?}", initial_path) })?; fs.atomic_write(resolved_path.clone(), new_text) .await .with_context(|| { format!("Failed to write settings to file {:?}", resolved_path) })?; } else { fs.atomic_write(initial_path.to_path_buf(), new_text) .await .with_context(|| { format!("Failed to write settings to file {:?}", initial_path) })?; } anyhow::Ok(()) } .boxed_local() })) .ok(); } /// Updates the value of a setting in a JSON file, returning the new text /// for that JSON file. pub fn new_text_for_update<T: Settings>( &self, old_text: String, update: impl FnOnce(&mut T::FileContent), ) -> String { let edits = self.edits_for_update::<T>(&old_text, update); let mut new_text = old_text; for (range, replacement) in edits.into_iter() { new_text.replace_range(range, &replacement); } new_text } /// Updates the value of a setting in a JSON file, returning a list /// of edits to apply to the JSON file. pub fn edits_for_update<T: Settings>( &self, text: &str, update: impl FnOnce(&mut T::FileContent), ) -> Vec<(Range<usize>, String)> { let setting_type_id = TypeId::of::<T>(); let preserved_keys = T::PRESERVED_KEYS.unwrap_or_default(); let setting = self .setting_values .get(&setting_type_id) .unwrap_or_else(|| panic!("unregistered setting type {}", type_name::<T>())); let raw_settings = parse_json_with_comments::<serde_json::Value>(text).unwrap_or_default(); let old_content = match setting.deserialize_setting(&raw_settings) { Ok(content) => content.0.downcast::<T::FileContent>().unwrap(), Err(_) => Box::<<T as Settings>::FileContent>::default(), }; let mut new_content = old_content.clone(); update(&mut new_content); let old_value = serde_json::to_value(&old_content).unwrap(); let new_value = serde_json::to_value(new_content).unwrap(); let mut key_path = Vec::new(); if let Some(key) = T::KEY { key_path.push(key); } let mut edits = Vec::new(); let tab_size = self.json_tab_size(); let mut text = text.to_string(); update_value_in_json_text( &mut text, &mut key_path, tab_size, &old_value, &new_value, preserved_keys, &mut edits, ); edits