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