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    pub fn merge(
140        &mut self,
141        data: &SettingsFileContent,
142        theme_registry: &ThemeRegistry,
143        font_cache: &FontCache,
144    ) {
145        if let Some(value) = &data.buffer_font_family {
146            if let Some(id) = font_cache.load_family(&[value]).log_err() {
147                self.buffer_font_family = id;
148            }
149        }
150        if let Some(value) = &data.theme {
151            if let Some(theme) = theme_registry.get(&value.to_string()).log_err() {
152                self.theme = theme;
153            }
154        }
155
156        merge(&mut self.buffer_font_size, data.buffer_font_size);
157        merge(&mut self.vim_mode, data.vim_mode);
158        merge(&mut self.format_on_save, data.format_on_save);
159        merge(&mut self.soft_wrap, data.editor.soft_wrap);
160        merge(&mut self.tab_size, data.editor.tab_size);
161        merge(
162            &mut self.preferred_line_length,
163            data.editor.preferred_line_length,
164        );
165
166        for (language_name, settings) in data.language_overrides.clone().into_iter() {
167            let target = self
168                .language_overrides
169                .entry(language_name.into())
170                .or_default();
171
172            merge_option(&mut target.tab_size, settings.tab_size);
173            merge_option(&mut target.soft_wrap, settings.soft_wrap);
174            merge_option(&mut target.format_on_save, settings.format_on_save);
175            merge_option(
176                &mut target.preferred_line_length,
177                settings.preferred_line_length,
178            );
179        }
180    }
181}
182
183pub fn settings_file_json_schema(
184    theme_names: Vec<String>,
185    language_names: Vec<String>,
186) -> serde_json::Value {
187    let settings = SchemaSettings::draft07().with(|settings| {
188        settings.option_add_null_type = false;
189    });
190    let generator = SchemaGenerator::new(settings);
191    let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
192
193    // Construct theme names reference type
194    let theme_names = theme_names
195        .into_iter()
196        .map(|name| Value::String(name))
197        .collect();
198    let theme_names_schema = Schema::Object(SchemaObject {
199        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
200        enum_values: Some(theme_names),
201        ..Default::default()
202    });
203    root_schema
204        .definitions
205        .insert("ThemeName".to_owned(), theme_names_schema);
206
207    // Construct language overrides reference type
208    let language_override_schema_reference = Schema::Object(SchemaObject {
209        reference: Some("#/definitions/LanguageOverride".to_owned()),
210        ..Default::default()
211    });
212    let language_overrides_properties = language_names
213        .into_iter()
214        .map(|name| {
215            (
216                name,
217                Schema::Object(SchemaObject {
218                    subschemas: Some(Box::new(SubschemaValidation {
219                        all_of: Some(vec![language_override_schema_reference.clone()]),
220                        ..Default::default()
221                    })),
222                    ..Default::default()
223                }),
224            )
225        })
226        .collect();
227    let language_overrides_schema = Schema::Object(SchemaObject {
228        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
229        object: Some(Box::new(ObjectValidation {
230            properties: language_overrides_properties,
231            ..Default::default()
232        })),
233        ..Default::default()
234    });
235    root_schema
236        .definitions
237        .insert("LanguageOverrides".to_owned(), language_overrides_schema);
238
239    // Modify theme property to use new theme reference type
240    let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
241    let language_overrides_schema_reference = Schema::Object(SchemaObject {
242        reference: Some("#/definitions/ThemeName".to_owned()),
243        ..Default::default()
244    });
245    settings_file_schema.properties.insert(
246        "theme".to_owned(),
247        Schema::Object(SchemaObject {
248            subschemas: Some(Box::new(SubschemaValidation {
249                all_of: Some(vec![language_overrides_schema_reference]),
250                ..Default::default()
251            })),
252            ..Default::default()
253        }),
254    );
255
256    // Modify language_overrides property to use LanguageOverrides reference
257    settings_file_schema.properties.insert(
258        "language_overrides".to_owned(),
259        Schema::Object(SchemaObject {
260            reference: Some("#/definitions/LanguageOverrides".to_owned()),
261            ..Default::default()
262        }),
263    );
264    serde_json::to_value(root_schema).unwrap()
265}
266
267fn merge<T: Copy>(target: &mut T, value: Option<T>) {
268    if let Some(value) = value {
269        *target = value;
270    }
271}
272
273fn merge_option<T: Copy>(target: &mut Option<T>, value: Option<T>) {
274    if value.is_some() {
275        *target = value;
276    }
277}
278
279pub fn parse_json_with_comments<T: DeserializeOwned>(content: &str) -> Result<T> {
280    Ok(serde_json::from_reader(
281        json_comments::CommentSettings::c_style().strip_comments(content.as_bytes()),
282    )?)
283}