settings.rs

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