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