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, 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)]
 370pub struct ThemeSettingsContent {
 371    /// The default font size for text in the UI.
 372    #[serde(default)]
 373    pub ui_font_size: Option<f32>,
 374    /// The name of a font to use for rendering in the UI.
 375    #[serde(default)]
 376    pub ui_font_family: Option<FontFamilyName>,
 377    /// The font fallbacks to use for rendering in the UI.
 378    #[serde(default)]
 379    #[schemars(default = "default_font_fallbacks")]
 380    #[schemars(extend("uniqueItems" = true))]
 381    pub ui_font_fallbacks: Option<Vec<FontFamilyName>>,
 382    /// The OpenType features to enable for text in the UI.
 383    #[serde(default)]
 384    #[schemars(default = "default_font_features")]
 385    pub ui_font_features: Option<FontFeatures>,
 386    /// The weight of the UI font in CSS units from 100 to 900.
 387    #[serde(default)]
 388    pub ui_font_weight: Option<f32>,
 389    /// The name of a font to use for rendering in text buffers.
 390    #[serde(default)]
 391    pub buffer_font_family: Option<FontFamilyName>,
 392    /// The font fallbacks to use for rendering in text buffers.
 393    #[serde(default)]
 394    #[schemars(extend("uniqueItems" = true))]
 395    pub buffer_font_fallbacks: Option<Vec<FontFamilyName>>,
 396    /// The default font size for rendering in text buffers.
 397    #[serde(default)]
 398    pub buffer_font_size: Option<f32>,
 399    /// The weight of the editor font in CSS units from 100 to 900.
 400    #[serde(default)]
 401    pub buffer_font_weight: Option<f32>,
 402    /// The buffer's line height.
 403    #[serde(default)]
 404    pub buffer_line_height: Option<BufferLineHeight>,
 405    /// The OpenType features to enable for rendering in text buffers.
 406    #[serde(default)]
 407    #[schemars(default = "default_font_features")]
 408    pub buffer_font_features: Option<FontFeatures>,
 409    /// The font size for the agent panel. Falls back to the UI font size if unset.
 410    #[serde(default)]
 411    pub agent_font_size: Option<Option<f32>>,
 412    /// The name of the Zed theme to use.
 413    #[serde(default)]
 414    pub theme: Option<ThemeSelection>,
 415    /// The name of the icon theme to use.
 416    #[serde(default)]
 417    pub icon_theme: Option<IconThemeSelection>,
 418
 419    /// UNSTABLE: Expect many elements to be broken.
 420    ///
 421    // Controls the density of the UI.
 422    #[serde(rename = "unstable.ui_density", default)]
 423    pub ui_density: Option<UiDensity>,
 424
 425    /// How much to fade out unused code.
 426    #[serde(default)]
 427    pub unnecessary_code_fade: Option<f32>,
 428
 429    /// EXPERIMENTAL: Overrides for the current theme.
 430    ///
 431    /// These values will override the ones on the current theme specified in `theme`.
 432    #[serde(rename = "experimental.theme_overrides", default)]
 433    pub experimental_theme_overrides: Option<ThemeStyleContent>,
 434
 435    /// Overrides per theme
 436    ///
 437    /// These values will override the ones on the specified theme
 438    #[serde(default)]
 439    pub theme_overrides: HashMap<String, ThemeStyleContent>,
 440}
 441
 442fn default_font_features() -> Option<FontFeatures> {
 443    Some(FontFeatures::default())
 444}
 445
 446fn default_font_fallbacks() -> Option<FontFallbacks> {
 447    Some(FontFallbacks::default())
 448}
 449
 450impl ThemeSettingsContent {
 451    /// Sets the theme for the given appearance to the theme with the specified name.
 452    pub fn set_theme(&mut self, theme_name: impl Into<Arc<str>>, appearance: Appearance) {
 453        if let Some(selection) = self.theme.as_mut() {
 454            let theme_to_update = match selection {
 455                ThemeSelection::Static(theme) => theme,
 456                ThemeSelection::Dynamic { mode, light, dark } => match mode {
 457                    ThemeMode::Light => light,
 458                    ThemeMode::Dark => dark,
 459                    ThemeMode::System => match appearance {
 460                        Appearance::Light => light,
 461                        Appearance::Dark => dark,
 462                    },
 463                },
 464            };
 465
 466            *theme_to_update = ThemeName(theme_name.into());
 467        } else {
 468            self.theme = Some(ThemeSelection::Static(ThemeName(theme_name.into())));
 469        }
 470    }
 471
 472    /// Sets the icon theme for the given appearance to the icon theme with the specified name.
 473    pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
 474        if let Some(selection) = self.icon_theme.as_mut() {
 475            let icon_theme_to_update = match selection {
 476                IconThemeSelection::Static(theme) => theme,
 477                IconThemeSelection::Dynamic { mode, light, dark } => match mode {
 478                    ThemeMode::Light => light,
 479                    ThemeMode::Dark => dark,
 480                    ThemeMode::System => match appearance {
 481                        Appearance::Light => light,
 482                        Appearance::Dark => dark,
 483                    },
 484                },
 485            };
 486
 487            *icon_theme_to_update = IconThemeName(icon_theme_name.into());
 488        } else {
 489            self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
 490                icon_theme_name.into(),
 491            )));
 492        }
 493    }
 494
 495    /// Sets the mode for the theme.
 496    pub fn set_mode(&mut self, mode: ThemeMode) {
 497        if let Some(selection) = self.theme.as_mut() {
 498            match selection {
 499                ThemeSelection::Static(theme) => {
 500                    // If the theme was previously set to a single static theme,
 501                    // we don't know whether it was a light or dark theme, so we
 502                    // just use it for both.
 503                    self.theme = Some(ThemeSelection::Dynamic {
 504                        mode,
 505                        light: theme.clone(),
 506                        dark: theme.clone(),
 507                    });
 508                }
 509                ThemeSelection::Dynamic {
 510                    mode: mode_to_update,
 511                    ..
 512                } => *mode_to_update = mode,
 513            }
 514        } else {
 515            self.theme = Some(ThemeSelection::Dynamic {
 516                mode,
 517                light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
 518                dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
 519            });
 520        }
 521
 522        if let Some(selection) = self.icon_theme.as_mut() {
 523            match selection {
 524                IconThemeSelection::Static(icon_theme) => {
 525                    // If the icon theme was previously set to a single static
 526                    // theme, we don't know whether it was a light or dark
 527                    // theme, so we just use it for both.
 528                    self.icon_theme = Some(IconThemeSelection::Dynamic {
 529                        mode,
 530                        light: icon_theme.clone(),
 531                        dark: icon_theme.clone(),
 532                    });
 533                }
 534                IconThemeSelection::Dynamic {
 535                    mode: mode_to_update,
 536                    ..
 537                } => *mode_to_update = mode,
 538            }
 539        } else {
 540            self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
 541                DEFAULT_ICON_THEME_NAME.into(),
 542            )));
 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 agent panel font size. Falls back to the UI font size if unset.
 605    pub fn agent_font_size(&self, cx: &App) -> Pixels {
 606        cx.try_global::<AgentFontSize>()
 607            .map(|size| size.0)
 608            .or(self.agent_font_size)
 609            .map(clamp_font_size)
 610            .unwrap_or_else(|| self.ui_font_size(cx))
 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    /// Returns the agent font size, read from the settings.
 630    ///
 631    /// The real agent font size is stored in-memory, to support temporary font size changes.
 632    /// Use [`Self::agent_font_size`] to get the real font size.
 633    pub fn agent_font_size_settings(&self) -> Option<Pixels> {
 634        self.agent_font_size
 635    }
 636
 637    // TODO: Rename: `line_height` -> `buffer_line_height`
 638    /// Returns the buffer's line height.
 639    pub fn line_height(&self) -> f32 {
 640        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
 641    }
 642
 643    /// Switches to the theme with the given name, if it exists.
 644    ///
 645    /// Returns a `Some` containing the new theme if it was successful.
 646    /// Returns `None` otherwise.
 647    pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
 648        let themes = ThemeRegistry::default_global(cx);
 649
 650        let mut new_theme = None;
 651
 652        match themes.get(theme) {
 653            Ok(theme) => {
 654                self.active_theme = theme.clone();
 655                new_theme = Some(theme);
 656            }
 657            Err(err @ ThemeNotFoundError(_)) => {
 658                log::error!("{err}");
 659            }
 660        }
 661
 662        self.apply_theme_overrides();
 663
 664        new_theme
 665    }
 666
 667    /// Applies the theme overrides, if there are any, to the current theme.
 668    pub fn apply_theme_overrides(&mut self) {
 669        // Apply the old overrides setting first, so that the new setting can override those.
 670        if let Some(experimental_theme_overrides) = &self.experimental_theme_overrides {
 671            let mut theme = (*self.active_theme).clone();
 672            ThemeSettings::modify_theme(&mut theme, experimental_theme_overrides);
 673            self.active_theme = Arc::new(theme);
 674        }
 675
 676        if let Some(theme_overrides) = self.theme_overrides.get(self.active_theme.name.as_ref()) {
 677            let mut theme = (*self.active_theme).clone();
 678            ThemeSettings::modify_theme(&mut theme, theme_overrides);
 679            self.active_theme = Arc::new(theme);
 680        }
 681    }
 682
 683    fn modify_theme(base_theme: &mut Theme, theme_overrides: &ThemeStyleContent) {
 684        if let Some(window_background_appearance) = theme_overrides.window_background_appearance {
 685            base_theme.styles.window_background_appearance = window_background_appearance.into();
 686        }
 687
 688        base_theme
 689            .styles
 690            .colors
 691            .refine(&theme_overrides.theme_colors_refinement());
 692        base_theme
 693            .styles
 694            .status
 695            .refine(&theme_overrides.status_colors_refinement());
 696        base_theme.styles.player.merge(&theme_overrides.players);
 697        base_theme.styles.accents.merge(&theme_overrides.accents);
 698        base_theme.styles.syntax = SyntaxTheme::merge(
 699            base_theme.styles.syntax.clone(),
 700            theme_overrides.syntax_overrides(),
 701        );
 702    }
 703
 704    /// Switches to the icon theme with the given name, if it exists.
 705    ///
 706    /// Returns a `Some` containing the new icon theme if it was successful.
 707    /// Returns `None` otherwise.
 708    pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
 709        let themes = ThemeRegistry::default_global(cx);
 710
 711        let mut new_icon_theme = None;
 712
 713        if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
 714            self.active_icon_theme = icon_theme.clone();
 715            new_icon_theme = Some(icon_theme);
 716            cx.refresh_windows();
 717        }
 718
 719        new_icon_theme
 720    }
 721}
 722
 723/// Observe changes to the adjusted buffer font size.
 724pub fn observe_buffer_font_size_adjustment<V: 'static>(
 725    cx: &mut Context<V>,
 726    f: impl 'static + Fn(&mut V, &mut Context<V>),
 727) -> Subscription {
 728    cx.observe_global::<BufferFontSize>(f)
 729}
 730
 731/// Gets the font size, adjusted by the difference between the current buffer font size and the one set in the settings.
 732pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
 733    let adjusted_font_size =
 734        if let Some(BufferFontSize(adjusted_size)) = cx.try_global::<BufferFontSize>() {
 735            let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
 736            let delta = *adjusted_size - buffer_font_size;
 737            size + delta
 738        } else {
 739            size
 740        };
 741    clamp_font_size(adjusted_font_size)
 742}
 743
 744/// Adjusts the buffer font size.
 745pub fn adjust_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
 746    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
 747    let adjusted_size = cx
 748        .try_global::<BufferFontSize>()
 749        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
 750    cx.set_global(BufferFontSize(clamp_font_size(f(adjusted_size))));
 751    cx.refresh_windows();
 752}
 753
 754/// Resets the buffer font size to the default value.
 755pub fn reset_buffer_font_size(cx: &mut App) {
 756    if cx.has_global::<BufferFontSize>() {
 757        cx.remove_global::<BufferFontSize>();
 758        cx.refresh_windows();
 759    }
 760}
 761
 762// TODO: Make private, change usages to use `get_ui_font_size` instead.
 763#[allow(missing_docs)]
 764pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
 765    let (ui_font, ui_font_size) = {
 766        let theme_settings = ThemeSettings::get_global(cx);
 767        let font = theme_settings.ui_font.clone();
 768        (font, theme_settings.ui_font_size(cx))
 769    };
 770
 771    window.set_rem_size(ui_font_size);
 772    ui_font
 773}
 774
 775/// Sets the adjusted UI font size.
 776pub fn adjust_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
 777    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
 778    let adjusted_size = cx
 779        .try_global::<UiFontSize>()
 780        .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
 781    cx.set_global(UiFontSize(clamp_font_size(f(adjusted_size))));
 782    cx.refresh_windows();
 783}
 784
 785/// Resets the UI font size to the default value.
 786pub fn reset_ui_font_size(cx: &mut App) {
 787    if cx.has_global::<UiFontSize>() {
 788        cx.remove_global::<UiFontSize>();
 789        cx.refresh_windows();
 790    }
 791}
 792
 793/// Sets the adjusted agent panel font size.
 794pub fn adjust_agent_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
 795    let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx);
 796    let adjusted_size = cx
 797        .try_global::<AgentFontSize>()
 798        .map_or(agent_font_size, |adjusted_size| adjusted_size.0);
 799    cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
 800    cx.refresh_windows();
 801}
 802
 803/// Resets the agent panel font size to the default value.
 804pub fn reset_agent_font_size(cx: &mut App) {
 805    if cx.has_global::<AgentFontSize>() {
 806        cx.remove_global::<AgentFontSize>();
 807        cx.refresh_windows();
 808    }
 809}
 810
 811/// Ensures font size is within the valid range.
 812pub fn clamp_font_size(size: Pixels) -> Pixels {
 813    size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE)
 814}
 815
 816fn clamp_font_weight(weight: f32) -> FontWeight {
 817    FontWeight(weight.clamp(100., 950.))
 818}
 819
 820impl settings::Settings for ThemeSettings {
 821    const KEY: Option<&'static str> = None;
 822
 823    type FileContent = ThemeSettingsContent;
 824
 825    fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
 826        let themes = ThemeRegistry::default_global(cx);
 827        let system_appearance = SystemAppearance::default_global(cx);
 828
 829        fn font_fallbacks_from_settings(
 830            fallbacks: Option<Vec<FontFamilyName>>,
 831        ) -> Option<FontFallbacks> {
 832            fallbacks.map(|fallbacks| {
 833                FontFallbacks::from_fonts(
 834                    fallbacks
 835                        .into_iter()
 836                        .map(|font_family| font_family.0.to_string())
 837                        .collect(),
 838                )
 839            })
 840        }
 841
 842        let defaults = sources.default;
 843        let mut this = Self {
 844            ui_font_size: defaults.ui_font_size.unwrap().into(),
 845            ui_font: Font {
 846                family: defaults.ui_font_family.as_ref().unwrap().0.clone().into(),
 847                features: defaults.ui_font_features.clone().unwrap(),
 848                fallbacks: font_fallbacks_from_settings(defaults.ui_font_fallbacks.clone()),
 849                weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
 850                style: Default::default(),
 851            },
 852            buffer_font: Font {
 853                family: defaults
 854                    .buffer_font_family
 855                    .as_ref()
 856                    .unwrap()
 857                    .0
 858                    .clone()
 859                    .into(),
 860                features: defaults.buffer_font_features.clone().unwrap(),
 861                fallbacks: font_fallbacks_from_settings(defaults.buffer_font_fallbacks.clone()),
 862                weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
 863                style: FontStyle::default(),
 864            },
 865            buffer_font_size: defaults.buffer_font_size.unwrap().into(),
 866            buffer_line_height: defaults.buffer_line_height.unwrap(),
 867            agent_font_size: defaults.agent_font_size.flatten().map(Into::into),
 868            theme_selection: defaults.theme.clone(),
 869            active_theme: themes
 870                .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
 871                .or(themes.get(&zed_default_dark().name))
 872                .unwrap(),
 873            experimental_theme_overrides: None,
 874            theme_overrides: HashMap::default(),
 875            icon_theme_selection: defaults.icon_theme.clone(),
 876            active_icon_theme: defaults
 877                .icon_theme
 878                .as_ref()
 879                .and_then(|selection| {
 880                    themes
 881                        .get_icon_theme(selection.icon_theme(*system_appearance))
 882                        .ok()
 883                })
 884                .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
 885            ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
 886            unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
 887        };
 888
 889        for value in sources
 890            .user
 891            .into_iter()
 892            .chain(sources.release_channel)
 893            .chain(sources.operating_system)
 894            .chain(sources.profile)
 895            .chain(sources.server)
 896        {
 897            if let Some(value) = value.ui_density {
 898                this.ui_density = value;
 899            }
 900
 901            if let Some(value) = value.buffer_font_family.clone() {
 902                this.buffer_font.family = value.0.into();
 903            }
 904            if let Some(value) = value.buffer_font_features.clone() {
 905                this.buffer_font.features = value;
 906            }
 907            if let Some(value) = value.buffer_font_fallbacks.clone() {
 908                this.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value));
 909            }
 910            if let Some(value) = value.buffer_font_weight {
 911                this.buffer_font.weight = clamp_font_weight(value);
 912            }
 913
 914            if let Some(value) = value.ui_font_family.clone() {
 915                this.ui_font.family = value.0.into();
 916            }
 917            if let Some(value) = value.ui_font_features.clone() {
 918                this.ui_font.features = value;
 919            }
 920            if let Some(value) = value.ui_font_fallbacks.clone() {
 921                this.ui_font.fallbacks = font_fallbacks_from_settings(Some(value));
 922            }
 923            if let Some(value) = value.ui_font_weight {
 924                this.ui_font.weight = clamp_font_weight(value);
 925            }
 926
 927            if let Some(value) = &value.theme {
 928                this.theme_selection = Some(value.clone());
 929
 930                let theme_name = value.theme(*system_appearance);
 931
 932                match themes.get(theme_name) {
 933                    Ok(theme) => {
 934                        this.active_theme = theme;
 935                    }
 936                    Err(err @ ThemeNotFoundError(_)) => {
 937                        if themes.extensions_loaded() {
 938                            log::error!("{err}");
 939                        }
 940                    }
 941                }
 942            }
 943
 944            this.experimental_theme_overrides
 945                .clone_from(&value.experimental_theme_overrides);
 946            this.theme_overrides.clone_from(&value.theme_overrides);
 947            this.apply_theme_overrides();
 948
 949            if let Some(value) = &value.icon_theme {
 950                this.icon_theme_selection = Some(value.clone());
 951
 952                let icon_theme_name = value.icon_theme(*system_appearance);
 953
 954                match themes.get_icon_theme(icon_theme_name) {
 955                    Ok(icon_theme) => {
 956                        this.active_icon_theme = icon_theme;
 957                    }
 958                    Err(err @ IconThemeNotFoundError(_)) => {
 959                        if themes.extensions_loaded() {
 960                            log::error!("{err}");
 961                        }
 962                    }
 963                }
 964            }
 965
 966            merge(
 967                &mut this.ui_font_size,
 968                value.ui_font_size.map(Into::into).map(clamp_font_size),
 969            );
 970            merge(
 971                &mut this.buffer_font_size,
 972                value.buffer_font_size.map(Into::into).map(clamp_font_size),
 973            );
 974            merge(
 975                &mut this.agent_font_size,
 976                value
 977                    .agent_font_size
 978                    .map(|value| value.map(Into::into).map(clamp_font_size)),
 979            );
 980
 981            merge(&mut this.buffer_line_height, value.buffer_line_height);
 982
 983            // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
 984            merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
 985            this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
 986        }
 987
 988        Ok(this)
 989    }
 990
 991    fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
 992        vscode.f32_setting("editor.fontWeight", &mut current.buffer_font_weight);
 993        vscode.f32_setting("editor.fontSize", &mut current.buffer_font_size);
 994        if let Some(font) = vscode.read_string("editor.font") {
 995            current.buffer_font_family = Some(FontFamilyName(font.into()));
 996        }
 997        // TODO: possibly map editor.fontLigatures to buffer_font_features?
 998    }
 999}
1000
1001/// Newtype for a theme name. Its `ParameterizedJsonSchema` lists the theme names known at runtime.
1002#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1003#[serde(transparent)]
1004pub struct ThemeName(pub Arc<str>);
1005
1006inventory::submit! {
1007    ParameterizedJsonSchema {
1008        add_and_get_ref: |generator, _params, cx| {
1009            replace_subschema::<ThemeName>(generator, || json_schema!({
1010                "type": "string",
1011                "enum": ThemeRegistry::global(cx).list_names(),
1012            }))
1013        }
1014    }
1015}
1016
1017/// Newtype for a icon theme name. Its `ParameterizedJsonSchema` lists the icon theme names known at
1018/// runtime.
1019#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1020#[serde(transparent)]
1021pub struct IconThemeName(pub Arc<str>);
1022
1023inventory::submit! {
1024    ParameterizedJsonSchema {
1025        add_and_get_ref: |generator, _params, cx| {
1026            replace_subschema::<IconThemeName>(generator, || json_schema!({
1027                "type": "string",
1028                "enum": ThemeRegistry::global(cx)
1029                    .list_icon_themes()
1030                    .into_iter()
1031                    .map(|icon_theme| icon_theme.name)
1032                    .collect::<Vec<SharedString>>(),
1033            }))
1034        }
1035    }
1036}
1037
1038/// Newtype for font family name. Its `ParameterizedJsonSchema` lists the font families known at
1039/// runtime.
1040#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
1041#[serde(transparent)]
1042pub struct FontFamilyName(pub Arc<str>);
1043
1044inventory::submit! {
1045    ParameterizedJsonSchema {
1046        add_and_get_ref: |generator, params, _cx| {
1047            replace_subschema::<FontFamilyName>(generator, || {
1048                json_schema!({
1049                    "type": "string",
1050                    "enum": params.font_names,
1051                })
1052            })
1053        }
1054    }
1055}
1056
1057fn merge<T: Copy>(target: &mut T, value: Option<T>) {
1058    if let Some(value) = value {
1059        *target = value;
1060    }
1061}
1062
1063#[cfg(test)]
1064mod tests {
1065    use super::*;
1066    use serde_json::json;
1067
1068    #[test]
1069    fn test_buffer_line_height_deserialize_valid() {
1070        assert_eq!(
1071            serde_json::from_value::<BufferLineHeight>(json!("comfortable")).unwrap(),
1072            BufferLineHeight::Comfortable
1073        );
1074        assert_eq!(
1075            serde_json::from_value::<BufferLineHeight>(json!("standard")).unwrap(),
1076            BufferLineHeight::Standard
1077        );
1078        assert_eq!(
1079            serde_json::from_value::<BufferLineHeight>(json!({"custom": 1.0})).unwrap(),
1080            BufferLineHeight::Custom(1.0)
1081        );
1082        assert_eq!(
1083            serde_json::from_value::<BufferLineHeight>(json!({"custom": 1.5})).unwrap(),
1084            BufferLineHeight::Custom(1.5)
1085        );
1086    }
1087
1088    #[test]
1089    fn test_buffer_line_height_deserialize_invalid() {
1090        assert!(
1091            serde_json::from_value::<BufferLineHeight>(json!({"custom": 0.99}))
1092                .err()
1093                .unwrap()
1094                .to_string()
1095                .contains("buffer_line_height.custom must be at least 1.0")
1096        );
1097        assert!(
1098            serde_json::from_value::<BufferLineHeight>(json!({"custom": 0.0}))
1099                .err()
1100                .unwrap()
1101                .to_string()
1102                .contains("buffer_line_height.custom must be at least 1.0")
1103        );
1104        assert!(
1105            serde_json::from_value::<BufferLineHeight>(json!({"custom": -1.0}))
1106                .err()
1107                .unwrap()
1108                .to_string()
1109                .contains("buffer_line_height.custom must be at least 1.0")
1110        );
1111    }
1112}