settings.rs

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