settings.rs

  1use crate::fallback_themes::zed_default_dark;
  2use crate::{
  3    Appearance, DEFAULT_ICON_THEME_NAME, IconTheme, IconThemeNotFoundError, SyntaxTheme, Theme,
  4    ThemeColorsRefinement, ThemeNotFoundError, ThemeRegistry, ThemeStyleContent,
  5    status_colors_refinement, syntax_overrides, theme_colors_refinement,
  6};
  7use collections::HashMap;
  8use derive_more::{Deref, DerefMut};
  9use gpui::{
 10    App, Context, Font, FontFallbacks, FontStyle, FontWeight, Global, Pixels, Subscription, Window,
 11    px,
 12};
 13use refineable::Refineable;
 14use schemars::{JsonSchema, json_schema};
 15use serde::{Deserialize, Serialize};
 16use settings::{
 17    FontFamilyName, IconThemeName, ParameterizedJsonSchema, Settings, SettingsContent, ThemeMode,
 18    ThemeName,
 19};
 20use std::sync::Arc;
 21use util::schemars::replace_subschema;
 22use util::{MergeFrom, ResultExt as _};
 23
 24const MIN_FONT_SIZE: Pixels = px(6.0);
 25const MAX_FONT_SIZE: Pixels = px(100.0);
 26const MIN_LINE_HEIGHT: f32 = 1.0;
 27
 28#[derive(
 29    Debug,
 30    Default,
 31    PartialEq,
 32    Eq,
 33    PartialOrd,
 34    Ord,
 35    Hash,
 36    Clone,
 37    Copy,
 38    Serialize,
 39    Deserialize,
 40    JsonSchema,
 41)]
 42
 43/// Specifies the density of the UI.
 44/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
 45#[serde(rename_all = "snake_case")]
 46pub enum UiDensity {
 47    /// A denser UI with tighter spacing and smaller elements.
 48    #[serde(alias = "compact")]
 49    Compact,
 50    #[default]
 51    #[serde(alias = "default")]
 52    /// The default UI density.
 53    Default,
 54    #[serde(alias = "comfortable")]
 55    /// A looser UI with more spacing and larger elements.
 56    Comfortable,
 57}
 58
 59impl UiDensity {
 60    /// The spacing ratio of a given density.
 61    /// TODO: Standardize usage throughout the app or remove
 62    pub fn spacing_ratio(self) -> f32 {
 63        match self {
 64            UiDensity::Compact => 0.75,
 65            UiDensity::Default => 1.0,
 66            UiDensity::Comfortable => 1.25,
 67        }
 68    }
 69}
 70
 71impl From<String> for UiDensity {
 72    fn from(s: String) -> Self {
 73        match s.as_str() {
 74            "compact" => Self::Compact,
 75            "default" => Self::Default,
 76            "comfortable" => Self::Comfortable,
 77            _ => Self::default(),
 78        }
 79    }
 80}
 81
 82impl From<UiDensity> for String {
 83    fn from(val: UiDensity) -> Self {
 84        match val {
 85            UiDensity::Compact => "compact".to_string(),
 86            UiDensity::Default => "default".to_string(),
 87            UiDensity::Comfortable => "comfortable".to_string(),
 88        }
 89    }
 90}
 91
 92/// Customizable settings for the UI and theme system.
 93#[derive(Clone, PartialEq)]
 94pub struct ThemeSettings {
 95    /// The UI font size. Determines the size of text in the UI,
 96    /// as well as the size of a [gpui::Rems] unit.
 97    ///
 98    /// Changing this will impact the size of all UI elements.
 99    ui_font_size: Pixels,
100    /// The font used for UI elements.
101    pub ui_font: Font,
102    /// The font size used for buffers, and the terminal.
103    ///
104    /// The terminal font size can be overridden using it's own setting.
105    buffer_font_size: Pixels,
106    /// The font used for buffers, and the terminal.
107    ///
108    /// The terminal font family can be overridden using it's own setting.
109    pub buffer_font: Font,
110    /// The agent font size. Determines the size of text in the agent panel. Falls back to the UI font size if unset.
111    agent_font_size: Option<Pixels>,
112    /// The line height for buffers, and the terminal.
113    ///
114    /// Changing this may affect the spacing of some UI elements.
115    ///
116    /// The terminal font family can be overridden using it's own setting.
117    pub buffer_line_height: BufferLineHeight,
118    /// The current theme selection.
119    pub theme_selection: Option<ThemeSelection>,
120    /// The active theme.
121    pub active_theme: Arc<Theme>,
122    /// Manual overrides for the active theme.
123    ///
124    /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
125    pub experimental_theme_overrides: Option<settings::ThemeStyleContent>,
126    /// Manual overrides per theme
127    pub theme_overrides: HashMap<String, settings::ThemeStyleContent>,
128    /// The current icon theme selection.
129    pub icon_theme_selection: Option<IconThemeSelection>,
130    /// The active icon theme.
131    pub active_icon_theme: Arc<IconTheme>,
132    /// The density of the UI.
133    /// Note: This setting is still experimental. See [this tracking issue](
134    pub ui_density: settings::UiDensity,
135    /// The amount of fading applied to unnecessary code.
136    pub unnecessary_code_fade: f32,
137}
138
139impl ThemeSettings {
140    const DEFAULT_LIGHT_THEME: &'static str = "One Light";
141    const DEFAULT_DARK_THEME: &'static str = "One Dark";
142
143    /// Returns the name of the default theme for the given [`Appearance`].
144    pub fn default_theme(appearance: Appearance) -> &'static str {
145        match appearance {
146            Appearance::Light => Self::DEFAULT_LIGHT_THEME,
147            Appearance::Dark => Self::DEFAULT_DARK_THEME,
148        }
149    }
150
151    /// Reloads the current theme.
152    ///
153    /// Reads the [`ThemeSettings`] to know which theme should be loaded,
154    /// taking into account the current [`SystemAppearance`].
155    pub fn reload_current_theme(cx: &mut App) {
156        let mut theme_settings = ThemeSettings::get_global(cx).clone();
157        let system_appearance = SystemAppearance::global(cx);
158
159        if let Some(theme_selection) = theme_settings.theme_selection.clone() {
160            let mut theme_name = theme_selection.theme(*system_appearance);
161
162            // If the selected theme doesn't exist, fall back to a default theme
163            // based on the system appearance.
164            let theme_registry = ThemeRegistry::global(cx);
165            if let Err(err @ ThemeNotFoundError(_)) = theme_registry.get(theme_name) {
166                if theme_registry.extensions_loaded() {
167                    log::error!("{err}");
168                }
169
170                theme_name = Self::default_theme(*system_appearance);
171            };
172
173            if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
174                ThemeSettings::override_global(theme_settings, cx);
175            }
176        }
177    }
178
179    /// Reloads the current icon theme.
180    ///
181    /// Reads the [`ThemeSettings`] to know which icon theme should be loaded,
182    /// taking into account the current [`SystemAppearance`].
183    pub fn reload_current_icon_theme(cx: &mut App) {
184        let mut theme_settings = ThemeSettings::get_global(cx).clone();
185        let system_appearance = SystemAppearance::global(cx);
186
187        if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.clone() {
188            let mut icon_theme_name = icon_theme_selection.icon_theme(*system_appearance);
189
190            // If the selected icon theme doesn't exist, fall back to the default theme.
191            let theme_registry = ThemeRegistry::global(cx);
192            if let Err(err @ IconThemeNotFoundError(_)) =
193                theme_registry.get_icon_theme(icon_theme_name)
194            {
195                if theme_registry.extensions_loaded() {
196                    log::error!("{err}");
197                }
198
199                icon_theme_name = DEFAULT_ICON_THEME_NAME;
200            };
201
202            if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
203                ThemeSettings::override_global(theme_settings, cx);
204            }
205        }
206    }
207}
208
209/// The appearance of the system.
210#[derive(Debug, Clone, Copy, Deref)]
211pub struct SystemAppearance(pub Appearance);
212
213impl Default for SystemAppearance {
214    fn default() -> Self {
215        Self(Appearance::Dark)
216    }
217}
218
219#[derive(Deref, DerefMut, Default)]
220struct GlobalSystemAppearance(SystemAppearance);
221
222impl Global for GlobalSystemAppearance {}
223
224impl SystemAppearance {
225    /// Initializes the [`SystemAppearance`] for the application.
226    pub fn init(cx: &mut App) {
227        *cx.default_global::<GlobalSystemAppearance>() =
228            GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
229    }
230
231    /// Returns the global [`SystemAppearance`].
232    ///
233    /// Inserts a default [`SystemAppearance`] if one does not yet exist.
234    pub(crate) fn default_global(cx: &mut App) -> Self {
235        cx.default_global::<GlobalSystemAppearance>().0
236    }
237
238    /// Returns the global [`SystemAppearance`].
239    pub fn global(cx: &App) -> Self {
240        cx.global::<GlobalSystemAppearance>().0
241    }
242
243    /// Returns a mutable reference to the global [`SystemAppearance`].
244    pub fn global_mut(cx: &mut App) -> &mut Self {
245        cx.global_mut::<GlobalSystemAppearance>()
246    }
247}
248
249#[derive(Default)]
250struct BufferFontSize(Pixels);
251
252impl Global for BufferFontSize {}
253
254#[derive(Default)]
255pub(crate) struct UiFontSize(Pixels);
256
257impl Global for UiFontSize {}
258
259/// In-memory override for the font size in the agent panel.
260#[derive(Default)]
261pub struct AgentFontSize(Pixels);
262
263impl Global for AgentFontSize {}
264
265/// Represents the selection of a theme, which can be either static or dynamic.
266#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
267#[serde(untagged)]
268pub enum ThemeSelection {
269    /// A static theme selection, represented by a single theme name.
270    Static(ThemeName),
271    /// A dynamic theme selection, which can change based the [ThemeMode].
272    Dynamic {
273        /// The mode used to determine which theme to use.
274        #[serde(default)]
275        mode: ThemeMode,
276        /// The theme to use for light mode.
277        light: ThemeName,
278        /// The theme to use for dark mode.
279        dark: ThemeName,
280    },
281}
282
283impl From<settings::ThemeSelection> for ThemeSelection {
284    fn from(selection: settings::ThemeSelection) -> Self {
285        match selection {
286            settings::ThemeSelection::Static(theme) => ThemeSelection::Static(theme),
287            settings::ThemeSelection::Dynamic { mode, light, dark } => {
288                ThemeSelection::Dynamic { mode, light, dark }
289            }
290        }
291    }
292}
293
294impl ThemeSelection {
295    /// Returns the theme name for the selected [ThemeMode].
296    pub fn theme(&self, system_appearance: Appearance) -> &str {
297        match self {
298            Self::Static(theme) => &theme.0,
299            Self::Dynamic { mode, light, dark } => match mode {
300                ThemeMode::Light => &light.0,
301                ThemeMode::Dark => &dark.0,
302                ThemeMode::System => match system_appearance {
303                    Appearance::Light => &light.0,
304                    Appearance::Dark => &dark.0,
305                },
306            },
307        }
308    }
309
310    /// Returns the [ThemeMode] for the [ThemeSelection].
311    pub fn mode(&self) -> Option<ThemeMode> {
312        match self {
313            ThemeSelection::Static(_) => None,
314            ThemeSelection::Dynamic { mode, .. } => Some(*mode),
315        }
316    }
317}
318
319/// Represents the selection of an icon theme, which can be either static or dynamic.
320#[derive(Clone, Debug, PartialEq, Eq)]
321pub enum IconThemeSelection {
322    /// A static icon theme selection, represented by a single icon theme name.
323    Static(IconThemeName),
324    /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
325    Dynamic {
326        /// The mode used to determine which theme to use.
327        mode: ThemeMode,
328        /// The icon theme to use for light mode.
329        light: IconThemeName,
330        /// The icon theme to use for dark mode.
331        dark: IconThemeName,
332    },
333}
334
335impl From<settings::IconThemeSelection> for IconThemeSelection {
336    fn from(selection: settings::IconThemeSelection) -> Self {
337        match selection {
338            settings::IconThemeSelection::Static(theme) => IconThemeSelection::Static(theme),
339            settings::IconThemeSelection::Dynamic { mode, light, dark } => {
340                IconThemeSelection::Dynamic { mode, light, dark }
341            }
342        }
343    }
344}
345
346impl IconThemeSelection {
347    /// Returns the icon theme name based on the given [`Appearance`].
348    pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
349        match self {
350            Self::Static(theme) => &theme.0,
351            Self::Dynamic { mode, light, dark } => match mode {
352                ThemeMode::Light => &light.0,
353                ThemeMode::Dark => &dark.0,
354                ThemeMode::System => match system_appearance {
355                    Appearance::Light => &light.0,
356                    Appearance::Dark => &dark.0,
357                },
358            },
359        }
360    }
361
362    /// Returns the [`ThemeMode`] for the [`IconThemeSelection`].
363    pub fn mode(&self) -> Option<ThemeMode> {
364        match self {
365            IconThemeSelection::Static(_) => None,
366            IconThemeSelection::Dynamic { mode, .. } => Some(*mode),
367        }
368    }
369}
370
371// impl ThemeSettingsContent {
372//     /// Sets the theme for the given appearance to the theme with the specified name.
373//     pub fn set_theme(&mut self, theme_name: impl Into<Arc<str>>, appearance: Appearance) {
374//         if let Some(selection) = self.theme.as_mut() {
375//             let theme_to_update = match selection {
376//                 ThemeSelection::Static(theme) => theme,
377//                 ThemeSelection::Dynamic { mode, light, dark } => match mode {
378//                     ThemeMode::Light => light,
379//                     ThemeMode::Dark => dark,
380//                     ThemeMode::System => match appearance {
381//                         Appearance::Light => light,
382//                         Appearance::Dark => dark,
383//                     },
384//                 },
385//             };
386
387//             *theme_to_update = ThemeName(theme_name.into());
388//         } else {
389//             self.theme = Some(ThemeSelection::Static(ThemeName(theme_name.into())));
390//         }
391//     }
392
393//     /// Sets the icon theme for the given appearance to the icon theme with the specified name.
394//     pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
395//         if let Some(selection) = self.icon_theme.as_mut() {
396//             let icon_theme_to_update = match selection {
397//                 IconThemeSelection::Static(theme) => theme,
398//                 IconThemeSelection::Dynamic { mode, light, dark } => match mode {
399//                     ThemeMode::Light => light,
400//                     ThemeMode::Dark => dark,
401//                     ThemeMode::System => match appearance {
402//                         Appearance::Light => light,
403//                         Appearance::Dark => dark,
404//                     },
405//                 },
406//             };
407
408//             *icon_theme_to_update = IconThemeName(icon_theme_name.into());
409//         } else {
410//             self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
411//                 icon_theme_name.into(),
412//             )));
413//         }
414//     }
415
416//     /// Sets the mode for the theme.
417//     pub fn set_mode(&mut self, mode: ThemeMode) {
418//         if let Some(selection) = self.theme.as_mut() {
419//             match selection {
420//                 ThemeSelection::Static(theme) => {
421//                     // If the theme was previously set to a single static theme,
422//                     // we don't know whether it was a light or dark theme, so we
423//                     // just use it for both.
424//                     self.theme = Some(ThemeSelection::Dynamic {
425//                         mode,
426//                         light: theme.clone(),
427//                         dark: theme.clone(),
428//                     });
429//                 }
430//                 ThemeSelection::Dynamic {
431//                     mode: mode_to_update,
432//                     ..
433//                 } => *mode_to_update = mode,
434//             }
435//         } else {
436//             self.theme = Some(ThemeSelection::Dynamic {
437//                 mode,
438//                 light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
439//                 dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
440//             });
441//         }
442
443//         if let Some(selection) = self.icon_theme.as_mut() {
444//             match selection {
445//                 IconThemeSelection::Static(icon_theme) => {
446//                     // If the icon theme was previously set to a single static
447//                     // theme, we don't know whether it was a light or dark
448//                     // theme, so we just use it for both.
449//                     self.icon_theme = Some(IconThemeSelection::Dynamic {
450//                         mode,
451//                         light: icon_theme.clone(),
452//                         dark: icon_theme.clone(),
453//                     });
454//                 }
455//                 IconThemeSelection::Dynamic {
456//                     mode: mode_to_update,
457//                     ..
458//                 } => *mode_to_update = mode,
459//             }
460//         } else {
461//             self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
462//                 DEFAULT_ICON_THEME_NAME.into(),
463//             )));
464//         }
465//     }
466// }
467
468/// The buffer's line height.
469#[derive(Clone, Copy, Debug, PartialEq, Default)]
470pub enum BufferLineHeight {
471    /// A less dense line height.
472    #[default]
473    Comfortable,
474    /// The default line height.
475    Standard,
476    /// A custom line height, where 1.0 is the font's height. Must be at least 1.0.
477    Custom(f32),
478}
479
480impl From<settings::BufferLineHeight> for BufferLineHeight {
481    fn from(value: settings::BufferLineHeight) -> Self {
482        match value {
483            settings::BufferLineHeight::Comfortable => BufferLineHeight::Comfortable,
484            settings::BufferLineHeight::Standard => BufferLineHeight::Standard,
485            settings::BufferLineHeight::Custom(line_height) => {
486                BufferLineHeight::Custom(line_height)
487            }
488        }
489    }
490}
491
492impl BufferLineHeight {
493    /// Returns the value of the line height.
494    pub fn value(&self) -> f32 {
495        match self {
496            BufferLineHeight::Comfortable => 1.618,
497            BufferLineHeight::Standard => 1.3,
498            BufferLineHeight::Custom(line_height) => *line_height,
499        }
500    }
501}
502
503impl ThemeSettings {
504    /// Returns the buffer font size.
505    pub fn buffer_font_size(&self, cx: &App) -> Pixels {
506        let font_size = cx
507            .try_global::<BufferFontSize>()
508            .map(|size| size.0)
509            .unwrap_or(self.buffer_font_size);
510        clamp_font_size(font_size)
511    }
512
513    /// Returns the UI font size.
514    pub fn ui_font_size(&self, cx: &App) -> Pixels {
515        let font_size = cx
516            .try_global::<UiFontSize>()
517            .map(|size| size.0)
518            .unwrap_or(self.ui_font_size);
519        clamp_font_size(font_size)
520    }
521
522    /// Returns the agent panel font size. Falls back to the UI font size if unset.
523    pub fn agent_font_size(&self, cx: &App) -> Pixels {
524        cx.try_global::<AgentFontSize>()
525            .map(|size| size.0)
526            .or(self.agent_font_size)
527            .map(clamp_font_size)
528            .unwrap_or_else(|| self.ui_font_size(cx))
529    }
530
531    /// Returns the buffer font size, read from the settings.
532    ///
533    /// The real buffer font size is stored in-memory, to support temporary font size changes.
534    /// Use [`Self::buffer_font_size`] to get the real font size.
535    pub fn buffer_font_size_settings(&self) -> Pixels {
536        self.buffer_font_size
537    }
538
539    /// Returns the UI font size, read from the settings.
540    ///
541    /// The real UI font size is stored in-memory, to support temporary font size changes.
542    /// Use [`Self::ui_font_size`] to get the real font size.
543    pub fn ui_font_size_settings(&self) -> Pixels {
544        self.ui_font_size
545    }
546
547    /// Returns the agent font size, read from the settings.
548    ///
549    /// The real agent font size is stored in-memory, to support temporary font size changes.
550    /// Use [`Self::agent_font_size`] to get the real font size.
551    pub fn agent_font_size_settings(&self) -> Option<Pixels> {
552        self.agent_font_size
553    }
554
555    // TODO: Rename: `line_height` -> `buffer_line_height`
556    /// Returns the buffer's line height.
557    pub fn line_height(&self) -> f32 {
558        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
559    }
560
561    /// Switches to the theme with the given name, if it exists.
562    ///
563    /// Returns a `Some` containing the new theme if it was successful.
564    /// Returns `None` otherwise.
565    pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
566        let themes = ThemeRegistry::default_global(cx);
567
568        let mut new_theme = None;
569
570        match themes.get(theme) {
571            Ok(theme) => {
572                self.active_theme = theme.clone();
573                new_theme = Some(theme);
574            }
575            Err(err @ ThemeNotFoundError(_)) => {
576                log::error!("{err}");
577            }
578        }
579
580        self.apply_theme_overrides();
581
582        new_theme
583    }
584
585    /// Applies the theme overrides, if there are any, to the current theme.
586    pub fn apply_theme_overrides(&mut self) {
587        // Apply the old overrides setting first, so that the new setting can override those.
588        if let Some(experimental_theme_overrides) = &self.experimental_theme_overrides {
589            let mut theme = (*self.active_theme).clone();
590            ThemeSettings::modify_theme(&mut theme, experimental_theme_overrides);
591            self.active_theme = Arc::new(theme);
592        }
593
594        if let Some(theme_overrides) = self.theme_overrides.get(self.active_theme.name.as_ref()) {
595            let mut theme = (*self.active_theme).clone();
596            ThemeSettings::modify_theme(&mut theme, theme_overrides);
597            self.active_theme = Arc::new(theme);
598        }
599    }
600
601    fn modify_theme(base_theme: &mut Theme, theme_overrides: &settings::ThemeStyleContent) {
602        if let Some(window_background_appearance) = theme_overrides.window_background_appearance {
603            base_theme.styles.window_background_appearance = window_background_appearance.into();
604        }
605        let status_color_refinement = status_colors_refinement(&theme_overrides.status);
606
607        base_theme.styles.colors.refine(&theme_colors_refinement(
608            &theme_overrides.colors,
609            &status_color_refinement,
610        ));
611        base_theme.styles.status.refine(&status_color_refinement);
612        base_theme.styles.player.merge(&theme_overrides.players);
613        base_theme.styles.accents.merge(&theme_overrides.accents);
614        base_theme.styles.syntax = SyntaxTheme::merge(
615            base_theme.styles.syntax.clone(),
616            syntax_overrides(&theme_overrides),
617        );
618    }
619
620    /// Switches to the icon theme with the given name, if it exists.
621    ///
622    /// Returns a `Some` containing the new icon theme if it was successful.
623    /// Returns `None` otherwise.
624    pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
625        let themes = ThemeRegistry::default_global(cx);
626
627        let mut new_icon_theme = None;
628
629        if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
630            self.active_icon_theme = icon_theme.clone();
631            new_icon_theme = Some(icon_theme);
632            cx.refresh_windows();
633        }
634
635        new_icon_theme
636    }
637}
638
639/// Observe changes to the adjusted buffer font size.
640pub fn observe_buffer_font_size_adjustment<V: 'static>(
641    cx: &mut Context<V>,
642    f: impl 'static + Fn(&mut V, &mut Context<V>),
643) -> Subscription {
644    cx.observe_global::<BufferFontSize>(f)
645}
646
647/// Gets the font size, adjusted by the difference between the current buffer font size and the one set in the settings.
648pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
649    let adjusted_font_size =
650        if let Some(BufferFontSize(adjusted_size)) = cx.try_global::<BufferFontSize>() {
651            let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
652            let delta = *adjusted_size - buffer_font_size;
653            size + delta
654        } else {
655            size
656        };
657    clamp_font_size(adjusted_font_size)
658}
659
660/// Adjusts the buffer font size.
661pub fn adjust_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
662    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
663    let adjusted_size = cx
664        .try_global::<BufferFontSize>()
665        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
666    cx.set_global(BufferFontSize(clamp_font_size(f(adjusted_size))));
667    cx.refresh_windows();
668}
669
670/// Resets the buffer font size to the default value.
671pub fn reset_buffer_font_size(cx: &mut App) {
672    if cx.has_global::<BufferFontSize>() {
673        cx.remove_global::<BufferFontSize>();
674        cx.refresh_windows();
675    }
676}
677
678// TODO: Make private, change usages to use `get_ui_font_size` instead.
679#[allow(missing_docs)]
680pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
681    let (ui_font, ui_font_size) = {
682        let theme_settings = ThemeSettings::get_global(cx);
683        let font = theme_settings.ui_font.clone();
684        (font, theme_settings.ui_font_size(cx))
685    };
686
687    window.set_rem_size(ui_font_size);
688    ui_font
689}
690
691/// Sets the adjusted UI font size.
692pub fn adjust_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
693    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
694    let adjusted_size = cx
695        .try_global::<UiFontSize>()
696        .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
697    cx.set_global(UiFontSize(clamp_font_size(f(adjusted_size))));
698    cx.refresh_windows();
699}
700
701/// Resets the UI font size to the default value.
702pub fn reset_ui_font_size(cx: &mut App) {
703    if cx.has_global::<UiFontSize>() {
704        cx.remove_global::<UiFontSize>();
705        cx.refresh_windows();
706    }
707}
708
709/// Sets the adjusted agent panel font size.
710pub fn adjust_agent_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
711    let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx);
712    let adjusted_size = cx
713        .try_global::<AgentFontSize>()
714        .map_or(agent_font_size, |adjusted_size| adjusted_size.0);
715    cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
716    cx.refresh_windows();
717}
718
719/// Resets the agent panel font size to the default value.
720pub fn reset_agent_font_size(cx: &mut App) {
721    if cx.has_global::<AgentFontSize>() {
722        cx.remove_global::<AgentFontSize>();
723        cx.refresh_windows();
724    }
725}
726
727/// Ensures font size is within the valid range.
728pub fn clamp_font_size(size: Pixels) -> Pixels {
729    size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE)
730}
731
732fn clamp_font_weight(weight: f32) -> FontWeight {
733    FontWeight(weight.clamp(100., 950.))
734}
735
736fn font_fallbacks_from_settings(
737    fallbacks: Option<Vec<settings::FontFamilyName>>,
738) -> Option<FontFallbacks> {
739    fallbacks.map(|fallbacks| {
740        FontFallbacks::from_fonts(
741            fallbacks
742                .into_iter()
743                .map(|font_family| font_family.0.to_string())
744                .collect(),
745        )
746    })
747}
748
749impl settings::Settings for ThemeSettings {
750    fn from_file(content: &settings::SettingsContent, cx: &mut App) -> Option<Self> {
751        let content = &content.theme;
752        let themes = ThemeRegistry::default_global(cx);
753        let system_appearance = SystemAppearance::default_global(cx);
754        let theme_selection: ThemeSelection = content.theme.clone()?.into();
755        let icon_theme_selection: IconThemeSelection = content.icon_theme.clone()?.into();
756        let this = Self {
757            ui_font_size: content.ui_font_size?.into(),
758            ui_font: Font {
759                family: content.ui_font_family.as_ref()?.0.clone().into(),
760                features: content.ui_font_features.clone()?,
761                fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()),
762                weight: content.ui_font_weight.map(FontWeight)?,
763                style: Default::default(),
764            },
765            buffer_font: Font {
766                family: content.buffer_font_family.as_ref()?.0.clone().into(),
767                features: content.buffer_font_features.clone()?,
768                fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()),
769                weight: content.buffer_font_weight.map(FontWeight)?,
770                style: FontStyle::default(),
771            },
772            buffer_font_size: content.buffer_font_size?.into(),
773            buffer_line_height: content.buffer_line_height?.into(),
774            agent_font_size: content.agent_font_size.flatten().map(Into::into),
775            active_theme: themes
776                .get(theme_selection.theme(*system_appearance))
777                .or(themes.get(&zed_default_dark().name))
778                .unwrap(),
779            theme_selection: Some(theme_selection),
780            experimental_theme_overrides: None,
781            theme_overrides: HashMap::default(),
782            active_icon_theme: themes
783                .get_icon_theme(icon_theme_selection.icon_theme(*system_appearance))
784                .ok()?,
785            icon_theme_selection: Some(icon_theme_selection),
786            ui_density: content.ui_density?,
787            unnecessary_code_fade: content.unnecessary_code_fade?,
788        };
789
790        Some(this)
791    }
792
793    fn refine(&mut self, content: &SettingsContent, cx: &mut App) {
794        let value = &content.theme;
795
796        let themes = ThemeRegistry::default_global(cx);
797        let system_appearance = SystemAppearance::default_global(cx);
798
799        self.ui_density.merge_from(&value.ui_density);
800
801        if let Some(value) = value.buffer_font_family.clone() {
802            self.buffer_font.family = value.0.into();
803        }
804        if let Some(value) = value.buffer_font_features.clone() {
805            self.buffer_font.features = value;
806        }
807        if let Some(value) = value.buffer_font_fallbacks.clone() {
808            self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value));
809        }
810        if let Some(value) = value.buffer_font_weight {
811            self.buffer_font.weight = clamp_font_weight(value);
812        }
813
814        if let Some(value) = value.ui_font_family.clone() {
815            self.ui_font.family = value.0.into();
816        }
817        if let Some(value) = value.ui_font_features.clone() {
818            self.ui_font.features = value;
819        }
820        if let Some(value) = value.ui_font_fallbacks.clone() {
821            self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value));
822        }
823        if let Some(value) = value.ui_font_weight {
824            self.ui_font.weight = clamp_font_weight(value);
825        }
826
827        if let Some(value) = &value.theme {
828            self.theme_selection = Some(value.clone().into());
829
830            let theme_name = self
831                .theme_selection
832                .as_ref()
833                .unwrap()
834                .theme(*system_appearance);
835
836            match themes.get(theme_name) {
837                Ok(theme) => {
838                    self.active_theme = theme;
839                }
840                Err(err @ ThemeNotFoundError(_)) => {
841                    if themes.extensions_loaded() {
842                        log::error!("{err}");
843                    }
844                }
845            }
846        }
847
848        self.experimental_theme_overrides
849            .clone_from(&value.experimental_theme_overrides);
850        self.theme_overrides.clone_from(&value.theme_overrides);
851
852        self.apply_theme_overrides();
853
854        if let Some(value) = &value.icon_theme {
855            self.icon_theme_selection = Some(value.clone().into());
856
857            let icon_theme_name = self
858                .icon_theme_selection
859                .as_ref()
860                .unwrap()
861                .icon_theme(*system_appearance);
862
863            match themes.get_icon_theme(icon_theme_name) {
864                Ok(icon_theme) => {
865                    self.active_icon_theme = icon_theme;
866                }
867                Err(err @ IconThemeNotFoundError(_)) => {
868                    if themes.extensions_loaded() {
869                        log::error!("{err}");
870                    }
871                }
872            }
873        }
874
875        self.ui_font_size
876            .merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size));
877        self.buffer_font_size
878            .merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size));
879        self.agent_font_size.merge_from(
880            &value
881                .agent_font_size
882                .map(|value| value.map(Into::into).map(clamp_font_size)),
883        );
884
885        self.buffer_line_height
886            .merge_from(&value.buffer_line_height.map(Into::into));
887
888        // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
889        self.unnecessary_code_fade
890            .merge_from(&value.unnecessary_code_fade);
891        self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9);
892    }
893
894    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
895        vscode.f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight);
896        vscode.f32_setting("editor.fontSize", &mut current.theme.buffer_font_size);
897        if let Some(font) = vscode.read_string("editor.font") {
898            current.theme.buffer_font_family = Some(FontFamilyName(font.into()));
899        }
900        // TODO: possibly map editor.fontLigatures to buffer_font_features?
901    }
902}