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