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