settings.rs

  1#![allow(missing_docs)]
  2
  3use crate::schema::{status_colors_refinement, syntax_overrides, theme_colors_refinement};
  4use crate::{merge_accent_colors, merge_player_colors};
  5use collections::HashMap;
  6use gpui::{
  7    App, Context, Font, FontFallbacks, FontStyle, Global, Pixels, Subscription, Window, px,
  8};
  9use refineable::Refineable;
 10use schemars::JsonSchema;
 11use serde::{Deserialize, Serialize};
 12pub use settings::{FontFamilyName, IconThemeName, ThemeAppearanceMode, ThemeName};
 13use settings::{IntoGpui, RegisterSetting, Settings, SettingsContent};
 14use std::sync::Arc;
 15use theme::{Appearance, DEFAULT_ICON_THEME_NAME, SyntaxTheme, Theme};
 16
 17const MIN_FONT_SIZE: Pixels = px(6.0);
 18const MAX_FONT_SIZE: Pixels = px(100.0);
 19const MIN_LINE_HEIGHT: f32 = 1.0;
 20
 21#[derive(
 22    Debug,
 23    Default,
 24    PartialEq,
 25    Eq,
 26    PartialOrd,
 27    Ord,
 28    Hash,
 29    Clone,
 30    Copy,
 31    Serialize,
 32    Deserialize,
 33    JsonSchema,
 34)]
 35
 36/// Specifies the density of the UI.
 37/// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
 38#[serde(rename_all = "snake_case")]
 39pub enum UiDensity {
 40    /// A denser UI with tighter spacing and smaller elements.
 41    #[serde(alias = "compact")]
 42    Compact,
 43    #[default]
 44    #[serde(alias = "default")]
 45    /// The default UI density.
 46    Default,
 47    #[serde(alias = "comfortable")]
 48    /// A looser UI with more spacing and larger elements.
 49    Comfortable,
 50}
 51
 52impl UiDensity {
 53    /// The spacing ratio of a given density.
 54    /// TODO: Standardize usage throughout the app or remove
 55    pub fn spacing_ratio(self) -> f32 {
 56        match self {
 57            UiDensity::Compact => 0.75,
 58            UiDensity::Default => 1.0,
 59            UiDensity::Comfortable => 1.25,
 60        }
 61    }
 62}
 63
 64impl From<String> for UiDensity {
 65    fn from(s: String) -> Self {
 66        match s.as_str() {
 67            "compact" => Self::Compact,
 68            "default" => Self::Default,
 69            "comfortable" => Self::Comfortable,
 70            _ => Self::default(),
 71        }
 72    }
 73}
 74
 75impl From<UiDensity> for String {
 76    fn from(val: UiDensity) -> Self {
 77        match val {
 78            UiDensity::Compact => "compact".to_string(),
 79            UiDensity::Default => "default".to_string(),
 80            UiDensity::Comfortable => "comfortable".to_string(),
 81        }
 82    }
 83}
 84
 85impl From<settings::UiDensity> for UiDensity {
 86    fn from(val: settings::UiDensity) -> Self {
 87        match val {
 88            settings::UiDensity::Compact => Self::Compact,
 89            settings::UiDensity::Default => Self::Default,
 90            settings::UiDensity::Comfortable => Self::Comfortable,
 91        }
 92    }
 93}
 94
 95pub fn appearance_to_mode(appearance: Appearance) -> ThemeAppearanceMode {
 96    match appearance {
 97        Appearance::Light => ThemeAppearanceMode::Light,
 98        Appearance::Dark => ThemeAppearanceMode::Dark,
 99    }
100}
101
102/// Customizable settings for the UI and theme system.
103#[derive(Clone, PartialEq, RegisterSetting)]
104pub struct ThemeSettings {
105    /// The UI font size. Determines the size of text in the UI,
106    /// as well as the size of a [gpui::Rems] unit.
107    ///
108    /// Changing this will impact the size of all UI elements.
109    ui_font_size: Pixels,
110    /// The font used for UI elements.
111    pub ui_font: Font,
112    /// The font size used for buffers, and the terminal.
113    ///
114    /// The terminal font size can be overridden using it's own setting.
115    buffer_font_size: Pixels,
116    /// The font used for buffers, and the terminal.
117    ///
118    /// The terminal font family can be overridden using it's own setting.
119    pub buffer_font: Font,
120    /// The agent font size. Determines the size of text in the agent panel. Falls back to the UI font size if unset.
121    agent_ui_font_size: Option<Pixels>,
122    /// The agent buffer font size. Determines the size of user messages in the agent panel.
123    agent_buffer_font_size: Option<Pixels>,
124    /// The line height for buffers, and the terminal.
125    ///
126    /// Changing this may affect the spacing of some UI elements.
127    ///
128    /// The terminal font family can be overridden using it's own setting.
129    pub buffer_line_height: BufferLineHeight,
130    /// The current theme selection.
131    pub theme: ThemeSelection,
132    /// Manual overrides for the active theme.
133    ///
134    /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
135    pub experimental_theme_overrides: Option<settings::ThemeStyleContent>,
136    /// Manual overrides per theme
137    pub theme_overrides: HashMap<String, settings::ThemeStyleContent>,
138    /// The current icon theme selection.
139    pub icon_theme: IconThemeSelection,
140    /// The density of the UI.
141    /// Note: This setting is still experimental. See [this tracking issue](
142    pub ui_density: UiDensity,
143    /// The amount of fading applied to unnecessary code.
144    pub unnecessary_code_fade: f32,
145}
146
147/// Returns the name of the default theme for the given [`Appearance`].
148pub fn default_theme(appearance: Appearance) -> &'static str {
149    match appearance {
150        Appearance::Light => settings::DEFAULT_LIGHT_THEME,
151        Appearance::Dark => settings::DEFAULT_DARK_THEME,
152    }
153}
154
155#[derive(Default)]
156struct BufferFontSize(Pixels);
157
158impl Global for BufferFontSize {}
159
160#[derive(Default)]
161pub(crate) struct UiFontSize(Pixels);
162
163impl Global for UiFontSize {}
164
165/// In-memory override for the font size in the agent panel.
166#[derive(Default)]
167pub struct AgentFontSize(Pixels);
168
169impl Global for AgentFontSize {}
170
171/// Represents the selection of a theme, which can be either static or dynamic.
172#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
173#[serde(untagged)]
174pub enum ThemeSelection {
175    /// A static theme selection, represented by a single theme name.
176    Static(ThemeName),
177    /// A dynamic theme selection, which can change based the [ThemeMode].
178    Dynamic {
179        /// The mode used to determine which theme to use.
180        #[serde(default)]
181        mode: ThemeAppearanceMode,
182        /// The theme to use for light mode.
183        light: ThemeName,
184        /// The theme to use for dark mode.
185        dark: ThemeName,
186    },
187}
188
189impl From<settings::ThemeSelection> for ThemeSelection {
190    fn from(selection: settings::ThemeSelection) -> Self {
191        match selection {
192            settings::ThemeSelection::Static(theme) => ThemeSelection::Static(theme),
193            settings::ThemeSelection::Dynamic { mode, light, dark } => {
194                ThemeSelection::Dynamic { mode, light, dark }
195            }
196        }
197    }
198}
199
200impl ThemeSelection {
201    /// Returns the theme name for the selected [ThemeMode].
202    pub fn name(&self, system_appearance: Appearance) -> ThemeName {
203        match self {
204            Self::Static(theme) => theme.clone(),
205            Self::Dynamic { mode, light, dark } => match mode {
206                ThemeAppearanceMode::Light => light.clone(),
207                ThemeAppearanceMode::Dark => dark.clone(),
208                ThemeAppearanceMode::System => match system_appearance {
209                    Appearance::Light => light.clone(),
210                    Appearance::Dark => dark.clone(),
211                },
212            },
213        }
214    }
215
216    /// Returns the [ThemeMode] for the [ThemeSelection].
217    pub fn mode(&self) -> Option<ThemeAppearanceMode> {
218        match self {
219            ThemeSelection::Static(_) => None,
220            ThemeSelection::Dynamic { mode, .. } => Some(*mode),
221        }
222    }
223}
224
225/// Represents the selection of an icon theme, which can be either static or dynamic.
226#[derive(Clone, Debug, PartialEq, Eq)]
227pub enum IconThemeSelection {
228    /// A static icon theme selection, represented by a single icon theme name.
229    Static(IconThemeName),
230    /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
231    Dynamic {
232        /// The mode used to determine which theme to use.
233        mode: ThemeAppearanceMode,
234        /// The icon theme to use for light mode.
235        light: IconThemeName,
236        /// The icon theme to use for dark mode.
237        dark: IconThemeName,
238    },
239}
240
241impl From<settings::IconThemeSelection> for IconThemeSelection {
242    fn from(selection: settings::IconThemeSelection) -> Self {
243        match selection {
244            settings::IconThemeSelection::Static(theme) => IconThemeSelection::Static(theme),
245            settings::IconThemeSelection::Dynamic { mode, light, dark } => {
246                IconThemeSelection::Dynamic { mode, light, dark }
247            }
248        }
249    }
250}
251
252impl IconThemeSelection {
253    /// Returns the icon theme name based on the given [`Appearance`].
254    pub fn name(&self, system_appearance: Appearance) -> IconThemeName {
255        match self {
256            Self::Static(theme) => theme.clone(),
257            Self::Dynamic { mode, light, dark } => match mode {
258                ThemeAppearanceMode::Light => light.clone(),
259                ThemeAppearanceMode::Dark => dark.clone(),
260                ThemeAppearanceMode::System => match system_appearance {
261                    Appearance::Light => light.clone(),
262                    Appearance::Dark => dark.clone(),
263                },
264            },
265        }
266    }
267
268    /// Returns the [`ThemeMode`] for the [`IconThemeSelection`].
269    pub fn mode(&self) -> Option<ThemeAppearanceMode> {
270        match self {
271            IconThemeSelection::Static(_) => None,
272            IconThemeSelection::Dynamic { mode, .. } => Some(*mode),
273        }
274    }
275}
276
277/// Sets the theme for the given appearance to the theme with the specified name.
278///
279/// The caller should make sure that the [`Appearance`] matches the theme associated with the name.
280///
281/// If the current [`ThemeAppearanceMode`] is set to [`System`] and the user's system [`Appearance`]
282/// is different than the new theme's [`Appearance`], this function will update the
283/// [`ThemeAppearanceMode`] to the new theme's appearance in order to display the new theme.
284///
285/// [`System`]: ThemeAppearanceMode::System
286pub fn set_theme(
287    current: &mut SettingsContent,
288    theme_name: impl Into<Arc<str>>,
289    theme_appearance: Appearance,
290    system_appearance: Appearance,
291) {
292    let theme_name = ThemeName(theme_name.into());
293
294    let Some(selection) = current.theme.theme.as_mut() else {
295        current.theme.theme = Some(settings::ThemeSelection::Static(theme_name));
296        return;
297    };
298
299    match selection {
300        settings::ThemeSelection::Static(theme) => {
301            *theme = theme_name;
302        }
303        settings::ThemeSelection::Dynamic { mode, light, dark } => {
304            match theme_appearance {
305                Appearance::Light => *light = theme_name,
306                Appearance::Dark => *dark = theme_name,
307            }
308
309            let should_update_mode =
310                !(mode == &ThemeAppearanceMode::System && theme_appearance == system_appearance);
311
312            if should_update_mode {
313                *mode = appearance_to_mode(theme_appearance);
314            }
315        }
316    }
317}
318
319/// Sets the icon theme for the given appearance to the icon theme with the specified name.
320pub fn set_icon_theme(
321    current: &mut SettingsContent,
322    icon_theme_name: IconThemeName,
323    appearance: Appearance,
324) {
325    if let Some(selection) = current.theme.icon_theme.as_mut() {
326        let icon_theme_to_update = match selection {
327            settings::IconThemeSelection::Static(theme) => theme,
328            settings::IconThemeSelection::Dynamic { mode, light, dark } => match mode {
329                ThemeAppearanceMode::Light => light,
330                ThemeAppearanceMode::Dark => dark,
331                ThemeAppearanceMode::System => match appearance {
332                    Appearance::Light => light,
333                    Appearance::Dark => dark,
334                },
335            },
336        };
337
338        *icon_theme_to_update = icon_theme_name;
339    } else {
340        current.theme.icon_theme = Some(settings::IconThemeSelection::Static(icon_theme_name));
341    }
342}
343
344/// Sets the mode for the theme.
345pub fn set_mode(content: &mut SettingsContent, mode: ThemeAppearanceMode) {
346    let theme = content.theme.as_mut();
347
348    if let Some(selection) = theme.theme.as_mut() {
349        match selection {
350            settings::ThemeSelection::Static(_) => {
351                *selection = settings::ThemeSelection::Dynamic {
352                    mode: ThemeAppearanceMode::System,
353                    light: ThemeName(settings::DEFAULT_LIGHT_THEME.into()),
354                    dark: ThemeName(settings::DEFAULT_DARK_THEME.into()),
355                };
356            }
357            settings::ThemeSelection::Dynamic {
358                mode: mode_to_update,
359                ..
360            } => *mode_to_update = mode,
361        }
362    } else {
363        theme.theme = Some(settings::ThemeSelection::Dynamic {
364            mode,
365            light: ThemeName(settings::DEFAULT_LIGHT_THEME.into()),
366            dark: ThemeName(settings::DEFAULT_DARK_THEME.into()),
367        });
368    }
369
370    if let Some(selection) = theme.icon_theme.as_mut() {
371        match selection {
372            settings::IconThemeSelection::Static(icon_theme) => {
373                *selection = settings::IconThemeSelection::Dynamic {
374                    mode,
375                    light: icon_theme.clone(),
376                    dark: icon_theme.clone(),
377                };
378            }
379            settings::IconThemeSelection::Dynamic {
380                mode: mode_to_update,
381                ..
382            } => *mode_to_update = mode,
383        }
384    } else {
385        theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName(
386            DEFAULT_ICON_THEME_NAME.into(),
387        )));
388    }
389}
390
391/// The buffer's line height.
392#[derive(Clone, Copy, Debug, PartialEq, Default)]
393pub enum BufferLineHeight {
394    /// A less dense line height.
395    #[default]
396    Comfortable,
397    /// The default line height.
398    Standard,
399    /// A custom line height, where 1.0 is the font's height. Must be at least 1.0.
400    Custom(f32),
401}
402
403impl From<settings::BufferLineHeight> for BufferLineHeight {
404    fn from(value: settings::BufferLineHeight) -> Self {
405        match value {
406            settings::BufferLineHeight::Comfortable => BufferLineHeight::Comfortable,
407            settings::BufferLineHeight::Standard => BufferLineHeight::Standard,
408            settings::BufferLineHeight::Custom(line_height) => {
409                BufferLineHeight::Custom(line_height)
410            }
411        }
412    }
413}
414
415impl BufferLineHeight {
416    /// Returns the value of the line height.
417    pub fn value(&self) -> f32 {
418        match self {
419            BufferLineHeight::Comfortable => 1.618,
420            BufferLineHeight::Standard => 1.3,
421            BufferLineHeight::Custom(line_height) => *line_height,
422        }
423    }
424}
425
426impl ThemeSettings {
427    /// Returns the buffer font size.
428    pub fn buffer_font_size(&self, cx: &App) -> Pixels {
429        let font_size = cx
430            .try_global::<BufferFontSize>()
431            .map(|size| size.0)
432            .unwrap_or(self.buffer_font_size);
433        clamp_font_size(font_size)
434    }
435
436    /// Returns the UI font size.
437    pub fn ui_font_size(&self, cx: &App) -> Pixels {
438        let font_size = cx
439            .try_global::<UiFontSize>()
440            .map(|size| size.0)
441            .unwrap_or(self.ui_font_size);
442        clamp_font_size(font_size)
443    }
444
445    /// Returns the agent panel font size. Falls back to the UI font size if unset.
446    pub fn agent_ui_font_size(&self, cx: &App) -> Pixels {
447        cx.try_global::<AgentFontSize>()
448            .map(|size| size.0)
449            .or(self.agent_ui_font_size)
450            .map(clamp_font_size)
451            .unwrap_or_else(|| self.ui_font_size(cx))
452    }
453
454    /// Returns the agent panel buffer font size.
455    pub fn agent_buffer_font_size(&self, cx: &App) -> Pixels {
456        cx.try_global::<AgentFontSize>()
457            .map(|size| size.0)
458            .or(self.agent_buffer_font_size)
459            .map(clamp_font_size)
460            .unwrap_or_else(|| self.buffer_font_size(cx))
461    }
462
463    /// Returns the buffer font size, read from the settings.
464    ///
465    /// The real buffer font size is stored in-memory, to support temporary font size changes.
466    /// Use [`Self::buffer_font_size`] to get the real font size.
467    pub fn buffer_font_size_settings(&self) -> Pixels {
468        self.buffer_font_size
469    }
470
471    /// Returns the UI font size, read from the settings.
472    ///
473    /// The real UI font size is stored in-memory, to support temporary font size changes.
474    /// Use [`Self::ui_font_size`] to get the real font size.
475    pub fn ui_font_size_settings(&self) -> Pixels {
476        self.ui_font_size
477    }
478
479    /// Returns the agent font size, read from the settings.
480    ///
481    /// The real agent font size is stored in-memory, to support temporary font size changes.
482    /// Use [`Self::agent_ui_font_size`] to get the real font size.
483    pub fn agent_ui_font_size_settings(&self) -> Option<Pixels> {
484        self.agent_ui_font_size
485    }
486
487    /// Returns the agent buffer font size, read from the settings.
488    ///
489    /// The real agent buffer font size is stored in-memory, to support temporary font size changes.
490    /// Use [`Self::agent_buffer_font_size`] to get the real font size.
491    pub fn agent_buffer_font_size_settings(&self) -> Option<Pixels> {
492        self.agent_buffer_font_size
493    }
494
495    /// Returns the buffer's line height.
496    pub fn line_height(&self) -> f32 {
497        f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
498    }
499
500    /// Applies the theme overrides, if there are any, to the current theme.
501    pub fn apply_theme_overrides(&self, mut arc_theme: Arc<Theme>) -> Arc<Theme> {
502        if let Some(experimental_theme_overrides) = &self.experimental_theme_overrides {
503            let mut theme = (*arc_theme).clone();
504            ThemeSettings::modify_theme(&mut theme, experimental_theme_overrides);
505            arc_theme = Arc::new(theme);
506        }
507
508        if let Some(theme_overrides) = self.theme_overrides.get(arc_theme.name.as_ref()) {
509            let mut theme = (*arc_theme).clone();
510            ThemeSettings::modify_theme(&mut theme, theme_overrides);
511            arc_theme = Arc::new(theme);
512        }
513
514        arc_theme
515    }
516
517    fn modify_theme(base_theme: &mut Theme, theme_overrides: &settings::ThemeStyleContent) {
518        if let Some(window_background_appearance) = theme_overrides.window_background_appearance {
519            base_theme.styles.window_background_appearance =
520                window_background_appearance.into_gpui();
521        }
522        let status_color_refinement = status_colors_refinement(&theme_overrides.status);
523
524        base_theme.styles.colors.refine(&theme_colors_refinement(
525            &theme_overrides.colors,
526            &status_color_refinement,
527        ));
528        base_theme.styles.status.refine(&status_color_refinement);
529        merge_player_colors(&mut base_theme.styles.player, &theme_overrides.players);
530        merge_accent_colors(&mut base_theme.styles.accents, &theme_overrides.accents);
531        base_theme.styles.syntax = SyntaxTheme::merge(
532            base_theme.styles.syntax.clone(),
533            syntax_overrides(theme_overrides),
534        );
535    }
536}
537
538/// Observe changes to the adjusted buffer font size.
539pub fn observe_buffer_font_size_adjustment<V: 'static>(
540    cx: &mut Context<V>,
541    f: impl 'static + Fn(&mut V, &mut Context<V>),
542) -> Subscription {
543    cx.observe_global::<BufferFontSize>(f)
544}
545
546/// Gets the font size, adjusted by the difference between the current buffer font size and the one set in the settings.
547pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
548    let adjusted_font_size =
549        if let Some(BufferFontSize(adjusted_size)) = cx.try_global::<BufferFontSize>() {
550            let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
551            let delta = *adjusted_size - buffer_font_size;
552            size + delta
553        } else {
554            size
555        };
556    clamp_font_size(adjusted_font_size)
557}
558
559/// Adjusts the buffer font size.
560pub fn adjust_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
561    let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
562    let adjusted_size = cx
563        .try_global::<BufferFontSize>()
564        .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
565    cx.set_global(BufferFontSize(clamp_font_size(f(adjusted_size))));
566    cx.refresh_windows();
567}
568
569/// Resets the buffer font size to the default value.
570pub fn reset_buffer_font_size(cx: &mut App) {
571    if cx.has_global::<BufferFontSize>() {
572        cx.remove_global::<BufferFontSize>();
573        cx.refresh_windows();
574    }
575}
576
577#[allow(missing_docs)]
578pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
579    let (ui_font, ui_font_size) = {
580        let theme_settings = ThemeSettings::get_global(cx);
581        let font = theme_settings.ui_font.clone();
582        (font, theme_settings.ui_font_size(cx))
583    };
584
585    window.set_rem_size(ui_font_size);
586    ui_font
587}
588
589/// Sets the adjusted UI font size.
590pub fn adjust_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
591    let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
592    let adjusted_size = cx
593        .try_global::<UiFontSize>()
594        .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
595    cx.set_global(UiFontSize(clamp_font_size(f(adjusted_size))));
596    cx.refresh_windows();
597}
598
599/// Resets the UI font size to the default value.
600pub fn reset_ui_font_size(cx: &mut App) {
601    if cx.has_global::<UiFontSize>() {
602        cx.remove_global::<UiFontSize>();
603        cx.refresh_windows();
604    }
605}
606
607/// Sets the adjusted font size of agent responses in the agent panel.
608pub fn adjust_agent_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
609    let agent_ui_font_size = ThemeSettings::get_global(cx).agent_ui_font_size(cx);
610    let adjusted_size = cx
611        .try_global::<AgentFontSize>()
612        .map_or(agent_ui_font_size, |adjusted_size| adjusted_size.0);
613    cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
614    cx.refresh_windows();
615}
616
617/// Resets the agent response font size in the agent panel to the default value.
618pub fn reset_agent_ui_font_size(cx: &mut App) {
619    if cx.has_global::<AgentFontSize>() {
620        cx.remove_global::<AgentFontSize>();
621        cx.refresh_windows();
622    }
623}
624
625/// Sets the adjusted font size of user messages in the agent panel.
626pub fn adjust_agent_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
627    let agent_buffer_font_size = ThemeSettings::get_global(cx).agent_buffer_font_size(cx);
628    let adjusted_size = cx
629        .try_global::<AgentFontSize>()
630        .map_or(agent_buffer_font_size, |adjusted_size| adjusted_size.0);
631    cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
632    cx.refresh_windows();
633}
634
635/// Resets the user message font size in the agent panel to the default value.
636pub fn reset_agent_buffer_font_size(cx: &mut App) {
637    if cx.has_global::<AgentFontSize>() {
638        cx.remove_global::<AgentFontSize>();
639        cx.refresh_windows();
640    }
641}
642
643/// Ensures font size is within the valid range.
644pub fn clamp_font_size(size: Pixels) -> Pixels {
645    size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE)
646}
647
648fn font_fallbacks_from_settings(
649    fallbacks: Option<Vec<settings::FontFamilyName>>,
650) -> Option<FontFallbacks> {
651    fallbacks.map(|fallbacks| {
652        FontFallbacks::from_fonts(
653            fallbacks
654                .into_iter()
655                .map(|font_family| font_family.0.to_string())
656                .collect(),
657        )
658    })
659}
660
661impl settings::Settings for ThemeSettings {
662    fn from_settings(content: &settings::SettingsContent) -> Self {
663        let content = &content.theme;
664        let theme_selection: ThemeSelection = content.theme.clone().unwrap().into();
665        let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into();
666        Self {
667            ui_font_size: clamp_font_size(content.ui_font_size.unwrap().into_gpui()),
668            ui_font: Font {
669                family: content.ui_font_family.as_ref().unwrap().0.clone().into(),
670                features: content.ui_font_features.clone().unwrap().into_gpui(),
671                fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()),
672                weight: content.ui_font_weight.unwrap().into_gpui(),
673                style: Default::default(),
674            },
675            buffer_font: Font {
676                family: content
677                    .buffer_font_family
678                    .as_ref()
679                    .unwrap()
680                    .0
681                    .clone()
682                    .into(),
683                features: content.buffer_font_features.clone().unwrap().into_gpui(),
684                fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()),
685                weight: content.buffer_font_weight.unwrap().into_gpui(),
686                style: FontStyle::default(),
687            },
688            buffer_font_size: clamp_font_size(content.buffer_font_size.unwrap().into_gpui()),
689            buffer_line_height: content.buffer_line_height.unwrap().into(),
690            agent_ui_font_size: content.agent_ui_font_size.map(|s| s.into_gpui()),
691            agent_buffer_font_size: content.agent_buffer_font_size.map(|s| s.into_gpui()),
692            theme: theme_selection,
693            experimental_theme_overrides: content.experimental_theme_overrides.clone(),
694            theme_overrides: content.theme_overrides.clone(),
695            icon_theme: icon_theme_selection,
696            ui_density: content.ui_density.unwrap_or_default().into(),
697            unnecessary_code_fade: content.unnecessary_code_fade.unwrap().0.clamp(0.0, 0.9),
698        }
699    }
700}