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