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.
420pub fn set_theme(
421    current: &mut SettingsContent,
422    theme_name: impl Into<Arc<str>>,
423    appearance: Appearance,
424) {
425    if let Some(selection) = current.theme.theme.as_mut() {
426        let theme_to_update = match selection {
427            settings::ThemeSelection::Static(theme) => theme,
428            settings::ThemeSelection::Dynamic { mode, light, dark } => match mode {
429                ThemeMode::Light => light,
430                ThemeMode::Dark => dark,
431                ThemeMode::System => match appearance {
432                    Appearance::Light => light,
433                    Appearance::Dark => dark,
434                },
435            },
436        };
437
438        *theme_to_update = ThemeName(theme_name.into());
439    } else {
440        current.theme.theme = Some(settings::ThemeSelection::Static(ThemeName(
441            theme_name.into(),
442        )));
443    }
444}
445
446//     /// Sets the icon theme for the given appearance to the icon theme with the specified name.
447//     pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
448//         if let Some(selection) = self.icon_theme.as_mut() {
449//             let icon_theme_to_update = match selection {
450//                 IconThemeSelection::Static(theme) => theme,
451//                 IconThemeSelection::Dynamic { mode, light, dark } => match mode {
452//                     ThemeMode::Light => light,
453//                     ThemeMode::Dark => dark,
454//                     ThemeMode::System => match appearance {
455//                         Appearance::Light => light,
456//                         Appearance::Dark => dark,
457//                     },
458//                 },
459//             };
460
461//             *icon_theme_to_update = IconThemeName(icon_theme_name.into());
462//         } else {
463//             self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
464//                 icon_theme_name.into(),
465//             )));
466//         }
467//     }
468
469/// Sets the mode for the theme.
470pub fn set_mode(content: &mut SettingsContent, mode: ThemeMode) {
471    let theme = content.theme.as_mut();
472
473    if let Some(selection) = theme.theme.as_mut() {
474        match selection {
475            settings::ThemeSelection::Static(theme) => {
476                // If the theme was previously set to a single static theme,
477                // we don't know whether it was a light or dark theme, so we
478                // just use it for both.
479                *selection = settings::ThemeSelection::Dynamic {
480                    mode,
481                    light: theme.clone(),
482                    dark: theme.clone(),
483                };
484            }
485            settings::ThemeSelection::Dynamic {
486                mode: mode_to_update,
487                ..
488            } => *mode_to_update = mode,
489        }
490    } else {
491        theme.theme = Some(settings::ThemeSelection::Dynamic {
492            mode,
493            light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
494            dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
495        });
496    }
497
498    if let Some(selection) = theme.icon_theme.as_mut() {
499        match selection {
500            settings::IconThemeSelection::Static(icon_theme) => {
501                // If the icon theme was previously set to a single static
502                // theme, we don't know whether it was a light or dark
503                // theme, so we just use it for both.
504                *selection = settings::IconThemeSelection::Dynamic {
505                    mode,
506                    light: icon_theme.clone(),
507                    dark: icon_theme.clone(),
508                };
509            }
510            settings::IconThemeSelection::Dynamic {
511                mode: mode_to_update,
512                ..
513            } => *mode_to_update = mode,
514        }
515    } else {
516        theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName(
517            DEFAULT_ICON_THEME_NAME.into(),
518        )));
519    }
520}
521// }
522
523/// The buffer's line height.
524#[derive(Clone, Copy, Debug, PartialEq, Default)]
525pub enum BufferLineHeight {
526    /// A less dense line height.
527    #[default]
528    Comfortable,
529    /// The default line height.
530    Standard,
531    /// A custom line height, where 1.0 is the font's height. Must be at least 1.0.
532    Custom(f32),
533}
534
535impl From<settings::BufferLineHeight> for BufferLineHeight {
536    fn from(value: settings::BufferLineHeight) -> Self {
537        match value {
538            settings::BufferLineHeight::Comfortable => BufferLineHeight::Comfortable,
539            settings::BufferLineHeight::Standard => BufferLineHeight::Standard,
540            settings::BufferLineHeight::Custom(line_height) => {
541                BufferLineHeight::Custom(line_height)
542            }
543        }
544    }
545}
546
547impl BufferLineHeight {
548    /// Returns the value of the line height.
549    pub fn value(&self) -> f32 {
550        match self {
551            BufferLineHeight::Comfortable => 1.618,
552            BufferLineHeight::Standard => 1.3,
553            BufferLineHeight::Custom(line_height) => *line_height,
554        }
555    }
556}
557
558impl ThemeSettings {
559    /// Returns the buffer font size.
560    pub fn buffer_font_size(&self, cx: &App) -> Pixels {
561        let font_size = cx
562            .try_global::<BufferFontSize>()
563            .map(|size| size.0)
564            .unwrap_or(self.buffer_font_size);
565        clamp_font_size(font_size)
566    }
567
568    /// Returns the UI font size.
569    pub fn ui_font_size(&self, cx: &App) -> Pixels {
570        let font_size = cx
571            .try_global::<UiFontSize>()
572            .map(|size| size.0)
573            .unwrap_or(self.ui_font_size);
574        clamp_font_size(font_size)
575    }
576
577    /// Returns the agent panel font size. Falls back to the UI font size if unset.
578    pub fn agent_font_size(&self, cx: &App) -> Pixels {
579        cx.try_global::<AgentFontSize>()
580            .map(|size| size.0)
581            .or(self.agent_font_size)
582            .map(clamp_font_size)
583            .unwrap_or_else(|| self.ui_font_size(cx))
584    }
585
586    /// Returns the buffer font size, read from the settings.
587    ///
588    /// The real buffer font size is stored in-memory, to support temporary font size changes.
589    /// Use [`Self::buffer_font_size`] to get the real font size.
590    pub fn buffer_font_size_settings(&self) -> Pixels {
591        self.buffer_font_size
592    }
593
594    /// Returns the UI font size, read from the settings.
595    ///
596    /// The real UI font size is stored in-memory, to support temporary font size changes.
597    /// Use [`Self::ui_font_size`] to get the real font size.
598    pub fn ui_font_size_settings(&self) -> Pixels {
599        self.ui_font_size
600    }
601
602    /// Returns the agent font size, read from the settings.
603    ///
604    /// The real agent font size is stored in-memory, to support temporary font size changes.
605    /// Use [`Self::agent_font_size`] to get the real font size.
606    pub fn agent_font_size_settings(&self) -> Option<Pixels> {
607        self.agent_font_size
608    }
609
610    // TODO: Rename: `line_height` -> `buffer_line_height`
611    /// Returns the buffer's line height.
612    pub fn line_height(&self) -> f32 {
613        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
614    }
615
616    /// Switches to the theme with the given name, if it exists.
617    ///
618    /// Returns a `Some` containing the new theme if it was successful.
619    /// Returns `None` otherwise.
620    pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
621        let themes = ThemeRegistry::default_global(cx);
622
623        let mut new_theme = None;
624
625        match themes.get(theme) {
626            Ok(theme) => {
627                self.active_theme = theme.clone();
628                new_theme = Some(theme);
629            }
630            Err(err @ ThemeNotFoundError(_)) => {
631                log::error!("{err}");
632            }
633        }
634
635        self.apply_theme_overrides();
636
637        new_theme
638    }
639
640    /// Applies the theme overrides, if there are any, to the current theme.
641    pub fn apply_theme_overrides(&mut self) {
642        // Apply the old overrides setting first, so that the new setting can override those.
643        if let Some(experimental_theme_overrides) = &self.experimental_theme_overrides {
644            let mut theme = (*self.active_theme).clone();
645            ThemeSettings::modify_theme(&mut theme, experimental_theme_overrides);
646            self.active_theme = Arc::new(theme);
647        }
648
649        if let Some(theme_overrides) = self.theme_overrides.get(self.active_theme.name.as_ref()) {
650            let mut theme = (*self.active_theme).clone();
651            ThemeSettings::modify_theme(&mut theme, theme_overrides);
652            self.active_theme = Arc::new(theme);
653        }
654    }
655
656    fn modify_theme(base_theme: &mut Theme, theme_overrides: &settings::ThemeStyleContent) {
657        if let Some(window_background_appearance) = theme_overrides.window_background_appearance {
658            base_theme.styles.window_background_appearance = window_background_appearance.into();
659        }
660        let status_color_refinement = status_colors_refinement(&theme_overrides.status);
661
662        base_theme.styles.colors.refine(&theme_colors_refinement(
663            &theme_overrides.colors,
664            &status_color_refinement,
665        ));
666        base_theme.styles.status.refine(&status_color_refinement);
667        base_theme.styles.player.merge(&theme_overrides.players);
668        base_theme.styles.accents.merge(&theme_overrides.accents);
669        base_theme.styles.syntax = SyntaxTheme::merge(
670            base_theme.styles.syntax.clone(),
671            syntax_overrides(&theme_overrides),
672        );
673    }
674
675    /// Switches to the icon theme with the given name, if it exists.
676    ///
677    /// Returns a `Some` containing the new icon theme if it was successful.
678    /// Returns `None` otherwise.
679    pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
680        let themes = ThemeRegistry::default_global(cx);
681
682        let mut new_icon_theme = None;
683
684        if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
685            self.active_icon_theme = icon_theme.clone();
686            new_icon_theme = Some(icon_theme);
687            cx.refresh_windows();
688        }
689
690        new_icon_theme
691    }
692}
693
694/// Observe changes to the adjusted buffer font size.
695pub fn observe_buffer_font_size_adjustment<V: 'static>(
696    cx: &mut Context<V>,
697    f: impl 'static + Fn(&mut V, &mut Context<V>),
698) -> Subscription {
699    cx.observe_global::<BufferFontSize>(f)
700}
701
702/// Gets the font size, adjusted by the difference between the current buffer font size and the one set in the settings.
703pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
704    let adjusted_font_size =
705        if let Some(BufferFontSize(adjusted_size)) = cx.try_global::<BufferFontSize>() {
706            let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
707            let delta = *adjusted_size - buffer_font_size;
708            size + delta
709        } else {
710            size
711        };
712    clamp_font_size(adjusted_font_size)
713}
714
715/// Adjusts the buffer font size.
716pub fn adjust_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
717    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
718    let adjusted_size = cx
719        .try_global::<BufferFontSize>()
720        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
721    cx.set_global(BufferFontSize(clamp_font_size(f(adjusted_size))));
722    cx.refresh_windows();
723}
724
725/// Resets the buffer font size to the default value.
726pub fn reset_buffer_font_size(cx: &mut App) {
727    if cx.has_global::<BufferFontSize>() {
728        cx.remove_global::<BufferFontSize>();
729        cx.refresh_windows();
730    }
731}
732
733// TODO: Make private, change usages to use `get_ui_font_size` instead.
734#[allow(missing_docs)]
735pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
736    let (ui_font, ui_font_size) = {
737        let theme_settings = ThemeSettings::get_global(cx);
738        let font = theme_settings.ui_font.clone();
739        (font, theme_settings.ui_font_size(cx))
740    };
741
742    window.set_rem_size(ui_font_size);
743    ui_font
744}
745
746/// Sets the adjusted UI font size.
747pub fn adjust_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
748    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
749    let adjusted_size = cx
750        .try_global::<UiFontSize>()
751        .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
752    cx.set_global(UiFontSize(clamp_font_size(f(adjusted_size))));
753    cx.refresh_windows();
754}
755
756/// Resets the UI font size to the default value.
757pub fn reset_ui_font_size(cx: &mut App) {
758    if cx.has_global::<UiFontSize>() {
759        cx.remove_global::<UiFontSize>();
760        cx.refresh_windows();
761    }
762}
763
764/// Sets the adjusted agent panel font size.
765pub fn adjust_agent_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
766    let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx);
767    let adjusted_size = cx
768        .try_global::<AgentFontSize>()
769        .map_or(agent_font_size, |adjusted_size| adjusted_size.0);
770    cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
771    cx.refresh_windows();
772}
773
774/// Resets the agent panel font size to the default value.
775pub fn reset_agent_font_size(cx: &mut App) {
776    if cx.has_global::<AgentFontSize>() {
777        cx.remove_global::<AgentFontSize>();
778        cx.refresh_windows();
779    }
780}
781
782/// Ensures font size is within the valid range.
783pub fn clamp_font_size(size: Pixels) -> Pixels {
784    size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE)
785}
786
787fn clamp_font_weight(weight: f32) -> FontWeight {
788    FontWeight(weight.clamp(100., 950.))
789}
790
791/// font fallback from settings
792pub fn font_fallbacks_from_settings(
793    fallbacks: Option<Vec<settings::FontFamilyName>>,
794) -> Option<FontFallbacks> {
795    fallbacks.map(|fallbacks| {
796        FontFallbacks::from_fonts(
797            fallbacks
798                .into_iter()
799                .map(|font_family| font_family.0.to_string())
800                .collect(),
801        )
802    })
803}
804
805impl settings::Settings for ThemeSettings {
806    fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
807        let content = &content.theme;
808        // todo(settings_refactor). This should *not* require cx...
809        let themes = ThemeRegistry::default_global(cx);
810        let system_appearance = SystemAppearance::default_global(cx);
811        let theme_selection: ThemeSelection = content.theme.clone().unwrap().into();
812        let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into();
813        Self {
814            ui_font_size: content.ui_font_size.unwrap().into(),
815            ui_font: Font {
816                family: content.ui_font_family.as_ref().unwrap().0.clone().into(),
817                features: content.ui_font_features.clone().unwrap(),
818                fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()),
819                weight: content.ui_font_weight.map(FontWeight).unwrap(),
820                style: Default::default(),
821            },
822            buffer_font: Font {
823                family: content
824                    .buffer_font_family
825                    .as_ref()
826                    .unwrap()
827                    .0
828                    .clone()
829                    .into(),
830                features: content.buffer_font_features.clone().unwrap(),
831                fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()),
832                weight: content.buffer_font_weight.map(FontWeight).unwrap(),
833                style: FontStyle::default(),
834            },
835            buffer_font_size: content.buffer_font_size.unwrap().into(),
836            buffer_line_height: content.buffer_line_height.unwrap().into(),
837            agent_font_size: content.agent_font_size.flatten().map(Into::into),
838            active_theme: themes
839                .get(theme_selection.theme(*system_appearance))
840                .or(themes.get(&zed_default_dark().name))
841                .unwrap(),
842            theme_selection: Some(theme_selection),
843            experimental_theme_overrides: None,
844            theme_overrides: HashMap::default(),
845            active_icon_theme: themes
846                .get_icon_theme(icon_theme_selection.icon_theme(*system_appearance))
847                .ok()
848                .unwrap(),
849            icon_theme_selection: Some(icon_theme_selection),
850            ui_density: content.ui_density.unwrap_or_default().into(),
851            unnecessary_code_fade: content.unnecessary_code_fade.unwrap(),
852        }
853    }
854
855    fn refine(&mut self, content: &SettingsContent, cx: &mut App) {
856        let value = &content.theme;
857
858        let themes = ThemeRegistry::default_global(cx);
859        let system_appearance = SystemAppearance::default_global(cx);
860
861        self.ui_density
862            .merge_from(&value.ui_density.map(Into::into));
863
864        if let Some(value) = value.buffer_font_family.clone() {
865            self.buffer_font.family = value.0.into();
866        }
867        if let Some(value) = value.buffer_font_features.clone() {
868            self.buffer_font.features = value;
869        }
870        if let Some(value) = value.buffer_font_fallbacks.clone() {
871            self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value));
872        }
873        if let Some(value) = value.buffer_font_weight {
874            self.buffer_font.weight = clamp_font_weight(value);
875        }
876
877        if let Some(value) = value.ui_font_family.clone() {
878            self.ui_font.family = value.0.into();
879        }
880        if let Some(value) = value.ui_font_features.clone() {
881            self.ui_font.features = value;
882        }
883        if let Some(value) = value.ui_font_fallbacks.clone() {
884            self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value));
885        }
886        if let Some(value) = value.ui_font_weight {
887            self.ui_font.weight = clamp_font_weight(value);
888        }
889
890        if let Some(value) = &value.theme {
891            self.theme_selection = Some(value.clone().into());
892
893            let theme_name = self
894                .theme_selection
895                .as_ref()
896                .unwrap()
897                .theme(*system_appearance);
898
899            match themes.get(theme_name) {
900                Ok(theme) => {
901                    self.active_theme = theme;
902                }
903                Err(err @ ThemeNotFoundError(_)) => {
904                    if themes.extensions_loaded() {
905                        log::error!("{err}");
906                    }
907                }
908            }
909        }
910
911        self.experimental_theme_overrides
912            .clone_from(&value.experimental_theme_overrides);
913        self.theme_overrides.clone_from(&value.theme_overrides);
914
915        self.apply_theme_overrides();
916
917        if let Some(value) = &value.icon_theme {
918            self.icon_theme_selection = Some(value.clone().into());
919
920            let icon_theme_name = self
921                .icon_theme_selection
922                .as_ref()
923                .unwrap()
924                .icon_theme(*system_appearance);
925
926            match themes.get_icon_theme(icon_theme_name) {
927                Ok(icon_theme) => {
928                    self.active_icon_theme = icon_theme;
929                }
930                Err(err @ IconThemeNotFoundError(_)) => {
931                    if themes.extensions_loaded() {
932                        log::error!("{err}");
933                    }
934                }
935            }
936        }
937
938        self.ui_font_size
939            .merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size));
940        self.buffer_font_size
941            .merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size));
942        self.agent_font_size.merge_from(
943            &value
944                .agent_font_size
945                .map(|value| value.map(Into::into).map(clamp_font_size)),
946        );
947
948        self.buffer_line_height
949            .merge_from(&value.buffer_line_height.map(Into::into));
950
951        // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
952        self.unnecessary_code_fade
953            .merge_from(&value.unnecessary_code_fade);
954        self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9);
955    }
956
957    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
958        vscode.f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight);
959        vscode.f32_setting("editor.fontSize", &mut current.theme.buffer_font_size);
960        if let Some(font) = vscode.read_string("editor.font") {
961            current.theme.buffer_font_family = Some(FontFamilyName(font.into()));
962        }
963        // TODO: possibly map editor.fontLigatures to buffer_font_features?
964    }
965}