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