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    /// TODO: Document this further
116    pub theme_selection: Option<ThemeSelection>,
117    /// The active theme.
118    pub active_theme: Arc<Theme>,
119    /// Manual overrides for the active theme.
120    ///
121    /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
122    pub theme_overrides: Option<ThemeStyleContent>,
123    /// The active icon theme.
124    pub active_icon_theme: Arc<IconTheme>,
125    /// The density of the UI.
126    /// Note: This setting is still experimental. See [this tracking issue](
127    pub ui_density: UiDensity,
128    /// The amount of fading applied to unnecessary code.
129    pub unnecessary_code_fade: f32,
130}
131
132impl ThemeSettings {
133    const DEFAULT_LIGHT_THEME: &'static str = "One Light";
134    const DEFAULT_DARK_THEME: &'static str = "One Dark";
135
136    /// Returns the name of the default theme for the given [`Appearance`].
137    pub fn default_theme(appearance: Appearance) -> &'static str {
138        match appearance {
139            Appearance::Light => Self::DEFAULT_LIGHT_THEME,
140            Appearance::Dark => Self::DEFAULT_DARK_THEME,
141        }
142    }
143
144    /// Reloads the current theme.
145    ///
146    /// Reads the [`ThemeSettings`] to know which theme should be loaded,
147    /// taking into account the current [`SystemAppearance`].
148    pub fn reload_current_theme(cx: &mut App) {
149        let mut theme_settings = ThemeSettings::get_global(cx).clone();
150        let system_appearance = SystemAppearance::global(cx);
151
152        if let Some(theme_selection) = theme_settings.theme_selection.clone() {
153            let mut theme_name = theme_selection.theme(*system_appearance);
154
155            // If the selected theme doesn't exist, fall back to a default theme
156            // based on the system appearance.
157            let theme_registry = ThemeRegistry::global(cx);
158            if theme_registry.get(theme_name).ok().is_none() {
159                theme_name = Self::default_theme(*system_appearance);
160            };
161
162            if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
163                ThemeSettings::override_global(theme_settings, cx);
164            }
165        }
166    }
167
168    /// Reloads the current icon theme.
169    ///
170    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded.
171    pub fn reload_current_icon_theme(cx: &mut App) {
172        let mut theme_settings = ThemeSettings::get_global(cx).clone();
173
174        let active_theme = theme_settings.active_icon_theme.clone();
175        let mut icon_theme_name = active_theme.name.as_ref();
176
177        // If the selected theme doesn't exist, fall back to the default theme.
178        let theme_registry = ThemeRegistry::global(cx);
179        if theme_registry
180            .get_icon_theme(icon_theme_name)
181            .ok()
182            .is_none()
183        {
184            icon_theme_name = DEFAULT_ICON_THEME_NAME;
185        };
186
187        if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
188            ThemeSettings::override_global(theme_settings, cx);
189        }
190    }
191}
192
193/// The appearance of the system.
194#[derive(Debug, Clone, Copy, Deref)]
195pub struct SystemAppearance(pub Appearance);
196
197impl Default for SystemAppearance {
198    fn default() -> Self {
199        Self(Appearance::Dark)
200    }
201}
202
203#[derive(Deref, DerefMut, Default)]
204struct GlobalSystemAppearance(SystemAppearance);
205
206impl Global for GlobalSystemAppearance {}
207
208impl SystemAppearance {
209    /// Initializes the [`SystemAppearance`] for the application.
210    pub fn init(cx: &mut App) {
211        *cx.default_global::<GlobalSystemAppearance>() =
212            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
213    }
214
215    /// Returns the global [`SystemAppearance`].
216    ///
217    /// Inserts a default [`SystemAppearance`] if one does not yet exist.
218    pub(crate) fn default_global(cx: &mut App) -> Self {
219        cx.default_global::<GlobalSystemAppearance>().0
220    }
221
222    /// Returns the global [`SystemAppearance`].
223    pub fn global(cx: &App) -> Self {
224        cx.global::<GlobalSystemAppearance>().0
225    }
226
227    /// Returns a mutable reference to the global [`SystemAppearance`].
228    pub fn global_mut(cx: &mut App) -> &mut Self {
229        cx.global_mut::<GlobalSystemAppearance>()
230    }
231}
232
233/// Represents the selection of a theme, which can be either static or dynamic.
234#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
235#[serde(untagged)]
236pub enum ThemeSelection {
237    /// A static theme selection, represented by a single theme name.
238    Static(#[schemars(schema_with = "theme_name_ref")] String),
239    /// A dynamic theme selection, which can change based the [ThemeMode].
240    Dynamic {
241        /// The mode used to determine which theme to use.
242        #[serde(default)]
243        mode: ThemeMode,
244        /// The theme to use for light mode.
245        #[schemars(schema_with = "theme_name_ref")]
246        light: String,
247        /// The theme to use for dark mode.
248        #[schemars(schema_with = "theme_name_ref")]
249        dark: String,
250    },
251}
252
253fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
254    Schema::new_ref("#/definitions/ThemeName".into())
255}
256
257// TODO: Rename ThemeMode -> ThemeAppearanceMode
258/// The mode use to select a theme.
259///
260/// `Light` and `Dark` will select their respective themes.
261///
262/// `System` will select the theme based on the system's appearance.
263#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
264#[serde(rename_all = "snake_case")]
265pub enum ThemeMode {
266    /// Use the specified `light` theme.
267    Light,
268
269    /// Use the specified `dark` theme.
270    Dark,
271
272    /// Use the theme based on the system's appearance.
273    #[default]
274    System,
275}
276
277impl ThemeSelection {
278    /// Returns the theme name for the selected [ThemeMode].
279    pub fn theme(&self, system_appearance: Appearance) -> &str {
280        match self {
281            Self::Static(theme) => theme,
282            Self::Dynamic { mode, light, dark } => match mode {
283                ThemeMode::Light => light,
284                ThemeMode::Dark => dark,
285                ThemeMode::System => match system_appearance {
286                    Appearance::Light => light,
287                    Appearance::Dark => dark,
288                },
289            },
290        }
291    }
292
293    /// Returns the [ThemeMode] for the [ThemeSelection].
294    pub fn mode(&self) -> Option<ThemeMode> {
295        match self {
296            ThemeSelection::Static(_) => None,
297            ThemeSelection::Dynamic { mode, .. } => Some(*mode),
298        }
299    }
300}
301
302/// Settings for rendering text in UI and text buffers.
303#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
304pub struct ThemeSettingsContent {
305    /// The default font size for text in the UI.
306    #[serde(default)]
307    pub ui_font_size: Option<f32>,
308    /// The name of a font to use for rendering in the UI.
309    #[serde(default)]
310    pub ui_font_family: Option<String>,
311    /// The font fallbacks to use for rendering in the UI.
312    #[serde(default)]
313    #[schemars(default = "default_font_fallbacks")]
314    pub ui_font_fallbacks: Option<Vec<String>>,
315    /// The OpenType features to enable for text in the UI.
316    #[serde(default)]
317    #[schemars(default = "default_font_features")]
318    pub ui_font_features: Option<FontFeatures>,
319    /// The weight of the UI font in CSS units from 100 to 900.
320    #[serde(default)]
321    pub ui_font_weight: Option<f32>,
322    /// The name of a font to use for rendering in text buffers.
323    #[serde(default)]
324    pub buffer_font_family: Option<String>,
325    /// The font fallbacks to use for rendering in text buffers.
326    #[serde(default)]
327    #[schemars(default = "default_font_fallbacks")]
328    pub buffer_font_fallbacks: Option<Vec<String>>,
329    /// The default font size for rendering in text buffers.
330    #[serde(default)]
331    pub buffer_font_size: Option<f32>,
332    /// The weight of the editor font in CSS units from 100 to 900.
333    #[serde(default)]
334    pub buffer_font_weight: Option<f32>,
335    /// The buffer's line height.
336    #[serde(default)]
337    pub buffer_line_height: Option<BufferLineHeight>,
338    /// The OpenType features to enable for rendering in text buffers.
339    #[serde(default)]
340    #[schemars(default = "default_font_features")]
341    pub buffer_font_features: Option<FontFeatures>,
342    /// The name of the Zed theme to use.
343    #[serde(default)]
344    pub theme: Option<ThemeSelection>,
345    /// The name of the icon theme to use.
346    #[serde(default)]
347    pub icon_theme: Option<String>,
348
349    /// UNSTABLE: Expect many elements to be broken.
350    ///
351    // Controls the density of the UI.
352    #[serde(rename = "unstable.ui_density", default)]
353    pub ui_density: Option<UiDensity>,
354
355    /// How much to fade out unused code.
356    #[serde(default)]
357    pub unnecessary_code_fade: Option<f32>,
358
359    /// EXPERIMENTAL: Overrides for the current theme.
360    ///
361    /// These values will override the ones on the current theme specified in `theme`.
362    #[serde(rename = "experimental.theme_overrides", default)]
363    pub theme_overrides: Option<ThemeStyleContent>,
364}
365
366fn default_font_features() -> Option<FontFeatures> {
367    Some(FontFeatures::default())
368}
369
370fn default_font_fallbacks() -> Option<FontFallbacks> {
371    Some(FontFallbacks::default())
372}
373
374impl ThemeSettingsContent {
375    /// Sets the theme for the given appearance to the theme with the specified name.
376    pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
377        if let Some(selection) = self.theme.as_mut() {
378            let theme_to_update = match selection {
379                ThemeSelection::Static(theme) => theme,
380                ThemeSelection::Dynamic { mode, light, dark } => match mode {
381                    ThemeMode::Light => light,
382                    ThemeMode::Dark => dark,
383                    ThemeMode::System => match appearance {
384                        Appearance::Light => light,
385                        Appearance::Dark => dark,
386                    },
387                },
388            };
389
390            *theme_to_update = theme_name.to_string();
391        } else {
392            self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
393        }
394    }
395
396    /// Sets the mode for the theme.
397    pub fn set_mode(&mut self, mode: ThemeMode) {
398        if let Some(selection) = self.theme.as_mut() {
399            match selection {
400                ThemeSelection::Static(theme) => {
401                    // If the theme was previously set to a single static theme,
402                    // we don't know whether it was a light or dark theme, so we
403                    // just use it for both.
404                    self.theme = Some(ThemeSelection::Dynamic {
405                        mode,
406                        light: theme.clone(),
407                        dark: theme.clone(),
408                    });
409                }
410                ThemeSelection::Dynamic {
411                    mode: mode_to_update,
412                    ..
413                } => *mode_to_update = mode,
414            }
415        } else {
416            self.theme = Some(ThemeSelection::Dynamic {
417                mode,
418                light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
419                dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
420            });
421        }
422    }
423}
424
425/// The buffer's line height.
426#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
427#[serde(rename_all = "snake_case")]
428pub enum BufferLineHeight {
429    /// A less dense line height.
430    #[default]
431    Comfortable,
432    /// The default line height.
433    Standard,
434    /// A custom line height.
435    ///
436    /// A line height of 1.0 is the height of the buffer's font size.
437    Custom(f32),
438}
439
440impl BufferLineHeight {
441    /// Returns the value of the line height.
442    pub fn value(&self) -> f32 {
443        match self {
444            BufferLineHeight::Comfortable => 1.618,
445            BufferLineHeight::Standard => 1.3,
446            BufferLineHeight::Custom(line_height) => *line_height,
447        }
448    }
449}
450
451impl ThemeSettings {
452    /// Returns the buffer font size.
453    pub fn buffer_font_size(&self) -> Pixels {
454        Self::clamp_font_size(self.buffer_font_size)
455    }
456
457    /// Ensures that the font size is within the valid range.
458    pub fn clamp_font_size(size: Pixels) -> Pixels {
459        size.max(MIN_FONT_SIZE)
460    }
461
462    // TODO: Rename: `line_height` -> `buffer_line_height`
463    /// Returns the buffer's line height.
464    pub fn line_height(&self) -> f32 {
465        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
466    }
467
468    /// Switches to the theme with the given name, if it exists.
469    ///
470    /// Returns a `Some` containing the new theme if it was successful.
471    /// Returns `None` otherwise.
472    pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
473        let themes = ThemeRegistry::default_global(cx);
474
475        let mut new_theme = None;
476
477        if let Some(theme) = themes.get(theme).log_err() {
478            self.active_theme = theme.clone();
479            new_theme = Some(theme);
480        }
481
482        self.apply_theme_overrides();
483
484        new_theme
485    }
486
487    /// Applies the theme overrides, if there are any, to the current theme.
488    pub fn apply_theme_overrides(&mut self) {
489        if let Some(theme_overrides) = &self.theme_overrides {
490            let mut base_theme = (*self.active_theme).clone();
491
492            if let Some(window_background_appearance) = theme_overrides.window_background_appearance
493            {
494                base_theme.styles.window_background_appearance =
495                    window_background_appearance.into();
496            }
497
498            base_theme
499                .styles
500                .colors
501                .refine(&theme_overrides.theme_colors_refinement());
502            base_theme
503                .styles
504                .status
505                .refine(&theme_overrides.status_colors_refinement());
506            base_theme.styles.player.merge(&theme_overrides.players);
507            base_theme.styles.accents.merge(&theme_overrides.accents);
508            base_theme.styles.syntax =
509                SyntaxTheme::merge(base_theme.styles.syntax, theme_overrides.syntax_overrides());
510
511            self.active_theme = Arc::new(base_theme);
512        }
513    }
514
515    /// Switches to the icon theme with the given name, if it exists.
516    ///
517    /// Returns a `Some` containing the new icon theme if it was successful.
518    /// Returns `None` otherwise.
519    pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
520        let themes = ThemeRegistry::default_global(cx);
521
522        let mut new_icon_theme = None;
523
524        if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
525            self.active_icon_theme = icon_theme.clone();
526            new_icon_theme = Some(icon_theme);
527            cx.refresh_windows();
528        }
529
530        new_icon_theme
531    }
532}
533
534// TODO: Make private, change usages to use `get_ui_font_size` instead.
535#[allow(missing_docs)]
536pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
537    let (ui_font, ui_font_size) = {
538        let theme_settings = ThemeSettings::get_global(cx);
539        let font = theme_settings.ui_font.clone();
540        (font, theme_settings.ui_font_size)
541    };
542
543    window.set_rem_size(ui_font_size);
544    ui_font
545}
546
547fn clamp_font_weight(weight: f32) -> FontWeight {
548    FontWeight(weight.clamp(100., 950.))
549}
550
551impl settings::Settings for ThemeSettings {
552    const KEY: Option<&'static str> = None;
553
554    type FileContent = ThemeSettingsContent;
555
556    fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
557        let themes = ThemeRegistry::default_global(cx);
558        let system_appearance = SystemAppearance::default_global(cx);
559
560        let defaults = sources.default;
561        let mut this = Self {
562            ui_font_size: defaults.ui_font_size.unwrap().into(),
563            ui_font: Font {
564                family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
565                features: defaults.ui_font_features.clone().unwrap(),
566                fallbacks: defaults
567                    .ui_font_fallbacks
568                    .as_ref()
569                    .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
570                weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
571                style: Default::default(),
572            },
573            buffer_font: Font {
574                family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
575                features: defaults.buffer_font_features.clone().unwrap(),
576                fallbacks: defaults
577                    .buffer_font_fallbacks
578                    .as_ref()
579                    .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
580                weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
581                style: FontStyle::default(),
582            },
583            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
584            buffer_line_height: defaults.buffer_line_height.unwrap(),
585            theme_selection: defaults.theme.clone(),
586            active_theme: themes
587                .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
588                .or(themes.get(&zed_default_dark().name))
589                .unwrap(),
590            theme_overrides: None,
591            active_icon_theme: defaults
592                .icon_theme
593                .as_ref()
594                .and_then(|name| themes.get_icon_theme(name).ok())
595                .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
596            ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
597            unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
598        };
599
600        for value in sources
601            .user
602            .into_iter()
603            .chain(sources.release_channel)
604            .chain(sources.server)
605        {
606            if let Some(value) = value.ui_density {
607                this.ui_density = value;
608            }
609
610            if let Some(value) = value.buffer_font_family.clone() {
611                this.buffer_font.family = value.into();
612            }
613            if let Some(value) = value.buffer_font_features.clone() {
614                this.buffer_font.features = value;
615            }
616            if let Some(value) = value.buffer_font_fallbacks.clone() {
617                this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
618            }
619            if let Some(value) = value.buffer_font_weight {
620                this.buffer_font.weight = clamp_font_weight(value);
621            }
622
623            if let Some(value) = value.ui_font_family.clone() {
624                this.ui_font.family = value.into();
625            }
626            if let Some(value) = value.ui_font_features.clone() {
627                this.ui_font.features = value;
628            }
629            if let Some(value) = value.ui_font_fallbacks.clone() {
630                this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
631            }
632            if let Some(value) = value.ui_font_weight {
633                this.ui_font.weight = clamp_font_weight(value);
634            }
635
636            if let Some(value) = &value.theme {
637                this.theme_selection = Some(value.clone());
638
639                let theme_name = value.theme(*system_appearance);
640
641                if let Some(theme) = themes.get(theme_name).log_err() {
642                    this.active_theme = theme;
643                }
644            }
645
646            this.theme_overrides.clone_from(&value.theme_overrides);
647            this.apply_theme_overrides();
648
649            if let Some(value) = &value.icon_theme {
650                if let Some(icon_theme) = themes.get_icon_theme(value).log_err() {
651                    this.active_icon_theme = icon_theme.clone();
652                }
653            }
654
655            merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
656            this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
657
658            merge(
659                &mut this.buffer_font_size,
660                value.buffer_font_size.map(Into::into),
661            );
662            this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
663
664            merge(&mut this.buffer_line_height, value.buffer_line_height);
665
666            // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
667            merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
668            this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
669        }
670
671        Ok(this)
672    }
673
674    fn json_schema(
675        generator: &mut SchemaGenerator,
676        params: &SettingsJsonSchemaParams,
677        cx: &App,
678    ) -> schemars::schema::RootSchema {
679        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
680        let theme_names = ThemeRegistry::global(cx)
681            .list_names()
682            .into_iter()
683            .map(|theme_name| Value::String(theme_name.to_string()))
684            .collect();
685
686        let theme_name_schema = SchemaObject {
687            instance_type: Some(InstanceType::String.into()),
688            enum_values: Some(theme_names),
689            ..Default::default()
690        };
691
692        root_schema.definitions.extend([
693            ("ThemeName".into(), theme_name_schema.into()),
694            ("FontFamilies".into(), params.font_family_schema()),
695            ("FontFallbacks".into(), params.font_fallback_schema()),
696        ]);
697
698        add_references_to_properties(
699            &mut root_schema,
700            &[
701                ("buffer_font_family", "#/definitions/FontFamilies"),
702                ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
703                ("ui_font_family", "#/definitions/FontFamilies"),
704                ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
705            ],
706        );
707
708        root_schema
709    }
710}
711
712fn merge<T: Copy>(target: &mut T, value: Option<T>) {
713    if let Some(value) = value {
714        *target = value;
715    }
716}