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