settings.rs

  1use anyhow::Result;
  2use gpui::font_cache::{FamilyId, FontCache};
  3use schemars::{
  4    gen::{SchemaGenerator, SchemaSettings},
  5    schema::{
  6        InstanceType, ObjectValidation, Schema, SchemaObject, SingleOrVec, SubschemaValidation,
  7    },
  8    JsonSchema,
  9};
 10use serde::Deserialize;
 11use serde_json::Value;
 12use std::{collections::HashMap, sync::Arc};
 13use theme::{Theme, ThemeRegistry};
 14use util::ResultExt as _;
 15
 16#[derive(Clone)]
 17pub struct Settings {
 18    pub buffer_font_family: FamilyId,
 19    pub buffer_font_size: f32,
 20    pub vim_mode: bool,
 21    pub tab_size: u32,
 22    pub soft_wrap: SoftWrap,
 23    pub preferred_line_length: u32,
 24    pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
 25    pub theme: Arc<Theme>,
 26}
 27
 28#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 29pub struct LanguageOverride {
 30    pub tab_size: Option<u32>,
 31    pub soft_wrap: Option<SoftWrap>,
 32    pub preferred_line_length: Option<u32>,
 33}
 34
 35#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
 36#[serde(rename_all = "snake_case")]
 37pub enum SoftWrap {
 38    None,
 39    EditorWidth,
 40    PreferredLineLength,
 41}
 42
 43#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 44pub struct SettingsFileContent {
 45    #[serde(default)]
 46    pub buffer_font_family: Option<String>,
 47    #[serde(default)]
 48    pub buffer_font_size: Option<f32>,
 49    #[serde(default)]
 50    pub vim_mode: Option<bool>,
 51    #[serde(flatten)]
 52    pub editor: LanguageOverride,
 53    #[serde(default)]
 54    pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
 55    #[serde(default)]
 56    pub theme: Option<String>,
 57}
 58
 59impl Settings {
 60    pub fn new(
 61        buffer_font_family: &str,
 62        font_cache: &FontCache,
 63        theme: Arc<Theme>,
 64    ) -> Result<Self> {
 65        Ok(Self {
 66            buffer_font_family: font_cache.load_family(&[buffer_font_family])?,
 67            buffer_font_size: 15.,
 68            vim_mode: false,
 69            tab_size: 4,
 70            soft_wrap: SoftWrap::None,
 71            preferred_line_length: 80,
 72            language_overrides: Default::default(),
 73            theme,
 74        })
 75    }
 76
 77    pub fn file_json_schema(
 78        theme_names: Vec<String>,
 79        language_names: Vec<String>,
 80    ) -> serde_json::Value {
 81        let settings = SchemaSettings::draft07().with(|settings| {
 82            settings.option_add_null_type = false;
 83        });
 84        let generator = SchemaGenerator::new(settings);
 85        let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
 86
 87        // Construct theme names reference type
 88        let theme_names = theme_names
 89            .into_iter()
 90            .map(|name| Value::String(name))
 91            .collect();
 92        let theme_names_schema = Schema::Object(SchemaObject {
 93            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
 94            enum_values: Some(theme_names),
 95            ..Default::default()
 96        });
 97        root_schema
 98            .definitions
 99            .insert("ThemeName".to_owned(), theme_names_schema);
100
101        // Construct language overrides reference type
102        let language_override_schema_reference = Schema::Object(SchemaObject {
103            reference: Some("#/definitions/LanguageOverride".to_owned()),
104            ..Default::default()
105        });
106        let language_overrides_properties = language_names
107            .into_iter()
108            .map(|name| {
109                (
110                    name,
111                    Schema::Object(SchemaObject {
112                        subschemas: Some(Box::new(SubschemaValidation {
113                            all_of: Some(vec![language_override_schema_reference.clone()]),
114                            ..Default::default()
115                        })),
116                        ..Default::default()
117                    }),
118                )
119            })
120            .collect();
121        let language_overrides_schema = Schema::Object(SchemaObject {
122            instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
123            object: Some(Box::new(ObjectValidation {
124                properties: language_overrides_properties,
125                ..Default::default()
126            })),
127            ..Default::default()
128        });
129        root_schema
130            .definitions
131            .insert("LanguageOverrides".to_owned(), language_overrides_schema);
132
133        // Modify theme property to use new theme reference type
134        let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
135        let language_overrides_schema_reference = Schema::Object(SchemaObject {
136            reference: Some("#/definitions/ThemeName".to_owned()),
137            ..Default::default()
138        });
139        settings_file_schema.properties.insert(
140            "theme".to_owned(),
141            Schema::Object(SchemaObject {
142                subschemas: Some(Box::new(SubschemaValidation {
143                    all_of: Some(vec![language_overrides_schema_reference]),
144                    ..Default::default()
145                })),
146                ..Default::default()
147            }),
148        );
149
150        // Modify language_overrides property to use LanguageOverrides reference
151        settings_file_schema.properties.insert(
152            "language_overrides".to_owned(),
153            Schema::Object(SchemaObject {
154                reference: Some("#/definitions/LanguageOverrides".to_owned()),
155                ..Default::default()
156            }),
157        );
158        serde_json::to_value(root_schema).unwrap()
159    }
160
161    pub fn with_overrides(
162        mut self,
163        language_name: impl Into<Arc<str>>,
164        overrides: LanguageOverride,
165    ) -> Self {
166        self.language_overrides
167            .insert(language_name.into(), overrides);
168        self
169    }
170
171    pub fn tab_size(&self, language: Option<&str>) -> u32 {
172        language
173            .and_then(|language| self.language_overrides.get(language))
174            .and_then(|settings| settings.tab_size)
175            .unwrap_or(self.tab_size)
176    }
177
178    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
179        language
180            .and_then(|language| self.language_overrides.get(language))
181            .and_then(|settings| settings.soft_wrap)
182            .unwrap_or(self.soft_wrap)
183    }
184
185    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
186        language
187            .and_then(|language| self.language_overrides.get(language))
188            .and_then(|settings| settings.preferred_line_length)
189            .unwrap_or(self.preferred_line_length)
190    }
191
192    #[cfg(any(test, feature = "test-support"))]
193    pub fn test(cx: &gpui::AppContext) -> Settings {
194        Settings {
195            buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
196            buffer_font_size: 14.,
197            vim_mode: false,
198            tab_size: 4,
199            soft_wrap: SoftWrap::None,
200            preferred_line_length: 80,
201            language_overrides: Default::default(),
202            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
203        }
204    }
205
206    pub fn merge(
207        &mut self,
208        data: &SettingsFileContent,
209        theme_registry: &ThemeRegistry,
210        font_cache: &FontCache,
211    ) {
212        if let Some(value) = &data.buffer_font_family {
213            if let Some(id) = font_cache.load_family(&[value]).log_err() {
214                self.buffer_font_family = id;
215            }
216        }
217        if let Some(value) = &data.theme {
218            if let Some(theme) = theme_registry.get(&value.to_string()).log_err() {
219                self.theme = theme;
220            }
221        }
222
223        merge(&mut self.buffer_font_size, data.buffer_font_size);
224        merge(&mut self.vim_mode, data.vim_mode);
225        merge(&mut self.soft_wrap, data.editor.soft_wrap);
226        merge(&mut self.tab_size, data.editor.tab_size);
227        merge(
228            &mut self.preferred_line_length,
229            data.editor.preferred_line_length,
230        );
231
232        for (language_name, settings) in data.language_overrides.clone().into_iter() {
233            let target = self
234                .language_overrides
235                .entry(language_name.into())
236                .or_default();
237
238            merge_option(&mut target.tab_size, settings.tab_size);
239            merge_option(&mut target.soft_wrap, settings.soft_wrap);
240            merge_option(
241                &mut target.preferred_line_length,
242                settings.preferred_line_length,
243            );
244        }
245    }
246}
247
248fn merge<T: Copy>(target: &mut T, value: Option<T>) {
249    if let Some(value) = value {
250        *target = value;
251    }
252}
253
254fn merge_option<T: Copy>(target: &mut Option<T>, value: Option<T>) {
255    if value.is_some() {
256        *target = value;
257    }
258}