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