settings.rs

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