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