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    pub base_keymap: BaseKeymap,
 38}
 39
 40impl Setting for Settings {
 41    const KEY: Option<&'static str> = None;
 42
 43    type FileContent = SettingsFileContent;
 44
 45    fn load(
 46        defaults: &Self::FileContent,
 47        user_values: &[&Self::FileContent],
 48        cx: &AppContext,
 49    ) -> Result<Self> {
 50        let buffer_font_features = defaults.buffer_font_features.clone().unwrap();
 51        let themes = cx.global::<Arc<ThemeRegistry>>();
 52
 53        let mut this = Self {
 54            buffer_font_family: cx
 55                .font_cache()
 56                .load_family(
 57                    &[defaults.buffer_font_family.as_ref().unwrap()],
 58                    &buffer_font_features,
 59                )
 60                .unwrap(),
 61            buffer_font_family_name: defaults.buffer_font_family.clone().unwrap(),
 62            buffer_font_features,
 63            buffer_font_size: defaults.buffer_font_size.unwrap(),
 64            theme: themes.get(defaults.theme.as_ref().unwrap()).unwrap(),
 65            base_keymap: Default::default(),
 66        };
 67
 68        for value in user_values.into_iter().copied().cloned() {
 69            this.set_user_settings(value, themes.as_ref(), cx.font_cache());
 70        }
 71
 72        Ok(this)
 73    }
 74
 75    fn json_schema(
 76        generator: &mut SchemaGenerator,
 77        params: &SettingsJsonSchemaParams,
 78    ) -> schemars::schema::RootSchema {
 79        let mut root_schema = generator.root_schema_for::<SettingsFileContent>();
 80
 81        // Create a schema for a theme name.
 82        let theme_name_schema = SchemaObject {
 83            instance_type: Some(InstanceType::String.into()),
 84            enum_values: Some(
 85                params
 86                    .theme_names
 87                    .iter()
 88                    .cloned()
 89                    .map(Value::String)
 90                    .collect(),
 91            ),
 92            ..Default::default()
 93        };
 94
 95        root_schema
 96            .definitions
 97            .extend([("ThemeName".into(), theme_name_schema.into())]);
 98
 99        root_schema
100            .schema
101            .object
102            .as_mut()
103            .unwrap()
104            .properties
105            .extend([(
106                "theme".to_owned(),
107                Schema::new_ref("#/definitions/ThemeName".into()),
108            )]);
109
110        root_schema
111    }
112}
113
114#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Default)]
115pub enum BaseKeymap {
116    #[default]
117    VSCode,
118    JetBrains,
119    SublimeText,
120    Atom,
121    TextMate,
122}
123
124impl BaseKeymap {
125    pub const OPTIONS: [(&'static str, Self); 5] = [
126        ("VSCode (Default)", Self::VSCode),
127        ("Atom", Self::Atom),
128        ("JetBrains", Self::JetBrains),
129        ("Sublime Text", Self::SublimeText),
130        ("TextMate", Self::TextMate),
131    ];
132
133    pub fn asset_path(&self) -> Option<&'static str> {
134        match self {
135            BaseKeymap::JetBrains => Some("keymaps/jetbrains.json"),
136            BaseKeymap::SublimeText => Some("keymaps/sublime_text.json"),
137            BaseKeymap::Atom => Some("keymaps/atom.json"),
138            BaseKeymap::TextMate => Some("keymaps/textmate.json"),
139            BaseKeymap::VSCode => None,
140        }
141    }
142
143    pub fn names() -> impl Iterator<Item = &'static str> {
144        Self::OPTIONS.iter().map(|(name, _)| *name)
145    }
146
147    pub fn from_names(option: &str) -> BaseKeymap {
148        Self::OPTIONS
149            .iter()
150            .copied()
151            .find_map(|(name, value)| (name == option).then(|| value))
152            .unwrap_or_default()
153    }
154}
155
156#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
157pub struct SettingsFileContent {
158    #[serde(default)]
159    pub buffer_font_family: Option<String>,
160    #[serde(default)]
161    pub buffer_font_size: Option<f32>,
162    #[serde(default)]
163    pub buffer_font_features: Option<fonts::Features>,
164    #[serde(default)]
165    pub theme: Option<String>,
166    #[serde(default)]
167    pub base_keymap: Option<BaseKeymap>,
168}
169
170impl Settings {
171    pub fn initial_user_settings_content(assets: &'static impl AssetSource) -> Cow<'static, str> {
172        match assets.load(INITIAL_USER_SETTINGS_ASSET_PATH).unwrap() {
173            Cow::Borrowed(s) => Cow::Borrowed(str::from_utf8(s).unwrap()),
174            Cow::Owned(s) => Cow::Owned(String::from_utf8(s).unwrap()),
175        }
176    }
177
178    /// Fill out the settings corresponding to the default.json file, overrides will be set later
179    pub fn defaults(
180        assets: impl AssetSource,
181        font_cache: &FontCache,
182        themes: &ThemeRegistry,
183    ) -> Self {
184        let defaults: SettingsFileContent = settings_store::parse_json_with_comments(
185            str::from_utf8(assets.load(DEFAULT_SETTINGS_ASSET_PATH).unwrap().as_ref()).unwrap(),
186        )
187        .unwrap();
188
189        let buffer_font_features = defaults.buffer_font_features.unwrap();
190        Self {
191            buffer_font_family: font_cache
192                .load_family(
193                    &[defaults.buffer_font_family.as_ref().unwrap()],
194                    &buffer_font_features,
195                )
196                .unwrap(),
197            buffer_font_family_name: defaults.buffer_font_family.unwrap(),
198            buffer_font_features,
199            buffer_font_size: defaults.buffer_font_size.unwrap(),
200            theme: themes.get(&defaults.theme.unwrap()).unwrap(),
201            base_keymap: Default::default(),
202        }
203    }
204
205    // Fill out the overrride and etc. settings from the user's settings.json
206    fn set_user_settings(
207        &mut self,
208        data: SettingsFileContent,
209        theme_registry: &ThemeRegistry,
210        font_cache: &FontCache,
211    ) {
212        let mut family_changed = false;
213        if let Some(value) = data.buffer_font_family {
214            self.buffer_font_family_name = value;
215            family_changed = true;
216        }
217        if let Some(value) = data.buffer_font_features {
218            self.buffer_font_features = value;
219            family_changed = true;
220        }
221        if family_changed {
222            if let Some(id) = font_cache
223                .load_family(&[&self.buffer_font_family_name], &self.buffer_font_features)
224                .log_err()
225            {
226                self.buffer_font_family = id;
227            }
228        }
229
230        if let Some(value) = &data.theme {
231            if let Some(theme) = theme_registry.get(value).log_err() {
232                self.theme = theme;
233            }
234        }
235
236        merge(&mut self.buffer_font_size, data.buffer_font_size);
237        merge(&mut self.base_keymap, data.base_keymap);
238    }
239
240    #[cfg(any(test, feature = "test-support"))]
241    pub fn test(cx: &gpui::AppContext) -> Settings {
242        Settings {
243            buffer_font_family_name: "Monaco".to_string(),
244            buffer_font_features: Default::default(),
245            buffer_font_family: cx
246                .font_cache()
247                .load_family(&["Monaco"], &Default::default())
248                .unwrap(),
249            buffer_font_size: 14.,
250            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), Default::default),
251            base_keymap: Default::default(),
252        }
253    }
254
255    #[cfg(any(test, feature = "test-support"))]
256    pub fn test_async(cx: &mut gpui::TestAppContext) {
257        cx.update(|cx| {
258            let settings = Self::test(cx);
259            cx.set_global(settings);
260        });
261    }
262}
263
264fn merge<T: Copy>(target: &mut T, value: Option<T>) {
265    if let Some(value) = value {
266        *target = value;
267    }
268}