mod font_size; mod keymap_file; mod settings_file; mod settings_store; use anyhow::Result; use gpui::{ font_cache::{FamilyId, FontCache}, fonts, AppContext, AssetSource, }; use schemars::{ gen::SchemaGenerator, schema::{InstanceType, Schema, SchemaObject}, JsonSchema, }; use serde::{Deserialize, Serialize}; use serde_json::Value; use std::{borrow::Cow, str, sync::Arc}; use theme::{Theme, ThemeRegistry}; use util::ResultExt as _; pub use font_size::{adjust_font_size_delta, font_size_for_setting}; pub use keymap_file::{keymap_file_json_schema, KeymapFileContent}; pub use settings_file::*; pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore}; pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json"; pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json"; #[derive(Clone)] pub struct Settings { pub buffer_font_family_name: String, pub buffer_font_features: fonts::Features, pub buffer_font_family: FamilyId, pub buffer_font_size: f32, pub theme: Arc, } impl Setting for Settings { const KEY: Option<&'static str> = None; type FileContent = SettingsFileContent; fn load( defaults: &Self::FileContent, user_values: &[&Self::FileContent], cx: &AppContext, ) -> Result { let buffer_font_features = defaults.buffer_font_features.clone().unwrap(); let themes = cx.global::>(); let mut this = Self { buffer_font_family: cx .font_cache() .load_family( &[defaults.buffer_font_family.as_ref().unwrap()], &buffer_font_features, ) .unwrap(), buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(), }; for value in user_values.into_iter().copied().cloned() { this.set_user_settings(value, themes.as_ref(), cx.font_cache()); } Ok(this) } fn json_schema( generator: &mut SchemaGenerator, params: &SettingsJsonSchemaParams, ) -> schemars::schema::RootSchema { let mut root_schema = generator.root_schema_for::(); // Create a schema for a theme name. let theme_name_schema = SchemaObject { instance_type: Some(InstanceType::String.into()), enum_values: Some( params .theme_names .iter() .cloned() .map(Value::String) .collect(), ), ..Default::default() }; root_schema .definitions .extend([("ThemeName".into(), theme_name_schema.into())]); root_schema .schema .object .as_mut() .unwrap() .properties .extend([( "theme".to_owned(), Schema::new_ref("#/definitions/ThemeName".into()), )]); root_schema } } #[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)] pub struct SettingsFileContent { #[serde(default)] pub buffer_font_family: Option, #[serde(default)] pub buffer_font_size: Option, #[serde(default)] pub buffer_font_features: Option, #[serde(default)] pub theme: Option, } impl Settings { pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> { match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() { Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()), Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()), } } /// Fill out the settings corresponding to the default.json file, overrides will be set later pub fn defaults( assets: impl AssetSource, font_cache: &FontCache, themes: &ThemeRegistry, ) -> Self { let defaults: SettingsFileContent = settings_store::parse_json_with_comments( str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(), ) .unwrap(); let buffer_font_features = defaults.buffer_font_features.unwrap(); Self { buffer_font_family: font_cache .load_family( &[defaults.buffer_font_family.as_ref().unwrap()], &buffer_font_features, ) .unwrap(), buffer_font_family_name: defaults.buffer_font_family.unwrap(), buffer_font_features, buffer_font_size: defaults.buffer_font_size.unwrap(), theme: themes.get(&defaults.theme.unwrap()).unwrap(), } } // Fill out the overrride and etc. settings from the user's settings.json fn set_user_settings( &mut self, data: SettingsFileContent, theme_registry: &ThemeRegistry, font_cache: &FontCache, ) { let mut family_changed = false; if let Some(value) = data.buffer_font_family { self.buffer_font_family_name = value; family_changed = true; } if let Some(value) = data.buffer_font_features { self.buffer_font_features = value; family_changed = true; } if family_changed { if let Some(id) = font_cache .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features) .log_err() { self.buffer_font_family = id; } } if let Some(value) = &data.theme { if let Some(theme) = theme_registry.get(value).log_err() { self.theme = theme; } } merge(&mut self.buffer_font_size, data.buffer_font_size); } #[cfg(any(test, feature = "test-support"))] pub fn test(cx: &gpui::AppContext) -> Settings { Settings { buffer_font_family_name: "Monaco".to_string(), buffer_font_features: Default::default(), buffer_font_family: cx .font_cache() .load_family(&["Monaco"], &Default::default()) .unwrap(), buffer_font_size: 14., theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default), } } #[cfg(any(test, feature = "test-support"))] pub fn test_async(cx: &mut gpui::TestAppContext) { cx.update(|cx| { let settings = Self::test(cx); cx.set_global(settings); }); } } fn merge(target: &mut T, value: Option) { if let Some(value) = value { *target = value; } }