settings.rs

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