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