settings.rs

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