settings.rs

  1use crate::fallback_themes::zed_default_dark;
  2use crate::{Appearance, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent};
  3use anyhow::Result;
  4use derive_more::{Deref, DerefMut};
  5use gpui::{
  6    px, AppContext, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
  7    Subscription, ViewContext, WindowContext,
  8};
  9use refineable::Refineable;
 10use schemars::{
 11    gen::SchemaGenerator,
 12    schema::{InstanceType, Schema, SchemaObject},
 13    JsonSchema,
 14};
 15use serde::{Deserialize, Serialize};
 16use serde_json::Value;
 17use settings::{add_references_to_properties, Settings, SettingsJsonSchemaParams, SettingsSources};
 18use std::sync::Arc;
 19use util::ResultExt as _;
 20
 21const MIN_FONT_SIZE: Pixels = px(6.0);
 22const MIN_LINE_HEIGHT: f32 = 1.0;
 23
 24#[derive(
 25    Debug,
 26    Default,
 27    PartialEq,
 28    Eq,
 29    PartialOrd,
 30    Ord,
 31    Hash,
 32    Clone,
 33    Copy,
 34    Serialize,
 35    Deserialize,
 36    JsonSchema,
 37)]
 38
 39/// Specifies the density of the UI.
 40/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
 41#[serde(rename_all = "snake_case")]
 42pub enum UiDensity {
 43    /// A denser UI with tighter spacing and smaller elements.
 44    #[serde(alias = "compact")]
 45    Compact,
 46    #[default]
 47    #[serde(alias = "default")]
 48    /// The default UI density.
 49    Default,
 50    #[serde(alias = "comfortable")]
 51    /// A looser UI with more spacing and larger elements.
 52    Comfortable,
 53}
 54
 55impl UiDensity {
 56    /// The spacing ratio of a given density.
 57    /// TODO: Standardize usage throughout the app or remove
 58    pub fn spacing_ratio(self) -> f32 {
 59        match self {
 60            UiDensity::Compact => 0.75,
 61            UiDensity::Default => 1.0,
 62            UiDensity::Comfortable => 1.25,
 63        }
 64    }
 65}
 66
 67impl From<String> for UiDensity {
 68    fn from(s: String) -> Self {
 69        match s.as_str() {
 70            "compact" => Self::Compact,
 71            "default" => Self::Default,
 72            "comfortable" => Self::Comfortable,
 73            _ => Self::default(),
 74        }
 75    }
 76}
 77
 78impl From<UiDensity> for String {
 79    fn from(val: UiDensity) -> Self {
 80        match val {
 81            UiDensity::Compact => "compact".to_string(),
 82            UiDensity::Default => "default".to_string(),
 83            UiDensity::Comfortable => "comfortable".to_string(),
 84        }
 85    }
 86}
 87
 88/// Customizable settings for the UI and theme system.
 89#[derive(Clone, PartialEq)]
 90pub struct ThemeSettings {
 91    /// The UI font size. Determines the size of text in the UI,
 92    /// as well as the size of a [gpui::Rems] unit.
 93    ///
 94    /// Changing this will impact the size of all UI elements.
 95    pub ui_font_size: Pixels,
 96    /// The font used for UI elements.
 97    pub ui_font: Font,
 98    /// The font size used for buffers, and the terminal.
 99    ///
100    /// The terminal font size can be overridden using it's own setting.
101    pub buffer_font_size: Pixels,
102    /// The font used for buffers, and the terminal.
103    ///
104    /// The terminal font family can be overridden using it's own setting.
105    pub buffer_font: Font,
106    /// The line height for buffers, and the terminal.
107    ///
108    /// Changing this may affect the spacing of some UI elements.
109    ///
110    /// The terminal font family can be overridden using it's own setting.
111    pub buffer_line_height: BufferLineHeight,
112    /// The current theme selection.
113    /// TODO: Document this further
114    pub theme_selection: Option<ThemeSelection>,
115    /// The active theme.
116    pub active_theme: Arc<Theme>,
117    /// Manual overrides for the active theme.
118    ///
119    /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
120    pub theme_overrides: Option<ThemeStyleContent>,
121    /// The density of the UI.
122    /// Note: This setting is still experimental. See [this tracking issue](
123    pub ui_density: UiDensity,
124    /// The amount of fading applied to unnecessary code.
125    pub unnecessary_code_fade: f32,
126}
127
128impl ThemeSettings {
129    const DEFAULT_LIGHT_THEME: &'static str = "One Light";
130    const DEFAULT_DARK_THEME: &'static str = "One Dark";
131
132    /// Returns the name of the default theme for the given [`Appearance`].
133    pub fn default_theme(appearance: Appearance) -> &'static str {
134        match appearance {
135            Appearance::Light => Self::DEFAULT_LIGHT_THEME,
136            Appearance::Dark => Self::DEFAULT_DARK_THEME,
137        }
138    }
139
140    /// Reloads the current theme.
141    ///
142    /// Reads the [`ThemeSettings`] to know which theme should be loaded,
143    /// taking into account the current [`SystemAppearance`].
144    pub fn reload_current_theme(cx: &mut AppContext) {
145        let mut theme_settings = ThemeSettings::get_global(cx).clone();
146        let system_appearance = SystemAppearance::global(cx);
147
148        if let Some(theme_selection) = theme_settings.theme_selection.clone() {
149            let mut theme_name = theme_selection.theme(*system_appearance);
150
151            // If the selected theme doesn't exist, fall back to a default theme
152            // based on the system appearance.
153            let theme_registry = ThemeRegistry::global(cx);
154            if theme_registry.get(theme_name).ok().is_none() {
155                theme_name = Self::default_theme(*system_appearance);
156            };
157
158            if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
159                ThemeSettings::override_global(theme_settings, cx);
160            }
161        }
162    }
163}
164
165/// The appearance of the system.
166#[derive(Debug, Clone, Copy, Deref)]
167pub struct SystemAppearance(pub Appearance);
168
169impl Default for SystemAppearance {
170    fn default() -> Self {
171        Self(Appearance::Dark)
172    }
173}
174
175#[derive(Deref, DerefMut, Default)]
176struct GlobalSystemAppearance(SystemAppearance);
177
178impl Global for GlobalSystemAppearance {}
179
180impl SystemAppearance {
181    /// Initializes the [`SystemAppearance`] for the application.
182    pub fn init(cx: &mut AppContext) {
183        *cx.default_global::<GlobalSystemAppearance>() =
184            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
185    }
186
187    /// Returns the global [`SystemAppearance`].
188    ///
189    /// Inserts a default [`SystemAppearance`] if one does not yet exist.
190    pub(crate) fn default_global(cx: &mut AppContext) -> Self {
191        cx.default_global::<GlobalSystemAppearance>().0
192    }
193
194    /// Returns the global [`SystemAppearance`].
195    pub fn global(cx: &AppContext) -> Self {
196        cx.global::<GlobalSystemAppearance>().0
197    }
198
199    /// Returns a mutable reference to the global [`SystemAppearance`].
200    pub fn global_mut(cx: &mut AppContext) -> &mut Self {
201        cx.global_mut::<GlobalSystemAppearance>()
202    }
203}
204
205#[derive(Default)]
206pub(crate) struct AdjustedBufferFontSize(Pixels);
207
208impl Global for AdjustedBufferFontSize {}
209
210#[derive(Default)]
211pub(crate) struct AdjustedUiFontSize(Pixels);
212
213impl Global for AdjustedUiFontSize {}
214
215/// Represents the selection of a theme, which can be either static or dynamic.
216#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
217#[serde(untagged)]
218pub enum ThemeSelection {
219    /// A static theme selection, represented by a single theme name.
220    Static(#[schemars(schema_with = "theme_name_ref")] String),
221    /// A dynamic theme selection, which can change based the [ThemeMode].
222    Dynamic {
223        /// The mode used to determine which theme to use.
224        #[serde(default)]
225        mode: ThemeMode,
226        /// The theme to use for light mode.
227        #[schemars(schema_with = "theme_name_ref")]
228        light: String,
229        /// The theme to use for dark mode.
230        #[schemars(schema_with = "theme_name_ref")]
231        dark: String,
232    },
233}
234
235fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
236    Schema::new_ref("#/definitions/ThemeName".into())
237}
238
239// TODO: Rename ThemeMode -> ThemeAppearanceMode
240/// The mode use to select a theme.
241///
242/// `Light` and `Dark` will select their respective themes.
243///
244/// `System` will select the theme based on the system's appearance.
245#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
246#[serde(rename_all = "snake_case")]
247pub enum ThemeMode {
248    /// Use the specified `light` theme.
249    Light,
250
251    /// Use the specified `dark` theme.
252    Dark,
253
254    /// Use the theme based on the system's appearance.
255    #[default]
256    System,
257}
258
259impl ThemeSelection {
260    /// Returns the theme name for the selected [ThemeMode].
261    pub fn theme(&self, system_appearance: Appearance) -> &str {
262        match self {
263            Self::Static(theme) => theme,
264            Self::Dynamic { mode, light, dark } => match mode {
265                ThemeMode::Light => light,
266                ThemeMode::Dark => dark,
267                ThemeMode::System => match system_appearance {
268                    Appearance::Light => light,
269                    Appearance::Dark => dark,
270                },
271            },
272        }
273    }
274
275    /// Returns the [ThemeMode] for the [ThemeSelection].
276    pub fn mode(&self) -> Option<ThemeMode> {
277        match self {
278            ThemeSelection::Static(_) => None,
279            ThemeSelection::Dynamic { mode, .. } => Some(*mode),
280        }
281    }
282}
283
284/// Settings for rendering text in UI and text buffers.
285#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
286pub struct ThemeSettingsContent {
287    /// The default font size for text in the UI.
288    #[serde(default)]
289    pub ui_font_size: Option<f32>,
290    /// The name of a font to use for rendering in the UI.
291    #[serde(default)]
292    pub ui_font_family: Option<String>,
293    /// The font fallbacks to use for rendering in the UI.
294    #[serde(default)]
295    #[schemars(default = "default_font_fallbacks")]
296    pub ui_font_fallbacks: Option<Vec<String>>,
297    /// The OpenType features to enable for text in the UI.
298    #[serde(default)]
299    #[schemars(default = "default_font_features")]
300    pub ui_font_features: Option<FontFeatures>,
301    /// The weight of the UI font in CSS units from 100 to 900.
302    #[serde(default)]
303    pub ui_font_weight: Option<f32>,
304    /// The name of a font to use for rendering in text buffers.
305    #[serde(default)]
306    pub buffer_font_family: Option<String>,
307    /// The font fallbacks to use for rendering in text buffers.
308    #[serde(default)]
309    #[schemars(default = "default_font_fallbacks")]
310    pub buffer_font_fallbacks: Option<Vec<String>>,
311    /// The default font size for rendering in text buffers.
312    #[serde(default)]
313    pub buffer_font_size: Option<f32>,
314    /// The weight of the editor font in CSS units from 100 to 900.
315    #[serde(default)]
316    pub buffer_font_weight: Option<f32>,
317    /// The buffer's line height.
318    #[serde(default)]
319    pub buffer_line_height: Option<BufferLineHeight>,
320    /// The OpenType features to enable for rendering in text buffers.
321    #[serde(default)]
322    #[schemars(default = "default_font_features")]
323    pub buffer_font_features: Option<FontFeatures>,
324    /// The name of the Zed theme to use.
325    #[serde(default)]
326    pub theme: Option<ThemeSelection>,
327
328    /// UNSTABLE: Expect many elements to be broken.
329    ///
330    // Controls the density of the UI.
331    #[serde(rename = "unstable.ui_density", default)]
332    pub ui_density: Option<UiDensity>,
333
334    /// How much to fade out unused code.
335    #[serde(default)]
336    pub unnecessary_code_fade: Option<f32>,
337
338    /// EXPERIMENTAL: Overrides for the current theme.
339    ///
340    /// These values will override the ones on the current theme specified in `theme`.
341    #[serde(rename = "experimental.theme_overrides", default)]
342    pub theme_overrides: Option<ThemeStyleContent>,
343}
344
345fn default_font_features() -> Option<FontFeatures> {
346    Some(FontFeatures::default())
347}
348
349fn default_font_fallbacks() -> Option<FontFallbacks> {
350    Some(FontFallbacks::default())
351}
352
353impl ThemeSettingsContent {
354    /// Sets the theme for the given appearance to the theme with the specified name.
355    pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
356        if let Some(selection) = self.theme.as_mut() {
357            let theme_to_update = match selection {
358                ThemeSelection::Static(theme) => theme,
359                ThemeSelection::Dynamic { mode, light, dark } => match mode {
360                    ThemeMode::Light => light,
361                    ThemeMode::Dark => dark,
362                    ThemeMode::System => match appearance {
363                        Appearance::Light => light,
364                        Appearance::Dark => dark,
365                    },
366                },
367            };
368
369            *theme_to_update = theme_name.to_string();
370        } else {
371            self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
372        }
373    }
374
375    /// Sets the mode for the theme.
376    pub fn set_mode(&mut self, mode: ThemeMode) {
377        if let Some(selection) = self.theme.as_mut() {
378            match selection {
379                ThemeSelection::Static(theme) => {
380                    // If the theme was previously set to a single static theme,
381                    // we don't know whether it was a light or dark theme, so we
382                    // just use it for both.
383                    self.theme = Some(ThemeSelection::Dynamic {
384                        mode,
385                        light: theme.clone(),
386                        dark: theme.clone(),
387                    });
388                }
389                ThemeSelection::Dynamic {
390                    mode: mode_to_update,
391                    ..
392                } => *mode_to_update = mode,
393            }
394        } else {
395            self.theme = Some(ThemeSelection::Dynamic {
396                mode,
397                light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
398                dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
399            });
400        }
401    }
402}
403
404/// The buffer's line height.
405#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
406#[serde(rename_all = "snake_case")]
407pub enum BufferLineHeight {
408    /// A less dense line height.
409    #[default]
410    Comfortable,
411    /// The default line height.
412    Standard,
413    /// A custom line height.
414    ///
415    /// A line height of 1.0 is the height of the buffer's font size.
416    Custom(f32),
417}
418
419impl BufferLineHeight {
420    /// Returns the value of the line height.
421    pub fn value(&self) -> f32 {
422        match self {
423            BufferLineHeight::Comfortable => 1.618,
424            BufferLineHeight::Standard => 1.3,
425            BufferLineHeight::Custom(line_height) => *line_height,
426        }
427    }
428}
429
430impl ThemeSettings {
431    /// Returns the [AdjustedBufferFontSize].
432    pub fn buffer_font_size(&self, cx: &AppContext) -> Pixels {
433        cx.try_global::<AdjustedBufferFontSize>()
434            .map_or(self.buffer_font_size, |size| size.0)
435            .max(MIN_FONT_SIZE)
436    }
437
438    // TODO: Rename: `line_height` -> `buffer_line_height`
439    /// Returns the buffer's line height.
440    pub fn line_height(&self) -> f32 {
441        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
442    }
443
444    /// Switches to the theme with the given name, if it exists.
445    ///
446    /// Returns a `Some` containing the new theme if it was successful.
447    /// Returns `None` otherwise.
448    pub fn switch_theme(&mut self, theme: &str, cx: &mut AppContext) -> Option<Arc<Theme>> {
449        let themes = ThemeRegistry::default_global(cx);
450
451        let mut new_theme = None;
452
453        if let Some(theme) = themes.get(theme).log_err() {
454            self.active_theme = theme.clone();
455            new_theme = Some(theme);
456        }
457
458        self.apply_theme_overrides();
459
460        new_theme
461    }
462
463    /// Applies the theme overrides, if there are any, to the current theme.
464    pub fn apply_theme_overrides(&mut self) {
465        if let Some(theme_overrides) = &self.theme_overrides {
466            let mut base_theme = (*self.active_theme).clone();
467
468            if let Some(window_background_appearance) = theme_overrides.window_background_appearance
469            {
470                base_theme.styles.window_background_appearance =
471                    window_background_appearance.into();
472            }
473
474            base_theme
475                .styles
476                .colors
477                .refine(&theme_overrides.theme_colors_refinement());
478            base_theme
479                .styles
480                .status
481                .refine(&theme_overrides.status_colors_refinement());
482            base_theme.styles.player.merge(&theme_overrides.players);
483            base_theme.styles.accents.merge(&theme_overrides.accents);
484            base_theme.styles.syntax =
485                SyntaxTheme::merge(base_theme.styles.syntax, theme_overrides.syntax_overrides());
486
487            self.active_theme = Arc::new(base_theme);
488        }
489    }
490}
491
492/// Observe changes to the adjusted buffer font size.
493pub fn observe_buffer_font_size_adjustment<V: 'static>(
494    cx: &mut ViewContext<V>,
495    f: impl 'static + Fn(&mut V, &mut ViewContext<V>),
496) -> Subscription {
497    cx.observe_global::<AdjustedBufferFontSize>(f)
498}
499
500/// Sets the adjusted buffer font size.
501pub fn adjusted_font_size(size: Pixels, cx: &AppContext) -> Pixels {
502    if let Some(AdjustedBufferFontSize(adjusted_size)) = cx.try_global::<AdjustedBufferFontSize>() {
503        let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
504        let delta = *adjusted_size - buffer_font_size;
505        size + delta
506    } else {
507        size
508    }
509    .max(MIN_FONT_SIZE)
510}
511
512/// Returns the adjusted buffer font size.
513pub fn get_buffer_font_size(cx: &AppContext) -> Pixels {
514    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
515    cx.try_global::<AdjustedBufferFontSize>()
516        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0)
517}
518
519/// Adjusts the buffer font size.
520pub fn adjust_buffer_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
521    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
522    let mut adjusted_size = cx
523        .try_global::<AdjustedBufferFontSize>()
524        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
525
526    f(&mut adjusted_size);
527    adjusted_size = adjusted_size.max(MIN_FONT_SIZE);
528    cx.set_global(AdjustedBufferFontSize(adjusted_size));
529    cx.refresh();
530}
531
532/// Returns whether the buffer font size has been adjusted.
533pub fn has_adjusted_buffer_font_size(cx: &AppContext) -> bool {
534    cx.has_global::<AdjustedBufferFontSize>()
535}
536
537/// Resets the buffer font size to the default value.
538pub fn reset_buffer_font_size(cx: &mut AppContext) {
539    if cx.has_global::<AdjustedBufferFontSize>() {
540        cx.remove_global::<AdjustedBufferFontSize>();
541        cx.refresh();
542    }
543}
544
545// TODO: Make private, change usages to use `get_ui_font_size` instead.
546#[allow(missing_docs)]
547pub fn setup_ui_font(cx: &mut WindowContext) -> gpui::Font {
548    let (ui_font, ui_font_size) = {
549        let theme_settings = ThemeSettings::get_global(cx);
550        let font = theme_settings.ui_font.clone();
551        (font, get_ui_font_size(cx))
552    };
553
554    cx.set_rem_size(ui_font_size);
555    ui_font
556}
557
558/// Gets the adjusted UI font size.
559pub fn get_ui_font_size(cx: &AppContext) -> Pixels {
560    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
561    cx.try_global::<AdjustedUiFontSize>()
562        .map_or(ui_font_size, |adjusted_size| adjusted_size.0)
563}
564
565/// Sets the adjusted UI font size.
566pub fn adjust_ui_font_size(cx: &mut AppContext, f: fn(&mut Pixels)) {
567    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
568    let mut adjusted_size = cx
569        .try_global::<AdjustedUiFontSize>()
570        .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
571
572    f(&mut adjusted_size);
573    adjusted_size = adjusted_size.max(MIN_FONT_SIZE);
574    cx.set_global(AdjustedUiFontSize(adjusted_size));
575    cx.refresh();
576}
577
578/// Returns whether the UI font size has been adjusted.
579pub fn has_adjusted_ui_font_size(cx: &AppContext) -> bool {
580    cx.has_global::<AdjustedUiFontSize>()
581}
582
583/// Resets the UI font size to the default value.
584pub fn reset_ui_font_size(cx: &mut AppContext) {
585    if cx.has_global::<AdjustedUiFontSize>() {
586        cx.remove_global::<AdjustedUiFontSize>();
587        cx.refresh();
588    }
589}
590
591fn clamp_font_weight(weight: f32) -> FontWeight {
592    FontWeight(weight.clamp(100., 950.))
593}
594
595impl settings::Settings for ThemeSettings {
596    const KEY: Option<&'static str> = None;
597
598    type FileContent = ThemeSettingsContent;
599
600    fn load(sources: SettingsSources<Self::FileContent>, cx: &mut AppContext) -> Result<Self> {
601        let themes = ThemeRegistry::default_global(cx);
602        let system_appearance = SystemAppearance::default_global(cx);
603
604        let defaults = sources.default;
605        let mut this = Self {
606            ui_font_size: defaults.ui_font_size.unwrap().into(),
607            ui_font: Font {
608                family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
609                features: defaults.ui_font_features.clone().unwrap(),
610                fallbacks: defaults
611                    .ui_font_fallbacks
612                    .as_ref()
613                    .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
614                weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
615                style: Default::default(),
616            },
617            buffer_font: Font {
618                family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
619                features: defaults.buffer_font_features.clone().unwrap(),
620                fallbacks: defaults
621                    .buffer_font_fallbacks
622                    .as_ref()
623                    .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
624                weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
625                style: FontStyle::default(),
626            },
627            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
628            buffer_line_height: defaults.buffer_line_height.unwrap(),
629            theme_selection: defaults.theme.clone(),
630            active_theme: themes
631                .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
632                .or(themes.get(&zed_default_dark().name))
633                .unwrap(),
634            theme_overrides: None,
635            ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
636            unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
637        };
638
639        for value in sources
640            .user
641            .into_iter()
642            .chain(sources.release_channel)
643            .chain(sources.server)
644        {
645            if let Some(value) = value.ui_density {
646                this.ui_density = value;
647            }
648
649            if let Some(value) = value.buffer_font_family.clone() {
650                this.buffer_font.family = value.into();
651            }
652            if let Some(value) = value.buffer_font_features.clone() {
653                this.buffer_font.features = value;
654            }
655            if let Some(value) = value.buffer_font_fallbacks.clone() {
656                this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
657            }
658            if let Some(value) = value.buffer_font_weight {
659                this.buffer_font.weight = clamp_font_weight(value);
660            }
661
662            if let Some(value) = value.ui_font_family.clone() {
663                this.ui_font.family = value.into();
664            }
665            if let Some(value) = value.ui_font_features.clone() {
666                this.ui_font.features = value;
667            }
668            if let Some(value) = value.ui_font_fallbacks.clone() {
669                this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
670            }
671            if let Some(value) = value.ui_font_weight {
672                this.ui_font.weight = clamp_font_weight(value);
673            }
674
675            if let Some(value) = &value.theme {
676                this.theme_selection = Some(value.clone());
677
678                let theme_name = value.theme(*system_appearance);
679
680                if let Some(theme) = themes.get(theme_name).log_err() {
681                    this.active_theme = theme;
682                }
683            }
684
685            this.theme_overrides.clone_from(&value.theme_overrides);
686            this.apply_theme_overrides();
687
688            merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
689            this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
690
691            merge(
692                &mut this.buffer_font_size,
693                value.buffer_font_size.map(Into::into),
694            );
695            this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
696
697            merge(&mut this.buffer_line_height, value.buffer_line_height);
698
699            // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
700            merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
701            this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
702        }
703
704        Ok(this)
705    }
706
707    fn json_schema(
708        generator: &mut SchemaGenerator,
709        params: &SettingsJsonSchemaParams,
710        cx: &AppContext,
711    ) -> schemars::schema::RootSchema {
712        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
713        let theme_names = ThemeRegistry::global(cx)
714            .list_names()
715            .into_iter()
716            .map(|theme_name| Value::String(theme_name.to_string()))
717            .collect();
718
719        let theme_name_schema = SchemaObject {
720            instance_type: Some(InstanceType::String.into()),
721            enum_values: Some(theme_names),
722            ..Default::default()
723        };
724
725        root_schema.definitions.extend([
726            ("ThemeName".into(), theme_name_schema.into()),
727            ("FontFamilies".into(), params.font_family_schema()),
728            ("FontFallbacks".into(), params.font_fallback_schema()),
729        ]);
730
731        add_references_to_properties(
732            &mut root_schema,
733            &[
734                ("buffer_font_family", "#/definitions/FontFamilies"),
735                ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
736                ("ui_font_family", "#/definitions/FontFamilies"),
737                ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
738            ],
739        );
740
741        root_schema
742    }
743}
744
745fn merge<T: Copy>(target: &mut T, value: Option<T>) {
746    if let Some(value) = value {
747        *target = value;
748    }
749}