settings.rs

  1use crate::fallback_themes::zed_default_dark;
  2use crate::{
  3    Appearance, IconTheme, IconThemeNotFoundError, SyntaxTheme, Theme, ThemeNotFoundError,
  4    ThemeRegistry, ThemeStyleContent, DEFAULT_ICON_THEME_NAME,
  5};
  6use anyhow::Result;
  7use derive_more::{Deref, DerefMut};
  8use gpui::{
  9    px, App, Context, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels,
 10    Subscription, Window,
 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    ///
 99    /// Use [ThemeSettings::ui_font_size] to access this.
100    ui_font_size: Pixels,
101    /// The font used for UI elements.
102    pub ui_font: Font,
103    /// The font size used for buffers, and the terminal.
104    ///
105    /// The terminal font size can be overridden using it's own setting.
106    ///
107    /// Use [ThemeSettings::buffer_font_size] to access this.
108    buffer_font_size: Pixels,
109    /// The font used for buffers, and the terminal.
110    ///
111    /// The terminal font family can be overridden using it's own setting.
112    pub buffer_font: Font,
113    /// The line height for buffers, and the terminal.
114    ///
115    /// Changing this may affect the spacing of some UI elements.
116    ///
117    /// The terminal font family can be overridden using it's own setting.
118    pub buffer_line_height: BufferLineHeight,
119    /// The current theme selection.
120    pub theme_selection: Option<ThemeSelection>,
121    /// The active theme.
122    pub active_theme: Arc<Theme>,
123    /// Manual overrides for the active theme.
124    ///
125    /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
126    pub theme_overrides: Option<ThemeStyleContent>,
127    /// The current icon theme selection.
128    pub icon_theme_selection: Option<IconThemeSelection>,
129    /// The active icon theme.
130    pub active_icon_theme: Arc<IconTheme>,
131    /// The density of the UI.
132    /// Note: This setting is still experimental. See [this tracking issue](
133    pub ui_density: UiDensity,
134    /// The amount of fading applied to unnecessary code.
135    pub unnecessary_code_fade: f32,
136}
137
138impl ThemeSettings {
139    const DEFAULT_LIGHT_THEME: &'static str = "One Light";
140    const DEFAULT_DARK_THEME: &'static str = "One Dark";
141
142    /// Returns the name of the default theme for the given [`Appearance`].
143    pub fn default_theme(appearance: Appearance) -> &'static str {
144        match appearance {
145            Appearance::Light => Self::DEFAULT_LIGHT_THEME,
146            Appearance::Dark => Self::DEFAULT_DARK_THEME,
147        }
148    }
149
150    /// Reloads the current theme.
151    ///
152    /// Reads the [`ThemeSettings`] to know which theme should be loaded,
153    /// taking into account the current [`SystemAppearance`].
154    pub fn reload_current_theme(cx: &mut App) {
155        let mut theme_settings = ThemeSettings::get_global(cx).clone();
156        let system_appearance = SystemAppearance::global(cx);
157
158        if let Some(theme_selection) = theme_settings.theme_selection.clone() {
159            let mut theme_name = theme_selection.theme(*system_appearance);
160
161            // If the selected theme doesn't exist, fall back to a default theme
162            // based on the system appearance.
163            let theme_registry = ThemeRegistry::global(cx);
164            if let Err(err @ ThemeNotFoundError(_)) = theme_registry.get(theme_name) {
165                if theme_registry.extensions_loaded() {
166                    log::error!("{err}");
167                }
168
169                theme_name = Self::default_theme(*system_appearance);
170            };
171
172            if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
173                ThemeSettings::override_global(theme_settings, cx);
174            }
175        }
176    }
177
178    /// Reloads the current icon theme.
179    ///
180    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded,
181    /// taking into account the current [`SystemAppearance`].
182    pub fn reload_current_icon_theme(cx: &mut App) {
183        let mut theme_settings = ThemeSettings::get_global(cx).clone();
184        let system_appearance = SystemAppearance::global(cx);
185
186        if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.clone() {
187            let mut icon_theme_name = icon_theme_selection.icon_theme(*system_appearance);
188
189            // If the selected icon theme doesn't exist, fall back to the default theme.
190            let theme_registry = ThemeRegistry::global(cx);
191            if let Err(err @ IconThemeNotFoundError(_)) =
192                theme_registry.get_icon_theme(icon_theme_name)
193            {
194                if theme_registry.extensions_loaded() {
195                    log::error!("{err}");
196                }
197
198                icon_theme_name = DEFAULT_ICON_THEME_NAME;
199            };
200
201            if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
202                ThemeSettings::override_global(theme_settings, cx);
203            }
204        }
205    }
206}
207
208/// The appearance of the system.
209#[derive(Debug, Clone, Copy, Deref)]
210pub struct SystemAppearance(pub Appearance);
211
212impl Default for SystemAppearance {
213    fn default() -> Self {
214        Self(Appearance::Dark)
215    }
216}
217
218#[derive(Deref, DerefMut, Default)]
219struct GlobalSystemAppearance(SystemAppearance);
220
221impl Global for GlobalSystemAppearance {}
222
223impl SystemAppearance {
224    /// Initializes the [`SystemAppearance`] for the application.
225    pub fn init(cx: &mut App) {
226        *cx.default_global::<GlobalSystemAppearance>() =
227            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
228    }
229
230    /// Returns the global [`SystemAppearance`].
231    ///
232    /// Inserts a default [`SystemAppearance`] if one does not yet exist.
233    pub(crate) fn default_global(cx: &mut App) -> Self {
234        cx.default_global::<GlobalSystemAppearance>().0
235    }
236
237    /// Returns the global [`SystemAppearance`].
238    pub fn global(cx: &App) -> Self {
239        cx.global::<GlobalSystemAppearance>().0
240    }
241
242    /// Returns a mutable reference to the global [`SystemAppearance`].
243    pub fn global_mut(cx: &mut App) -> &mut Self {
244        cx.global_mut::<GlobalSystemAppearance>()
245    }
246}
247
248#[derive(Default)]
249pub(crate) struct AdjustedBufferFontSize(Pixels);
250
251impl Global for AdjustedBufferFontSize {}
252
253#[derive(Default)]
254pub(crate) struct AdjustedUiFontSize(Pixels);
255
256impl Global for AdjustedUiFontSize {}
257
258/// Represents the selection of a theme, which can be either static or dynamic.
259#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
260#[serde(untagged)]
261pub enum ThemeSelection {
262    /// A static theme selection, represented by a single theme name.
263    Static(#[schemars(schema_with = "theme_name_ref")] String),
264    /// A dynamic theme selection, which can change based the [ThemeMode].
265    Dynamic {
266        /// The mode used to determine which theme to use.
267        #[serde(default)]
268        mode: ThemeMode,
269        /// The theme to use for light mode.
270        #[schemars(schema_with = "theme_name_ref")]
271        light: String,
272        /// The theme to use for dark mode.
273        #[schemars(schema_with = "theme_name_ref")]
274        dark: String,
275    },
276}
277
278fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
279    Schema::new_ref("#/definitions/ThemeName".into())
280}
281
282// TODO: Rename ThemeMode -> ThemeAppearanceMode
283/// The mode use to select a theme.
284///
285/// `Light` and `Dark` will select their respective themes.
286///
287/// `System` will select the theme based on the system's appearance.
288#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
289#[serde(rename_all = "snake_case")]
290pub enum ThemeMode {
291    /// Use the specified `light` theme.
292    Light,
293
294    /// Use the specified `dark` theme.
295    Dark,
296
297    /// Use the theme based on the system's appearance.
298    #[default]
299    System,
300}
301
302impl ThemeSelection {
303    /// Returns the theme name for the selected [ThemeMode].
304    pub fn theme(&self, system_appearance: Appearance) -> &str {
305        match self {
306            Self::Static(theme) => theme,
307            Self::Dynamic { mode, light, dark } => match mode {
308                ThemeMode::Light => light,
309                ThemeMode::Dark => dark,
310                ThemeMode::System => match system_appearance {
311                    Appearance::Light => light,
312                    Appearance::Dark => dark,
313                },
314            },
315        }
316    }
317
318    /// Returns the [ThemeMode] for the [ThemeSelection].
319    pub fn mode(&self) -> Option<ThemeMode> {
320        match self {
321            ThemeSelection::Static(_) => None,
322            ThemeSelection::Dynamic { mode, .. } => Some(*mode),
323        }
324    }
325}
326
327fn icon_theme_name_ref(_: &mut SchemaGenerator) -> Schema {
328    Schema::new_ref("#/definitions/IconThemeName".into())
329}
330
331/// Represents the selection of an icon theme, which can be either static or dynamic.
332#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
333#[serde(untagged)]
334pub enum IconThemeSelection {
335    /// A static icon theme selection, represented by a single icon theme name.
336    Static(#[schemars(schema_with = "icon_theme_name_ref")] String),
337    /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
338    Dynamic {
339        /// The mode used to determine which theme to use.
340        #[serde(default)]
341        mode: ThemeMode,
342        /// The icon theme to use for light mode.
343        #[schemars(schema_with = "icon_theme_name_ref")]
344        light: String,
345        /// The icon theme to use for dark mode.
346        #[schemars(schema_with = "icon_theme_name_ref")]
347        dark: String,
348    },
349}
350
351impl IconThemeSelection {
352    /// Returns the icon theme name based on the given [`Appearance`].
353    pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
354        match self {
355            Self::Static(theme) => theme,
356            Self::Dynamic { mode, light, dark } => match mode {
357                ThemeMode::Light => light,
358                ThemeMode::Dark => dark,
359                ThemeMode::System => match system_appearance {
360                    Appearance::Light => light,
361                    Appearance::Dark => dark,
362                },
363            },
364        }
365    }
366
367    /// Returns the [`ThemeMode`] for the [`IconThemeSelection`].
368    pub fn mode(&self) -> Option<ThemeMode> {
369        match self {
370            IconThemeSelection::Static(_) => None,
371            IconThemeSelection::Dynamic { mode, .. } => Some(*mode),
372        }
373    }
374}
375
376/// Settings for rendering text in UI and text buffers.
377#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
378pub struct ThemeSettingsContent {
379    /// The default font size for text in the UI.
380    #[serde(default)]
381    pub ui_font_size: Option<f32>,
382    /// The name of a font to use for rendering in the UI.
383    #[serde(default)]
384    pub ui_font_family: Option<String>,
385    /// The font fallbacks to use for rendering in the UI.
386    #[serde(default)]
387    #[schemars(default = "default_font_fallbacks")]
388    pub ui_font_fallbacks: Option<Vec<String>>,
389    /// The OpenType features to enable for text in the UI.
390    #[serde(default)]
391    #[schemars(default = "default_font_features")]
392    pub ui_font_features: Option<FontFeatures>,
393    /// The weight of the UI font in CSS units from 100 to 900.
394    #[serde(default)]
395    pub ui_font_weight: Option<f32>,
396    /// The name of a font to use for rendering in text buffers.
397    #[serde(default)]
398    pub buffer_font_family: Option<String>,
399    /// The font fallbacks to use for rendering in text buffers.
400    #[serde(default)]
401    #[schemars(default = "default_font_fallbacks")]
402    pub buffer_font_fallbacks: Option<Vec<String>>,
403    /// The default font size for rendering in text buffers.
404    #[serde(default)]
405    pub buffer_font_size: Option<f32>,
406    /// The weight of the editor font in CSS units from 100 to 900.
407    #[serde(default)]
408    pub buffer_font_weight: Option<f32>,
409    /// The buffer's line height.
410    #[serde(default)]
411    pub buffer_line_height: Option<BufferLineHeight>,
412    /// The OpenType features to enable for rendering in text buffers.
413    #[serde(default)]
414    #[schemars(default = "default_font_features")]
415    pub buffer_font_features: Option<FontFeatures>,
416    /// The name of the Zed theme to use.
417    #[serde(default)]
418    pub theme: Option<ThemeSelection>,
419    /// The name of the icon theme to use.
420    #[serde(default)]
421    pub icon_theme: Option<IconThemeSelection>,
422
423    /// UNSTABLE: Expect many elements to be broken.
424    ///
425    // Controls the density of the UI.
426    #[serde(rename = "unstable.ui_density", default)]
427    pub ui_density: Option<UiDensity>,
428
429    /// How much to fade out unused code.
430    #[serde(default)]
431    pub unnecessary_code_fade: Option<f32>,
432
433    /// EXPERIMENTAL: Overrides for the current theme.
434    ///
435    /// These values will override the ones on the current theme specified in `theme`.
436    #[serde(rename = "experimental.theme_overrides", default)]
437    pub theme_overrides: Option<ThemeStyleContent>,
438}
439
440fn default_font_features() -> Option<FontFeatures> {
441    Some(FontFeatures::default())
442}
443
444fn default_font_fallbacks() -> Option<FontFallbacks> {
445    Some(FontFallbacks::default())
446}
447
448impl ThemeSettingsContent {
449    /// Sets the theme for the given appearance to the theme with the specified name.
450    pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
451        if let Some(selection) = self.theme.as_mut() {
452            let theme_to_update = match selection {
453                ThemeSelection::Static(theme) => theme,
454                ThemeSelection::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            *theme_to_update = theme_name.to_string();
465        } else {
466            self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
467        }
468    }
469
470    /// Sets the icon theme for the given appearance to the icon theme with the specified name.
471    pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
472        if let Some(selection) = self.icon_theme.as_mut() {
473            let icon_theme_to_update = match selection {
474                IconThemeSelection::Static(theme) => theme,
475                IconThemeSelection::Dynamic { mode, light, dark } => match mode {
476                    ThemeMode::Light => light,
477                    ThemeMode::Dark => dark,
478                    ThemeMode::System => match appearance {
479                        Appearance::Light => light,
480                        Appearance::Dark => dark,
481                    },
482                },
483            };
484
485            *icon_theme_to_update = icon_theme_name.to_string();
486        } else {
487            self.icon_theme = Some(IconThemeSelection::Static(icon_theme_name.to_string()));
488        }
489    }
490
491    /// Sets the mode for the theme.
492    pub fn set_mode(&mut self, mode: ThemeMode) {
493        if let Some(selection) = self.theme.as_mut() {
494            match selection {
495                ThemeSelection::Static(theme) => {
496                    // If the theme was previously set to a single static theme,
497                    // we don't know whether it was a light or dark theme, so we
498                    // just use it for both.
499                    self.theme = Some(ThemeSelection::Dynamic {
500                        mode,
501                        light: theme.clone(),
502                        dark: theme.clone(),
503                    });
504                }
505                ThemeSelection::Dynamic {
506                    mode: mode_to_update,
507                    ..
508                } => *mode_to_update = mode,
509            }
510        } else {
511            self.theme = Some(ThemeSelection::Dynamic {
512                mode,
513                light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
514                dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
515            });
516        }
517
518        if let Some(selection) = self.icon_theme.as_mut() {
519            match selection {
520                IconThemeSelection::Static(icon_theme) => {
521                    // If the icon theme was previously set to a single static
522                    // theme, we don't know whether it was a light or dark
523                    // theme, so we just use it for both.
524                    self.icon_theme = Some(IconThemeSelection::Dynamic {
525                        mode,
526                        light: icon_theme.clone(),
527                        dark: icon_theme.clone(),
528                    });
529                }
530                IconThemeSelection::Dynamic {
531                    mode: mode_to_update,
532                    ..
533                } => *mode_to_update = mode,
534            }
535        } else {
536            self.icon_theme = Some(IconThemeSelection::Static(DEFAULT_ICON_THEME_NAME.into()));
537        }
538    }
539}
540
541/// The buffer's line height.
542#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
543#[serde(rename_all = "snake_case")]
544pub enum BufferLineHeight {
545    /// A less dense line height.
546    #[default]
547    Comfortable,
548    /// The default line height.
549    Standard,
550    /// A custom line height.
551    ///
552    /// A line height of 1.0 is the height of the buffer's font size.
553    Custom(f32),
554}
555
556impl BufferLineHeight {
557    /// Returns the value of the line height.
558    pub fn value(&self) -> f32 {
559        match self {
560            BufferLineHeight::Comfortable => 1.618,
561            BufferLineHeight::Standard => 1.3,
562            BufferLineHeight::Custom(line_height) => *line_height,
563        }
564    }
565}
566
567impl ThemeSettings {
568    /// Returns the buffer font size.
569    pub fn buffer_font_size(&self, cx: &App) -> Pixels {
570        let font_size = cx
571            .try_global::<AdjustedBufferFontSize>()
572            .map_or(self.buffer_font_size, |size| size.0);
573        clamp_font_size(font_size)
574    }
575
576    /// Returns the UI font size.
577    pub fn ui_font_size(&self, cx: &App) -> Pixels {
578        let font_size = cx
579            .try_global::<AdjustedUiFontSize>()
580            .map_or(self.ui_font_size, |size| size.0);
581        clamp_font_size(font_size)
582    }
583
584    // TODO: Rename: `line_height` -> `buffer_line_height`
585    /// Returns the buffer's line height.
586    pub fn line_height(&self) -> f32 {
587        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
588    }
589
590    /// Switches to the theme with the given name, if it exists.
591    ///
592    /// Returns a `Some` containing the new theme if it was successful.
593    /// Returns `None` otherwise.
594    pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
595        let themes = ThemeRegistry::default_global(cx);
596
597        let mut new_theme = None;
598
599        match themes.get(theme) {
600            Ok(theme) => {
601                self.active_theme = theme.clone();
602                new_theme = Some(theme);
603            }
604            Err(err @ ThemeNotFoundError(_)) => {
605                log::error!("{err}");
606            }
607        }
608
609        self.apply_theme_overrides();
610
611        new_theme
612    }
613
614    /// Applies the theme overrides, if there are any, to the current theme.
615    pub fn apply_theme_overrides(&mut self) {
616        if let Some(theme_overrides) = &self.theme_overrides {
617            let mut base_theme = (*self.active_theme).clone();
618
619            if let Some(window_background_appearance) = theme_overrides.window_background_appearance
620            {
621                base_theme.styles.window_background_appearance =
622                    window_background_appearance.into();
623            }
624
625            base_theme
626                .styles
627                .colors
628                .refine(&theme_overrides.theme_colors_refinement());
629            base_theme
630                .styles
631                .status
632                .refine(&theme_overrides.status_colors_refinement());
633            base_theme.styles.player.merge(&theme_overrides.players);
634            base_theme.styles.accents.merge(&theme_overrides.accents);
635            base_theme.styles.syntax =
636                SyntaxTheme::merge(base_theme.styles.syntax, theme_overrides.syntax_overrides());
637
638            self.active_theme = Arc::new(base_theme);
639        }
640    }
641
642    /// Switches to the icon theme with the given name, if it exists.
643    ///
644    /// Returns a `Some` containing the new icon theme if it was successful.
645    /// Returns `None` otherwise.
646    pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
647        let themes = ThemeRegistry::default_global(cx);
648
649        let mut new_icon_theme = None;
650
651        if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
652            self.active_icon_theme = icon_theme.clone();
653            new_icon_theme = Some(icon_theme);
654            cx.refresh_windows();
655        }
656
657        new_icon_theme
658    }
659}
660
661/// Observe changes to the adjusted buffer font size.
662pub fn observe_buffer_font_size_adjustment<V: 'static>(
663    cx: &mut Context<V>,
664    f: impl 'static + Fn(&mut V, &mut Context<V>),
665) -> Subscription {
666    cx.observe_global::<AdjustedBufferFontSize>(f)
667}
668
669/// Sets the adjusted buffer font size.
670pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
671    let adjusted_font_size = if let Some(AdjustedBufferFontSize(adjusted_size)) =
672        cx.try_global::<AdjustedBufferFontSize>()
673    {
674        let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
675        let delta = *adjusted_size - buffer_font_size;
676        size + delta
677    } else {
678        size
679    };
680    clamp_font_size(adjusted_font_size)
681}
682
683/// Returns the adjusted buffer font size.
684pub fn get_buffer_font_size(cx: &App) -> Pixels {
685    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
686    cx.try_global::<AdjustedBufferFontSize>()
687        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0)
688}
689
690/// Adjusts the buffer font size.
691pub fn adjust_buffer_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
692    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
693    let mut adjusted_size = cx
694        .try_global::<AdjustedBufferFontSize>()
695        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
696
697    f(&mut adjusted_size);
698    cx.set_global(AdjustedBufferFontSize(clamp_font_size(adjusted_size)));
699    cx.refresh_windows();
700}
701
702/// Returns whether the buffer font size has been adjusted.
703pub fn has_adjusted_buffer_font_size(cx: &App) -> bool {
704    cx.has_global::<AdjustedBufferFontSize>()
705}
706
707/// Resets the buffer font size to the default value.
708pub fn reset_buffer_font_size(cx: &mut App) {
709    if cx.has_global::<AdjustedBufferFontSize>() {
710        cx.remove_global::<AdjustedBufferFontSize>();
711        cx.refresh_windows();
712    }
713}
714
715// TODO: Make private, change usages to use `get_ui_font_size` instead.
716#[allow(missing_docs)]
717pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
718    let (ui_font, ui_font_size) = {
719        let theme_settings = ThemeSettings::get_global(cx);
720        let font = theme_settings.ui_font.clone();
721        (font, get_ui_font_size(cx))
722    };
723
724    window.set_rem_size(ui_font_size);
725    ui_font
726}
727
728/// Gets the adjusted UI font size.
729pub fn get_ui_font_size(cx: &App) -> Pixels {
730    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
731    cx.try_global::<AdjustedUiFontSize>()
732        .map_or(ui_font_size, |adjusted_size| adjusted_size.0)
733}
734
735/// Sets the adjusted UI font size.
736pub fn adjust_ui_font_size(cx: &mut App, mut f: impl FnMut(&mut Pixels)) {
737    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
738    let mut adjusted_size = cx
739        .try_global::<AdjustedUiFontSize>()
740        .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
741
742    f(&mut adjusted_size);
743    cx.set_global(AdjustedUiFontSize(clamp_font_size(adjusted_size)));
744    cx.refresh_windows();
745}
746
747/// Returns whether the UI font size has been adjusted.
748pub fn has_adjusted_ui_font_size(cx: &App) -> bool {
749    cx.has_global::<AdjustedUiFontSize>()
750}
751
752/// Resets the UI font size to the default value.
753pub fn reset_ui_font_size(cx: &mut App) {
754    if cx.has_global::<AdjustedUiFontSize>() {
755        cx.remove_global::<AdjustedUiFontSize>();
756        cx.refresh_windows();
757    }
758}
759
760/// Ensures font size is within the valid range.
761pub fn clamp_font_size(size: Pixels) -> Pixels {
762    size.max(MIN_FONT_SIZE)
763}
764
765fn clamp_font_weight(weight: f32) -> FontWeight {
766    FontWeight(weight.clamp(100., 950.))
767}
768
769impl settings::Settings for ThemeSettings {
770    const KEY: Option<&'static str> = None;
771
772    type FileContent = ThemeSettingsContent;
773
774    fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
775        let themes = ThemeRegistry::default_global(cx);
776        let system_appearance = SystemAppearance::default_global(cx);
777
778        let defaults = sources.default;
779        let mut this = Self {
780            ui_font_size: defaults.ui_font_size.unwrap().into(),
781            ui_font: Font {
782                family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
783                features: defaults.ui_font_features.clone().unwrap(),
784                fallbacks: defaults
785                    .ui_font_fallbacks
786                    .as_ref()
787                    .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
788                weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
789                style: Default::default(),
790            },
791            buffer_font: Font {
792                family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
793                features: defaults.buffer_font_features.clone().unwrap(),
794                fallbacks: defaults
795                    .buffer_font_fallbacks
796                    .as_ref()
797                    .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
798                weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
799                style: FontStyle::default(),
800            },
801            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
802            buffer_line_height: defaults.buffer_line_height.unwrap(),
803            theme_selection: defaults.theme.clone(),
804            active_theme: themes
805                .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
806                .or(themes.get(&zed_default_dark().name))
807                .unwrap(),
808            theme_overrides: None,
809            icon_theme_selection: defaults.icon_theme.clone(),
810            active_icon_theme: defaults
811                .icon_theme
812                .as_ref()
813                .and_then(|selection| {
814                    themes
815                        .get_icon_theme(selection.icon_theme(*system_appearance))
816                        .ok()
817                })
818                .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
819            ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
820            unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
821        };
822
823        for value in sources
824            .user
825            .into_iter()
826            .chain(sources.release_channel)
827            .chain(sources.server)
828        {
829            if let Some(value) = value.ui_density {
830                this.ui_density = value;
831            }
832
833            if let Some(value) = value.buffer_font_family.clone() {
834                this.buffer_font.family = value.into();
835            }
836            if let Some(value) = value.buffer_font_features.clone() {
837                this.buffer_font.features = value;
838            }
839            if let Some(value) = value.buffer_font_fallbacks.clone() {
840                this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
841            }
842            if let Some(value) = value.buffer_font_weight {
843                this.buffer_font.weight = clamp_font_weight(value);
844            }
845
846            if let Some(value) = value.ui_font_family.clone() {
847                this.ui_font.family = value.into();
848            }
849            if let Some(value) = value.ui_font_features.clone() {
850                this.ui_font.features = value;
851            }
852            if let Some(value) = value.ui_font_fallbacks.clone() {
853                this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
854            }
855            if let Some(value) = value.ui_font_weight {
856                this.ui_font.weight = clamp_font_weight(value);
857            }
858
859            if let Some(value) = &value.theme {
860                this.theme_selection = Some(value.clone());
861
862                let theme_name = value.theme(*system_appearance);
863
864                match themes.get(theme_name) {
865                    Ok(theme) => {
866                        this.active_theme = theme;
867                    }
868                    Err(err @ ThemeNotFoundError(_)) => {
869                        if themes.extensions_loaded() {
870                            log::error!("{err}");
871                        }
872                    }
873                }
874            }
875
876            this.theme_overrides.clone_from(&value.theme_overrides);
877            this.apply_theme_overrides();
878
879            if let Some(value) = &value.icon_theme {
880                this.icon_theme_selection = Some(value.clone());
881
882                let icon_theme_name = value.icon_theme(*system_appearance);
883
884                match themes.get_icon_theme(icon_theme_name) {
885                    Ok(icon_theme) => {
886                        this.active_icon_theme = icon_theme;
887                    }
888                    Err(err @ IconThemeNotFoundError(_)) => {
889                        if themes.extensions_loaded() {
890                            log::error!("{err}");
891                        }
892                    }
893                }
894            }
895
896            merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
897            this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
898
899            merge(
900                &mut this.buffer_font_size,
901                value.buffer_font_size.map(Into::into),
902            );
903            this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
904
905            merge(&mut this.buffer_line_height, value.buffer_line_height);
906
907            // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
908            merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
909            this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
910        }
911
912        Ok(this)
913    }
914
915    fn json_schema(
916        generator: &mut SchemaGenerator,
917        params: &SettingsJsonSchemaParams,
918        cx: &App,
919    ) -> schemars::schema::RootSchema {
920        let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
921        let theme_names = ThemeRegistry::global(cx)
922            .list_names()
923            .into_iter()
924            .map(|theme_name| Value::String(theme_name.to_string()))
925            .collect();
926
927        let theme_name_schema = SchemaObject {
928            instance_type: Some(InstanceType::String.into()),
929            enum_values: Some(theme_names),
930            ..Default::default()
931        };
932
933        let icon_theme_names = ThemeRegistry::global(cx)
934            .list_icon_themes()
935            .into_iter()
936            .map(|icon_theme| Value::String(icon_theme.name.to_string()))
937            .collect();
938
939        let icon_theme_name_schema = SchemaObject {
940            instance_type: Some(InstanceType::String.into()),
941            enum_values: Some(icon_theme_names),
942            ..Default::default()
943        };
944
945        root_schema.definitions.extend([
946            ("ThemeName".into(), theme_name_schema.into()),
947            ("IconThemeName".into(), icon_theme_name_schema.into()),
948            ("FontFamilies".into(), params.font_family_schema()),
949            ("FontFallbacks".into(), params.font_fallback_schema()),
950        ]);
951
952        add_references_to_properties(
953            &mut root_schema,
954            &[
955                ("buffer_font_family", "#/definitions/FontFamilies"),
956                ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
957                ("ui_font_family", "#/definitions/FontFamilies"),
958                ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
959            ],
960        );
961
962        root_schema
963    }
964}
965
966fn merge<T: Copy>(target: &mut T, value: Option<T>) {
967    if let Some(value) = value {
968        *target = value;
969    }
970}