settings.rs

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