settings.rs

  1mod font_size;
  2mod keymap_file;
  3mod settings_file;
  4mod settings_store;
  5
  6use anyhow::Result;
  7use gpui::{
  8    font_cache::{FamilyId, FontCache},
  9    fonts, AppContext, AssetSource,
 10};
 11use schemars::{
 12    gen::SchemaGenerator,
 13    schema::{InstanceType, Schema, SchemaObject},
 14    JsonSchema,
 15};
 16use serde::{Deserialize, Serialize};
 17use serde_json::Value;
 18use std::{borrow::Cow, str, sync::Arc};
 19use theme::{Theme, ThemeRegistry};
 20use util::ResultExt as _;
 21
 22pub use font_size::{adjust_font_size_delta, font_size_for_setting};
 23pub use keymap_file::{keymap_file_json_schema, KeymapFileContent};
 24pub use settings_file::*;
 25pub use settings_store::{Setting, SettingsJsonSchemaParams, SettingsStore};
 26
 27pub const DEFAULT_SETTINGS_ASSET_PATH: &str = "settings/default.json";
 28pub const INITIAL_USER_SETTINGS_ASSET_PATH: &str = "settings/initial_user_settings.json";
 29
 30#[derive(Clone)]
 31pub struct Settings {
 32    pub buffer_font_family_name: String,
 33    pub buffer_font_features: fonts::Features,
 34    pub buffer_font_family: FamilyId,
 35    pub buffer_font_size: f32,
 36    pub theme: Arc<Theme>,
 37}
 38
 39impl Setting for Settings {
 40    const KEY: Option<&'static str> = None;
 41
 42    type FileContent = SettingsFileContent;
 43
 44    fn load(
 45        defaults: &Self::FileContent,
 46        user_values: &[&Self::FileContent],
 47        cx: &AppContext,
 48    ) -> Result<Self> {
 49        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
 50        let themes = cx.global::<Arc<ThemeRegistry>>();
 51
 52        let mut this = Self {
 53            buffer_font_family: cx
 54                .font_cache()
 55                .load_family(
 56                    &[defaults.buffer_font_family.as_ref().unwrap()],
 57                    &buffer_font_features,
 58                )
 59                .unwrap(),
 60            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
 61            buffer_font_features,
 62            buffer_font_size: defaults.buffer_font_size.unwrap(),
 63            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
 64        };
 65
 66        for value in user_values.into_iter().copied().cloned() {
 67            this.set_user_settings(value, themes.as_ref(), cx.font_cache());
 68        }
 69
 70        Ok(this)
 71    }
 72
 73    fn json_schema(
 74        generator: &mut SchemaGenerator,
 75        params: &SettingsJsonSchemaParams,
 76    ) -> schemars::schema::RootSchema {
 77        let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
 78
 79        // Create a schema for a theme name.
 80        let theme_name_schema = SchemaObject {
 81            instance_type: Some(InstanceType::String.into()),
 82            enum_values: Some(
 83                params
 84                    .theme_names
 85                    .iter()
 86                    .cloned()
 87                    .map(Value::String)
 88                    .collect(),
 89            ),
 90            ..Default::default()
 91        };
 92
 93        root_schema
 94            .definitions
 95            .extend([("ThemeName".into(), theme_name_schema.into())]);
 96
 97        root_schema
 98            .schema
 99            .object
100            .as_mut()
101            .unwrap()
102            .properties
103            .extend([(
104                "theme".to_owned(),
105                Schema::new_ref("#/definitions/ThemeName".into()),
106            )]);
107
108        root_schema
109    }
110}
111
112#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
113pub struct SettingsFileContent {
114    #[serde(default)]
115    pub buffer_font_family: Option<String>,
116    #[serde(default)]
117    pub buffer_font_size: Option<f32>,
118    #[serde(default)]
119    pub buffer_font_features: Option<fonts::Features>,
120    #[serde(default)]
121    pub theme: Option<String>,
122}
123
124impl Settings {
125    pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
126        match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
127            Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
128            Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
129        }
130    }
131
132    /// Fill out the settings corresponding to the default.json file, overrides will be set later
133    pub fn defaults(
134        assets: impl AssetSource,
135        font_cache: &FontCache,
136        themes: &ThemeRegistry,
137    ) -> Self {
138        let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
139            str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
140        )
141        .unwrap();
142
143        let buffer_font_features = defaults.buffer_font_features.unwrap();
144        Self {
145            buffer_font_family: font_cache
146                .load_family(
147                    &[defaults.buffer_font_family.as_ref().unwrap()],
148                    &buffer_font_features,
149                )
150                .unwrap(),
151            buffer_font_family_name: defaults.buffer_font_family.unwrap(),
152            buffer_font_features,
153            buffer_font_size: defaults.buffer_font_size.unwrap(),
154            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
155        }
156    }
157
158    // Fill out the overrride and etc. settings from the user's settings.json
159    fn set_user_settings(
160        &mut self,
161        data: SettingsFileContent,
162        theme_registry: &ThemeRegistry,
163        font_cache: &FontCache,
164    ) {
165        let mut family_changed = false;
166        if let Some(value) = data.buffer_font_family {
167            self.buffer_font_family_name = value;
168            family_changed = true;
169        }
170        if let Some(value) = data.buffer_font_features {
171            self.buffer_font_features = value;
172            family_changed = true;
173        }
174        if family_changed {
175            if let Some(id) = font_cache
176                .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
177                .log_err()
178            {
179                self.buffer_font_family = id;
180            }
181        }
182
183        if let Some(value) = &data.theme {
184            if let Some(theme) = theme_registry.get(value).log_err() {
185                self.theme = theme;
186            }
187        }
188
189        merge(&mut self.buffer_font_size, data.buffer_font_size);
190    }
191
192    #[cfg(any(test, feature = "test-support"))]
193    pub fn test(cx: &gpui::AppContext) -> Settings {
194        Settings {
195            buffer_font_family_name: "Monaco".to_string(),
196            buffer_font_features: Default::default(),
197            buffer_font_family: cx
198                .font_cache()
199                .load_family(&["Monaco"], &Default::default())
200                .unwrap(),
201            buffer_font_size: 14.,
202            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
203        }
204    }
205
206    #[cfg(any(test, feature = "test-support"))]
207    pub fn test_async(cx: &mut gpui::TestAppContext) {
208        cx.update(|cx| {
209            let settings = Self::test(cx);
210            cx.set_global(settings);
211        });
212    }
213}
214
215fn merge<T: Copy>(target: &mut T, value: Option<T>) {
216    if let Some(value) = value {
217        *target = value;
218    }
219}