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.
420pub fn set_theme(
421 current: &mut SettingsContent,
422 theme_name: impl Into<Arc<str>>,
423 appearance: Appearance,
424) {
425 if let Some(selection) = current.theme.theme.as_mut() {
426 let theme_to_update = match selection {
427 settings::ThemeSelection::Static(theme) => theme,
428 settings::ThemeSelection::Dynamic { mode, light, dark } => match mode {
429 ThemeMode::Light => light,
430 ThemeMode::Dark => dark,
431 ThemeMode::System => match appearance {
432 Appearance::Light => light,
433 Appearance::Dark => dark,
434 },
435 },
436 };
437
438 *theme_to_update = ThemeName(theme_name.into());
439 } else {
440 current.theme.theme = Some(settings::ThemeSelection::Static(ThemeName(
441 theme_name.into(),
442 )));
443 }
444}
445
446// /// Sets the icon theme for the given appearance to the icon theme with the specified name.
447// pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
448// if let Some(selection) = self.icon_theme.as_mut() {
449// let icon_theme_to_update = match selection {
450// IconThemeSelection::Static(theme) => theme,
451// IconThemeSelection::Dynamic { mode, light, dark } => match mode {
452// ThemeMode::Light => light,
453// ThemeMode::Dark => dark,
454// ThemeMode::System => match appearance {
455// Appearance::Light => light,
456// Appearance::Dark => dark,
457// },
458// },
459// };
460
461// *icon_theme_to_update = IconThemeName(icon_theme_name.into());
462// } else {
463// self.icon_theme = Some(IconThemeSelection::Static(IconThemeName(
464// icon_theme_name.into(),
465// )));
466// }
467// }
468
469/// Sets the mode for the theme.
470pub fn set_mode(content: &mut SettingsContent, mode: ThemeMode) {
471 let theme = content.theme.as_mut();
472
473 if let Some(selection) = theme.theme.as_mut() {
474 match selection {
475 settings::ThemeSelection::Static(theme) => {
476 // If the theme was previously set to a single static theme,
477 // we don't know whether it was a light or dark theme, so we
478 // just use it for both.
479 *selection = settings::ThemeSelection::Dynamic {
480 mode,
481 light: theme.clone(),
482 dark: theme.clone(),
483 };
484 }
485 settings::ThemeSelection::Dynamic {
486 mode: mode_to_update,
487 ..
488 } => *mode_to_update = mode,
489 }
490 } else {
491 theme.theme = Some(settings::ThemeSelection::Dynamic {
492 mode,
493 light: ThemeName(ThemeSettings::DEFAULT_LIGHT_THEME.into()),
494 dark: ThemeName(ThemeSettings::DEFAULT_DARK_THEME.into()),
495 });
496 }
497
498 if let Some(selection) = theme.icon_theme.as_mut() {
499 match selection {
500 settings::IconThemeSelection::Static(icon_theme) => {
501 // If the icon theme was previously set to a single static
502 // theme, we don't know whether it was a light or dark
503 // theme, so we just use it for both.
504 *selection = settings::IconThemeSelection::Dynamic {
505 mode,
506 light: icon_theme.clone(),
507 dark: icon_theme.clone(),
508 };
509 }
510 settings::IconThemeSelection::Dynamic {
511 mode: mode_to_update,
512 ..
513 } => *mode_to_update = mode,
514 }
515 } else {
516 theme.icon_theme = Some(settings::IconThemeSelection::Static(IconThemeName(
517 DEFAULT_ICON_THEME_NAME.into(),
518 )));
519 }
520}
521// }
522
523/// The buffer's line height.
524#[derive(Clone, Copy, Debug, PartialEq, Default)]
525pub enum BufferLineHeight {
526 /// A less dense line height.
527 #[default]
528 Comfortable,
529 /// The default line height.
530 Standard,
531 /// A custom line height, where 1.0 is the font's height. Must be at least 1.0.
532 Custom(f32),
533}
534
535impl From<settings::BufferLineHeight> for BufferLineHeight {
536 fn from(value: settings::BufferLineHeight) -> Self {
537 match value {
538 settings::BufferLineHeight::Comfortable => BufferLineHeight::Comfortable,
539 settings::BufferLineHeight::Standard => BufferLineHeight::Standard,
540 settings::BufferLineHeight::Custom(line_height) => {
541 BufferLineHeight::Custom(line_height)
542 }
543 }
544 }
545}
546
547impl BufferLineHeight {
548 /// Returns the value of the line height.
549 pub fn value(&self) -> f32 {
550 match self {
551 BufferLineHeight::Comfortable => 1.618,
552 BufferLineHeight::Standard => 1.3,
553 BufferLineHeight::Custom(line_height) => *line_height,
554 }
555 }
556}
557
558impl ThemeSettings {
559 /// Returns the buffer font size.
560 pub fn buffer_font_size(&self, cx: &App) -> Pixels {
561 let font_size = cx
562 .try_global::<BufferFontSize>()
563 .map(|size| size.0)
564 .unwrap_or(self.buffer_font_size);
565 clamp_font_size(font_size)
566 }
567
568 /// Returns the UI font size.
569 pub fn ui_font_size(&self, cx: &App) -> Pixels {
570 let font_size = cx
571 .try_global::<UiFontSize>()
572 .map(|size| size.0)
573 .unwrap_or(self.ui_font_size);
574 clamp_font_size(font_size)
575 }
576
577 /// Returns the agent panel font size. Falls back to the UI font size if unset.
578 pub fn agent_font_size(&self, cx: &App) -> Pixels {
579 cx.try_global::<AgentFontSize>()
580 .map(|size| size.0)
581 .or(self.agent_font_size)
582 .map(clamp_font_size)
583 .unwrap_or_else(|| self.ui_font_size(cx))
584 }
585
586 /// Returns the buffer font size, read from the settings.
587 ///
588 /// The real buffer font size is stored in-memory, to support temporary font size changes.
589 /// Use [`Self::buffer_font_size`] to get the real font size.
590 pub fn buffer_font_size_settings(&self) -> Pixels {
591 self.buffer_font_size
592 }
593
594 /// Returns the UI font size, read from the settings.
595 ///
596 /// The real UI font size is stored in-memory, to support temporary font size changes.
597 /// Use [`Self::ui_font_size`] to get the real font size.
598 pub fn ui_font_size_settings(&self) -> Pixels {
599 self.ui_font_size
600 }
601
602 /// Returns the agent font size, read from the settings.
603 ///
604 /// The real agent font size is stored in-memory, to support temporary font size changes.
605 /// Use [`Self::agent_font_size`] to get the real font size.
606 pub fn agent_font_size_settings(&self) -> Option<Pixels> {
607 self.agent_font_size
608 }
609
610 // TODO: Rename: `line_height` -> `buffer_line_height`
611 /// Returns the buffer's line height.
612 pub fn line_height(&self) -> f32 {
613 f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
614 }
615
616 /// Switches to the theme with the given name, if it exists.
617 ///
618 /// Returns a `Some` containing the new theme if it was successful.
619 /// Returns `None` otherwise.
620 pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
621 let themes = ThemeRegistry::default_global(cx);
622
623 let mut new_theme = None;
624
625 match themes.get(theme) {
626 Ok(theme) => {
627 self.active_theme = theme.clone();
628 new_theme = Some(theme);
629 }
630 Err(err @ ThemeNotFoundError(_)) => {
631 log::error!("{err}");
632 }
633 }
634
635 self.apply_theme_overrides();
636
637 new_theme
638 }
639
640 /// Applies the theme overrides, if there are any, to the current theme.
641 pub fn apply_theme_overrides(&mut self) {
642 // Apply the old overrides setting first, so that the new setting can override those.
643 if let Some(experimental_theme_overrides) = &self.experimental_theme_overrides {
644 let mut theme = (*self.active_theme).clone();
645 ThemeSettings::modify_theme(&mut theme, experimental_theme_overrides);
646 self.active_theme = Arc::new(theme);
647 }
648
649 if let Some(theme_overrides) = self.theme_overrides.get(self.active_theme.name.as_ref()) {
650 let mut theme = (*self.active_theme).clone();
651 ThemeSettings::modify_theme(&mut theme, theme_overrides);
652 self.active_theme = Arc::new(theme);
653 }
654 }
655
656 fn modify_theme(base_theme: &mut Theme, theme_overrides: &settings::ThemeStyleContent) {
657 if let Some(window_background_appearance) = theme_overrides.window_background_appearance {
658 base_theme.styles.window_background_appearance = window_background_appearance.into();
659 }
660 let status_color_refinement = status_colors_refinement(&theme_overrides.status);
661
662 base_theme.styles.colors.refine(&theme_colors_refinement(
663 &theme_overrides.colors,
664 &status_color_refinement,
665 ));
666 base_theme.styles.status.refine(&status_color_refinement);
667 base_theme.styles.player.merge(&theme_overrides.players);
668 base_theme.styles.accents.merge(&theme_overrides.accents);
669 base_theme.styles.syntax = SyntaxTheme::merge(
670 base_theme.styles.syntax.clone(),
671 syntax_overrides(&theme_overrides),
672 );
673 }
674
675 /// Switches to the icon theme with the given name, if it exists.
676 ///
677 /// Returns a `Some` containing the new icon theme if it was successful.
678 /// Returns `None` otherwise.
679 pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
680 let themes = ThemeRegistry::default_global(cx);
681
682 let mut new_icon_theme = None;
683
684 if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
685 self.active_icon_theme = icon_theme.clone();
686 new_icon_theme = Some(icon_theme);
687 cx.refresh_windows();
688 }
689
690 new_icon_theme
691 }
692}
693
694/// Observe changes to the adjusted buffer font size.
695pub fn observe_buffer_font_size_adjustment<V: 'static>(
696 cx: &mut Context<V>,
697 f: impl 'static + Fn(&mut V, &mut Context<V>),
698) -> Subscription {
699 cx.observe_global::<BufferFontSize>(f)
700}
701
702/// Gets the font size, adjusted by the difference between the current buffer font size and the one set in the settings.
703pub fn adjusted_font_size(size: Pixels, cx: &App) -> Pixels {
704 let adjusted_font_size =
705 if let Some(BufferFontSize(adjusted_size)) = cx.try_global::<BufferFontSize>() {
706 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
707 let delta = *adjusted_size - buffer_font_size;
708 size + delta
709 } else {
710 size
711 };
712 clamp_font_size(adjusted_font_size)
713}
714
715/// Adjusts the buffer font size.
716pub fn adjust_buffer_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
717 let buffer_font_size = ThemeSettings::get_global(cx).buffer_font_size;
718 let adjusted_size = cx
719 .try_global::<BufferFontSize>()
720 .map_or(buffer_font_size, |adjusted_size| adjusted_size.0);
721 cx.set_global(BufferFontSize(clamp_font_size(f(adjusted_size))));
722 cx.refresh_windows();
723}
724
725/// Resets the buffer font size to the default value.
726pub fn reset_buffer_font_size(cx: &mut App) {
727 if cx.has_global::<BufferFontSize>() {
728 cx.remove_global::<BufferFontSize>();
729 cx.refresh_windows();
730 }
731}
732
733// TODO: Make private, change usages to use `get_ui_font_size` instead.
734#[allow(missing_docs)]
735pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
736 let (ui_font, ui_font_size) = {
737 let theme_settings = ThemeSettings::get_global(cx);
738 let font = theme_settings.ui_font.clone();
739 (font, theme_settings.ui_font_size(cx))
740 };
741
742 window.set_rem_size(ui_font_size);
743 ui_font
744}
745
746/// Sets the adjusted UI font size.
747pub fn adjust_ui_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
748 let ui_font_size = ThemeSettings::get_global(cx).ui_font_size(cx);
749 let adjusted_size = cx
750 .try_global::<UiFontSize>()
751 .map_or(ui_font_size, |adjusted_size| adjusted_size.0);
752 cx.set_global(UiFontSize(clamp_font_size(f(adjusted_size))));
753 cx.refresh_windows();
754}
755
756/// Resets the UI font size to the default value.
757pub fn reset_ui_font_size(cx: &mut App) {
758 if cx.has_global::<UiFontSize>() {
759 cx.remove_global::<UiFontSize>();
760 cx.refresh_windows();
761 }
762}
763
764/// Sets the adjusted agent panel font size.
765pub fn adjust_agent_font_size(cx: &mut App, f: impl FnOnce(Pixels) -> Pixels) {
766 let agent_font_size = ThemeSettings::get_global(cx).agent_font_size(cx);
767 let adjusted_size = cx
768 .try_global::<AgentFontSize>()
769 .map_or(agent_font_size, |adjusted_size| adjusted_size.0);
770 cx.set_global(AgentFontSize(clamp_font_size(f(adjusted_size))));
771 cx.refresh_windows();
772}
773
774/// Resets the agent panel font size to the default value.
775pub fn reset_agent_font_size(cx: &mut App) {
776 if cx.has_global::<AgentFontSize>() {
777 cx.remove_global::<AgentFontSize>();
778 cx.refresh_windows();
779 }
780}
781
782/// Ensures font size is within the valid range.
783pub fn clamp_font_size(size: Pixels) -> Pixels {
784 size.clamp(MIN_FONT_SIZE, MAX_FONT_SIZE)
785}
786
787fn clamp_font_weight(weight: f32) -> FontWeight {
788 FontWeight(weight.clamp(100., 950.))
789}
790
791/// font fallback from settings
792pub fn font_fallbacks_from_settings(
793 fallbacks: Option<Vec<settings::FontFamilyName>>,
794) -> Option<FontFallbacks> {
795 fallbacks.map(|fallbacks| {
796 FontFallbacks::from_fonts(
797 fallbacks
798 .into_iter()
799 .map(|font_family| font_family.0.to_string())
800 .collect(),
801 )
802 })
803}
804
805impl settings::Settings for ThemeSettings {
806 fn from_defaults(content: &settings::SettingsContent, cx: &mut App) -> Self {
807 let content = &content.theme;
808 // todo(settings_refactor). This should *not* require cx...
809 let themes = ThemeRegistry::default_global(cx);
810 let system_appearance = SystemAppearance::default_global(cx);
811 let theme_selection: ThemeSelection = content.theme.clone().unwrap().into();
812 let icon_theme_selection: IconThemeSelection = content.icon_theme.clone().unwrap().into();
813 Self {
814 ui_font_size: content.ui_font_size.unwrap().into(),
815 ui_font: Font {
816 family: content.ui_font_family.as_ref().unwrap().0.clone().into(),
817 features: content.ui_font_features.clone().unwrap(),
818 fallbacks: font_fallbacks_from_settings(content.ui_font_fallbacks.clone()),
819 weight: content.ui_font_weight.map(FontWeight).unwrap(),
820 style: Default::default(),
821 },
822 buffer_font: Font {
823 family: content
824 .buffer_font_family
825 .as_ref()
826 .unwrap()
827 .0
828 .clone()
829 .into(),
830 features: content.buffer_font_features.clone().unwrap(),
831 fallbacks: font_fallbacks_from_settings(content.buffer_font_fallbacks.clone()),
832 weight: content.buffer_font_weight.map(FontWeight).unwrap(),
833 style: FontStyle::default(),
834 },
835 buffer_font_size: content.buffer_font_size.unwrap().into(),
836 buffer_line_height: content.buffer_line_height.unwrap().into(),
837 agent_font_size: content.agent_font_size.flatten().map(Into::into),
838 active_theme: themes
839 .get(theme_selection.theme(*system_appearance))
840 .or(themes.get(&zed_default_dark().name))
841 .unwrap(),
842 theme_selection: Some(theme_selection),
843 experimental_theme_overrides: None,
844 theme_overrides: HashMap::default(),
845 active_icon_theme: themes
846 .get_icon_theme(icon_theme_selection.icon_theme(*system_appearance))
847 .ok()
848 .unwrap(),
849 icon_theme_selection: Some(icon_theme_selection),
850 ui_density: content.ui_density.unwrap_or_default().into(),
851 unnecessary_code_fade: content.unnecessary_code_fade.unwrap(),
852 }
853 }
854
855 fn refine(&mut self, content: &SettingsContent, cx: &mut App) {
856 let value = &content.theme;
857
858 let themes = ThemeRegistry::default_global(cx);
859 let system_appearance = SystemAppearance::default_global(cx);
860
861 self.ui_density
862 .merge_from(&value.ui_density.map(Into::into));
863
864 if let Some(value) = value.buffer_font_family.clone() {
865 self.buffer_font.family = value.0.into();
866 }
867 if let Some(value) = value.buffer_font_features.clone() {
868 self.buffer_font.features = value;
869 }
870 if let Some(value) = value.buffer_font_fallbacks.clone() {
871 self.buffer_font.fallbacks = font_fallbacks_from_settings(Some(value));
872 }
873 if let Some(value) = value.buffer_font_weight {
874 self.buffer_font.weight = clamp_font_weight(value);
875 }
876
877 if let Some(value) = value.ui_font_family.clone() {
878 self.ui_font.family = value.0.into();
879 }
880 if let Some(value) = value.ui_font_features.clone() {
881 self.ui_font.features = value;
882 }
883 if let Some(value) = value.ui_font_fallbacks.clone() {
884 self.ui_font.fallbacks = font_fallbacks_from_settings(Some(value));
885 }
886 if let Some(value) = value.ui_font_weight {
887 self.ui_font.weight = clamp_font_weight(value);
888 }
889
890 if let Some(value) = &value.theme {
891 self.theme_selection = Some(value.clone().into());
892
893 let theme_name = self
894 .theme_selection
895 .as_ref()
896 .unwrap()
897 .theme(*system_appearance);
898
899 match themes.get(theme_name) {
900 Ok(theme) => {
901 self.active_theme = theme;
902 }
903 Err(err @ ThemeNotFoundError(_)) => {
904 if themes.extensions_loaded() {
905 log::error!("{err}");
906 }
907 }
908 }
909 }
910
911 self.experimental_theme_overrides
912 .clone_from(&value.experimental_theme_overrides);
913 self.theme_overrides.clone_from(&value.theme_overrides);
914
915 self.apply_theme_overrides();
916
917 if let Some(value) = &value.icon_theme {
918 self.icon_theme_selection = Some(value.clone().into());
919
920 let icon_theme_name = self
921 .icon_theme_selection
922 .as_ref()
923 .unwrap()
924 .icon_theme(*system_appearance);
925
926 match themes.get_icon_theme(icon_theme_name) {
927 Ok(icon_theme) => {
928 self.active_icon_theme = icon_theme;
929 }
930 Err(err @ IconThemeNotFoundError(_)) => {
931 if themes.extensions_loaded() {
932 log::error!("{err}");
933 }
934 }
935 }
936 }
937
938 self.ui_font_size
939 .merge_from(&value.ui_font_size.map(Into::into).map(clamp_font_size));
940 self.buffer_font_size
941 .merge_from(&value.buffer_font_size.map(Into::into).map(clamp_font_size));
942 self.agent_font_size.merge_from(
943 &value
944 .agent_font_size
945 .map(|value| value.map(Into::into).map(clamp_font_size)),
946 );
947
948 self.buffer_line_height
949 .merge_from(&value.buffer_line_height.map(Into::into));
950
951 // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
952 self.unnecessary_code_fade
953 .merge_from(&value.unnecessary_code_fade);
954 self.unnecessary_code_fade = self.unnecessary_code_fade.clamp(0.0, 0.9);
955 }
956
957 fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut SettingsContent) {
958 vscode.f32_setting("editor.fontWeight", &mut current.theme.buffer_font_weight);
959 vscode.f32_setting("editor.fontSize", &mut current.theme.buffer_font_size);
960 if let Some(font) = vscode.read_string("editor.font") {
961 current.theme.buffer_font_family = Some(FontFamilyName(font.into()));
962 }
963 // TODO: possibly map editor.fontLigatures to buffer_font_features?
964 }
965}