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 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 with_overrides(
 82        mut self,
 83        language_name: impl Into<Arc<str>>,
 84        overrides: LanguageOverride,
 85    ) -> Self {
 86        self.language_overrides
 87            .insert(language_name.into(), overrides);
 88        self
 89    }
 90
 91    pub fn tab_size(&self, language: Option<&str>) -> u32 {
 92        language
 93            .and_then(|language| self.language_overrides.get(language))
 94            .and_then(|settings| settings.tab_size)
 95            .unwrap_or(self.tab_size)
 96    }
 97
 98    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
 99        language
100            .and_then(|language| self.language_overrides.get(language))
101            .and_then(|settings| settings.soft_wrap)
102            .unwrap_or(self.soft_wrap)
103    }
104
105    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
106        language
107            .and_then(|language| self.language_overrides.get(language))
108            .and_then(|settings| settings.preferred_line_length)
109            .unwrap_or(self.preferred_line_length)
110    }
111
112    #[cfg(any(test, feature = "test-support"))]
113    pub fn test(cx: &gpui::AppContext) -> Settings {
114        Settings {
115            buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
116            buffer_font_size: 14.,
117            vim_mode: false,
118            tab_size: 4,
119            soft_wrap: SoftWrap::None,
120            preferred_line_length: 80,
121            language_overrides: Default::default(),
122            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
123        }
124    }
125
126    pub fn merge(
127        &mut self,
128        data: &SettingsFileContent,
129        theme_registry: &ThemeRegistry,
130        font_cache: &FontCache,
131    ) {
132        if let Some(value) = &data.buffer_font_family {
133            if let Some(id) = font_cache.load_family(&[value]).log_err() {
134                self.buffer_font_family = id;
135            }
136        }
137        if let Some(value) = &data.theme {
138            if let Some(theme) = theme_registry.get(&value.to_string()).log_err() {
139                self.theme = theme;
140            }
141        }
142
143        merge(&mut self.buffer_font_size, data.buffer_font_size);
144        merge(&mut self.vim_mode, data.vim_mode);
145        merge(&mut self.soft_wrap, data.editor.soft_wrap);
146        merge(&mut self.tab_size, data.editor.tab_size);
147        merge(
148            &mut self.preferred_line_length,
149            data.editor.preferred_line_length,
150        );
151
152        for (language_name, settings) in data.language_overrides.clone().into_iter() {
153            let target = self
154                .language_overrides
155                .entry(language_name.into())
156                .or_default();
157
158            merge_option(&mut target.tab_size, settings.tab_size);
159            merge_option(&mut target.soft_wrap, settings.soft_wrap);
160            merge_option(
161                &mut target.preferred_line_length,
162                settings.preferred_line_length,
163            );
164        }
165    }
166}
167
168pub fn settings_file_json_schema(
169    theme_names: Vec<String>,
170    language_names: Vec<String>,
171) -> serde_json::Value {
172    let settings = SchemaSettings::draft07().with(|settings| {
173        settings.option_add_null_type = false;
174    });
175    let generator = SchemaGenerator::new(settings);
176    let mut root_schema = generator.into_root_schema_for::<SettingsFileContent>();
177
178    // Construct theme names reference type
179    let theme_names = theme_names
180        .into_iter()
181        .map(|name| Value::String(name))
182        .collect();
183    let theme_names_schema = Schema::Object(SchemaObject {
184        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::String))),
185        enum_values: Some(theme_names),
186        ..Default::default()
187    });
188    root_schema
189        .definitions
190        .insert("ThemeName".to_owned(), theme_names_schema);
191
192    // Construct language overrides reference type
193    let language_override_schema_reference = Schema::Object(SchemaObject {
194        reference: Some("#/definitions/LanguageOverride".to_owned()),
195        ..Default::default()
196    });
197    let language_overrides_properties = language_names
198        .into_iter()
199        .map(|name| {
200            (
201                name,
202                Schema::Object(SchemaObject {
203                    subschemas: Some(Box::new(SubschemaValidation {
204                        all_of: Some(vec![language_override_schema_reference.clone()]),
205                        ..Default::default()
206                    })),
207                    ..Default::default()
208                }),
209            )
210        })
211        .collect();
212    let language_overrides_schema = Schema::Object(SchemaObject {
213        instance_type: Some(SingleOrVec::Single(Box::new(InstanceType::Object))),
214        object: Some(Box::new(ObjectValidation {
215            properties: language_overrides_properties,
216            ..Default::default()
217        })),
218        ..Default::default()
219    });
220    root_schema
221        .definitions
222        .insert("LanguageOverrides".to_owned(), language_overrides_schema);
223
224    // Modify theme property to use new theme reference type
225    let settings_file_schema = root_schema.schema.object.as_mut().unwrap();
226    let language_overrides_schema_reference = Schema::Object(SchemaObject {
227        reference: Some("#/definitions/ThemeName".to_owned()),
228        ..Default::default()
229    });
230    settings_file_schema.properties.insert(
231        "theme".to_owned(),
232        Schema::Object(SchemaObject {
233            subschemas: Some(Box::new(SubschemaValidation {
234                all_of: Some(vec![language_overrides_schema_reference]),
235                ..Default::default()
236            })),
237            ..Default::default()
238        }),
239    );
240
241    // Modify language_overrides property to use LanguageOverrides reference
242    settings_file_schema.properties.insert(
243        "language_overrides".to_owned(),
244        Schema::Object(SchemaObject {
245            reference: Some("#/definitions/LanguageOverrides".to_owned()),
246            ..Default::default()
247        }),
248    );
249    serde_json::to_value(root_schema).unwrap()
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}