settings.rs

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