settings.rs

  1use crate::one_themes::one_dark;
  2use crate::{SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent};
  3use anyhow::Result;
  4use gpui::{
  5    px, AppContext, Font, FontFeatures, FontStyle, FontWeight, Pixels, Subscription, ViewContext,
  6};
  7use refineable::Refineable;
  8use schemars::{
  9    gen::SchemaGenerator,
 10    schema::{InstanceType, Schema, SchemaObject},
 11    JsonSchema,
 12};
 13use serde::{Deserialize, Serialize};
 14use serde_json::Value;
 15use settings::{Settings, SettingsJsonSchemaParams};
 16use std::sync::Arc;
 17use util::ResultExt as _;
 18
 19const MIN_FONT_SIZE: Pixels = px(6.0);
 20const MIN_LINE_HEIGHT: f32 = 1.0;
 21
 22#[derive(Clone)]
 23pub struct ThemeSettings {
 24    pub ui_font_size: Pixels,
 25    pub ui_font: Font,
 26    pub buffer_font: Font,
 27    pub buffer_font_size: Pixels,
 28    pub buffer_line_height: BufferLineHeight,
 29    pub active_theme: Arc<Theme>,
 30    pub theme_overrides: Option<ThemeStyleContent>,
 31}
 32
 33#[derive(Default)]
 34pub(crate) struct AdjustedBufferFontSize(Pixels);
 35
 36#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
 37pub struct ThemeSettingsContent {
 38    #[serde(default)]
 39    pub ui_font_size: Option<f32>,
 40    #[serde(default)]
 41    pub ui_font_family: Option<String>,
 42    #[serde(default)]
 43    pub ui_font_features: Option<FontFeatures>,
 44    #[serde(default)]
 45    pub buffer_font_family: Option<String>,
 46    #[serde(default)]
 47    pub buffer_font_size: Option<f32>,
 48    #[serde(default)]
 49    pub buffer_line_height: Option<BufferLineHeight>,
 50    #[serde(default)]
 51    pub buffer_font_features: Option<FontFeatures>,
 52    #[serde(default)]
 53    pub theme: Option<String>,
 54
 55    /// EXPERIMENTAL: Overrides for the current theme.
 56    ///
 57    /// These values will override the ones on the current theme specified in `theme`.
 58    #[serde(rename = "experimental.theme_overrides", default)]
 59    pub theme_overrides: Option<ThemeStyleContent>,
 60}
 61
 62#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
 63#[serde(rename_all = "snake_case")]
 64pub enum BufferLineHeight {
 65    #[default]
 66    Comfortable,
 67    Standard,
 68    Custom(f32),
 69}
 70
 71impl BufferLineHeight {
 72    pub fn value(&self) -> f32 {
 73        match self {
 74            BufferLineHeight::Comfortable => 1.618,
 75            BufferLineHeight::Standard => 1.3,
 76            BufferLineHeight::Custom(line_height) => *line_height,
 77        }
 78    }
 79}
 80
 81impl ThemeSettings {
 82    pub fn buffer_font_size(&self, cx: &AppContext) -> Pixels {
 83        cx.try_global::<AdjustedBufferFontSize>()
 84            .map_or(self.buffer_font_size, |size| size.0)
 85            .max(MIN_FONT_SIZE)
 86    }
 87
 88    pub fn line_height(&self) -> f32 {
 89        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
 90    }
 91
 92    /// Applies the theme overrides, if there are any, to the current theme.
 93    pub fn apply_theme_overrides(&mut self) {
 94        if let Some(theme_overrides) = &self.theme_overrides {
 95            let mut base_theme = (*self.active_theme).clone();
 96
 97            base_theme
 98                .styles
 99                .colors
100                .refine(&theme_overrides.theme_colors_refinement());
101            base_theme
102                .styles
103                .status
104                .refine(&theme_overrides.status_colors_refinement());
105            base_theme.styles.syntax = Arc::new(SyntaxTheme {
106                highlights: {
107                    let mut highlights = base_theme.styles.syntax.highlights.clone();
108                    // Overrides come second in the highlight list so that they take precedence
109                    // over the ones in the base theme.
110                    highlights.extend(theme_overrides.syntax_overrides());
111                    highlights
112                },
113            });
114
115            self.active_theme = Arc::new(base_theme);
116        }
117    }
118}
119
120pub fn observe_buffer_font_size_adjustment<V: 'static>(
121    cx: &mut ViewContext<V>,
122    f: impl 'static + Fn(&mut V, &mut ViewContext<V>),
123) -> Subscription {
124    cx.observe_global::<AdjustedBufferFontSize>(f)
125}
126
127pub fn adjusted_font_size(size: Pixels, cx: &mut AppContext) -> Pixels {
128    if let Some(AdjustedBufferFontSize(adjusted_size)) = cx.try_global::<AdjustedBufferFontSize>() {
129        let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
130        let delta = *adjusted_size - buffer_font_size;
131        size + delta
132    } else {
133        size
134    }
135    .max(MIN_FONT_SIZE)
136}
137
138pub fn adjust_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
139    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
140    let mut adjusted_size = cx
141        .try_global::<AdjustedBufferFontSize>()
142        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
143
144    f(&mut adjusted_size);
145    adjusted_size = adjusted_size.max(MIN_FONT_SIZE);
146    cx.set_global(AdjustedBufferFontSize(adjusted_size));
147    cx.refresh();
148}
149
150pub fn reset_font_size(cx: &mut AppContext) {
151    if cx.has_global::<AdjustedBufferFontSize>() {
152        cx.remove_global::<AdjustedBufferFontSize>();
153        cx.refresh();
154    }
155}
156
157impl settings::Settings for ThemeSettings {
158    const KEY: Option<&'static str> = None;
159
160    type FileContent = ThemeSettingsContent;
161
162    fn load(
163        defaults: &Self::FileContent,
164        user_values: &[&Self::FileContent],
165        cx: &mut AppContext,
166    ) -> Result<Self> {
167        let themes = cx.default_global::<ThemeRegistry>();
168
169        let mut this = Self {
170            ui_font_size: defaults.ui_font_size.unwrap().into(),
171            ui_font: Font {
172                family: defaults.ui_font_family.clone().unwrap().into(),
173                features: defaults.ui_font_features.clone().unwrap(),
174                weight: Default::default(),
175                style: Default::default(),
176            },
177            buffer_font: Font {
178                family: defaults.buffer_font_family.clone().unwrap().into(),
179                features: defaults.buffer_font_features.clone().unwrap(),
180                weight: FontWeight::default(),
181                style: FontStyle::default(),
182            },
183            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
184            buffer_line_height: defaults.buffer_line_height.unwrap(),
185            active_theme: themes
186                .get(defaults.theme.as_ref().unwrap())
187                .or(themes.get(&one_dark().name))
188                .unwrap(),
189            theme_overrides: None,
190        };
191
192        for value in user_values.into_iter().copied().cloned() {
193            if let Some(value) = value.buffer_font_family {
194                this.buffer_font.family = value.into();
195            }
196            if let Some(value) = value.buffer_font_features {
197                this.buffer_font.features = value;
198            }
199
200            if let Some(value) = value.ui_font_family {
201                this.ui_font.family = value.into();
202            }
203            if let Some(value) = value.ui_font_features {
204                this.ui_font.features = value;
205            }
206
207            if let Some(value) = &value.theme {
208                if let Some(theme) = themes.get(value).log_err() {
209                    this.active_theme = theme;
210                }
211            }
212
213            this.theme_overrides = value.theme_overrides;
214            this.apply_theme_overrides();
215
216            merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
217            merge(
218                &mut this.buffer_font_size,
219                value.buffer_font_size.map(Into::into),
220            );
221            merge(&mut this.buffer_line_height, value.buffer_line_height);
222        }
223
224        Ok(this)
225    }
226
227    fn json_schema(
228        generator: &mut SchemaGenerator,
229        params: &SettingsJsonSchemaParams,
230        cx: &AppContext,
231    ) -> schemars::schema::RootSchema {
232        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
233        let theme_names = cx
234            .global::<ThemeRegistry>()
235            .list_names(params.staff_mode)
236            .map(|theme_name| Value::String(theme_name.to_string()))
237            .collect();
238
239        let theme_name_schema = SchemaObject {
240            instance_type: Some(InstanceType::String.into()),
241            enum_values: Some(theme_names),
242            ..Default::default()
243        };
244
245        let available_fonts = params
246            .font_names
247            .iter()
248            .cloned()
249            .map(Value::String)
250            .collect();
251        let fonts_schema = SchemaObject {
252            instance_type: Some(InstanceType::String.into()),
253            enum_values: Some(available_fonts),
254            ..Default::default()
255        };
256        root_schema.definitions.extend([
257            ("ThemeName".into(), theme_name_schema.into()),
258            ("FontFamilies".into(), fonts_schema.into()),
259        ]);
260
261        root_schema
262            .schema
263            .object
264            .as_mut()
265            .unwrap()
266            .properties
267            .extend([
268                (
269                    "theme".to_owned(),
270                    Schema::new_ref("#/definitions/ThemeName".into()),
271                ),
272                (
273                    "buffer_font_family".to_owned(),
274                    Schema::new_ref("#/definitions/FontFamilies".into()),
275                ),
276                (
277                    "ui_font_family".to_owned(),
278                    Schema::new_ref("#/definitions/FontFamilies".into()),
279                ),
280            ]);
281
282        root_schema
283    }
284}
285
286fn merge<T: Copy>(target: &mut T, value: Option<T>) {
287    if let Some(value) = value {
288        *target = value;
289    }
290}