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