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