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