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