settings.rs

  1use anyhow::Result;
  2use gpui::font_cache::{FamilyId, FontCache};
  3use schemars::{schema_for, JsonSchema};
  4use serde::Deserialize;
  5use std::{collections::HashMap, sync::Arc};
  6use theme::{Theme, ThemeRegistry};
  7use util::ResultExt as _;
  8
  9#[derive(Clone)]
 10pub struct Settings {
 11    pub buffer_font_family: FamilyId,
 12    pub buffer_font_size: f32,
 13    pub vim_mode: bool,
 14    pub tab_size: u32,
 15    pub soft_wrap: SoftWrap,
 16    pub preferred_line_length: u32,
 17    pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
 18    pub theme: Arc<Theme>,
 19}
 20
 21#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 22pub struct LanguageOverride {
 23    pub tab_size: Option<u32>,
 24    pub soft_wrap: Option<SoftWrap>,
 25    pub preferred_line_length: Option<u32>,
 26}
 27
 28#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Eq, JsonSchema)]
 29#[serde(rename_all = "snake_case")]
 30pub enum SoftWrap {
 31    None,
 32    EditorWidth,
 33    PreferredLineLength,
 34}
 35
 36#[derive(Clone, Debug, Default, Deserialize, JsonSchema)]
 37pub struct SettingsFileContent {
 38    #[serde(default)]
 39    pub buffer_font_family: Option<String>,
 40    #[serde(default)]
 41    pub buffer_font_size: Option<f32>,
 42    #[serde(default)]
 43    pub vim_mode: Option<bool>,
 44    #[serde(flatten)]
 45    pub editor: LanguageOverride,
 46    #[serde(default)]
 47    pub language_overrides: HashMap<Arc<str>, LanguageOverride>,
 48    #[serde(default)]
 49    pub theme: Option<String>,
 50}
 51
 52impl Settings {
 53    pub fn new(
 54        buffer_font_family: &str,
 55        font_cache: &FontCache,
 56        theme: Arc<Theme>,
 57    ) -> Result<Self> {
 58        Ok(Self {
 59            buffer_font_family: font_cache.load_family(&[buffer_font_family])?,
 60            buffer_font_size: 15.,
 61            vim_mode: false,
 62            tab_size: 4,
 63            soft_wrap: SoftWrap::None,
 64            preferred_line_length: 80,
 65            language_overrides: Default::default(),
 66            theme,
 67        })
 68    }
 69
 70    pub fn file_json_schema() -> serde_json::Value {
 71        serde_json::to_value(schema_for!(SettingsFileContent)).unwrap()
 72    }
 73
 74    pub fn with_overrides(
 75        mut self,
 76        language_name: impl Into<Arc<str>>,
 77        overrides: LanguageOverride,
 78    ) -> Self {
 79        self.language_overrides
 80            .insert(language_name.into(), overrides);
 81        self
 82    }
 83
 84    pub fn tab_size(&self, language: Option<&str>) -> u32 {
 85        language
 86            .and_then(|language| self.language_overrides.get(language))
 87            .and_then(|settings| settings.tab_size)
 88            .unwrap_or(self.tab_size)
 89    }
 90
 91    pub fn soft_wrap(&self, language: Option<&str>) -> SoftWrap {
 92        language
 93            .and_then(|language| self.language_overrides.get(language))
 94            .and_then(|settings| settings.soft_wrap)
 95            .unwrap_or(self.soft_wrap)
 96    }
 97
 98    pub fn preferred_line_length(&self, language: Option<&str>) -> u32 {
 99        language
100            .and_then(|language| self.language_overrides.get(language))
101            .and_then(|settings| settings.preferred_line_length)
102            .unwrap_or(self.preferred_line_length)
103    }
104
105    #[cfg(any(test, feature = "test-support"))]
106    pub fn test(cx: &gpui::AppContext) -> Settings {
107        Settings {
108            buffer_font_family: cx.font_cache().load_family(&["Monaco"]).unwrap(),
109            buffer_font_size: 14.,
110            vim_mode: false,
111            tab_size: 4,
112            soft_wrap: SoftWrap::None,
113            preferred_line_length: 80,
114            language_overrides: Default::default(),
115            theme: gpui::fonts::with_font_cache(cx.font_cache().clone(), || Default::default()),
116        }
117    }
118
119    pub fn merge(
120        &mut self,
121        data: &SettingsFileContent,
122        theme_registry: &ThemeRegistry,
123        font_cache: &FontCache,
124    ) {
125        if let Some(value) = &data.buffer_font_family {
126            if let Some(id) = font_cache.load_family(&[value]).log_err() {
127                self.buffer_font_family = id;
128            }
129        }
130        if let Some(value) = &data.theme {
131            if let Some(theme) = theme_registry.get(value).log_err() {
132                self.theme = theme;
133            }
134        }
135
136        merge(&mut self.buffer_font_size, data.buffer_font_size);
137        merge(&mut self.vim_mode, data.vim_mode);
138        merge(&mut self.soft_wrap, data.editor.soft_wrap);
139        merge(&mut self.tab_size, data.editor.tab_size);
140        merge(
141            &mut self.preferred_line_length,
142            data.editor.preferred_line_length,
143        );
144
145        for (language_name, settings) in &data.language_overrides {
146            let target = self
147                .language_overrides
148                .entry(language_name.clone())
149                .or_default();
150
151            merge_option(&mut target.tab_size, settings.tab_size);
152            merge_option(&mut target.soft_wrap, settings.soft_wrap);
153            merge_option(
154                &mut target.preferred_line_length,
155                settings.preferred_line_length,
156            );
157        }
158    }
159}
160
161fn merge<T: Copy>(target: &mut T, value: Option<T>) {
162    if let Some(value) = value {
163        *target = value;
164    }
165}
166
167fn merge_option<T: Copy>(target: &mut Option<T>, value: Option<T>) {
168    if value.is_some() {
169        *target = value;
170    }
171}