1use crate::fallback_themes::zed_default_dark;
2use crate::{
3 Appearance, IconTheme, SyntaxTheme, Theme, ThemeRegistry, ThemeStyleContent,
4 DEFAULT_ICON_THEME_NAME,
5};
6use anyhow::Result;
7use derive_more::{Deref, DerefMut};
8use gpui::{
9 px, App, Font, FontFallbacks, FontFeatures, FontStyle, FontWeight, Global, Pixels, Window,
10};
11use refineable::Refineable;
12use schemars::{
13 gen::SchemaGenerator,
14 schema::{InstanceType, Schema, SchemaObject},
15 JsonSchema,
16};
17use serde::{Deserialize, Serialize};
18use serde_json::Value;
19use settings::{add_references_to_properties, Settings, SettingsJsonSchemaParams, SettingsSources};
20use std::sync::Arc;
21use util::ResultExt as _;
22
23const MIN_FONT_SIZE: Pixels = px(6.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
90/// Customizable settings for the UI and theme system.
91#[derive(Clone, PartialEq)]
92pub struct ThemeSettings {
93 /// The UI font size. Determines the size of text in the UI,
94 /// as well as the size of a [gpui::Rems] unit.
95 ///
96 /// Changing this will impact the size of all UI elements.
97 pub ui_font_size: Pixels,
98 /// The font used for UI elements.
99 pub ui_font: Font,
100 /// The font size used for buffers, and the terminal.
101 ///
102 /// The terminal font size can be overridden using it's own setting.
103 pub buffer_font_size: Pixels,
104 /// The font used for buffers, and the terminal.
105 ///
106 /// The terminal font family can be overridden using it's own setting.
107 pub buffer_font: Font,
108 /// The line height for buffers, and the terminal.
109 ///
110 /// Changing this may affect the spacing of some UI elements.
111 ///
112 /// The terminal font family can be overridden using it's own setting.
113 pub buffer_line_height: BufferLineHeight,
114 /// The current theme selection.
115 pub theme_selection: Option<ThemeSelection>,
116 /// The active theme.
117 pub active_theme: Arc<Theme>,
118 /// Manual overrides for the active theme.
119 ///
120 /// Note: This setting is still experimental. See [this tracking issue](https://github.com/zed-industries/zed/issues/18078)
121 pub theme_overrides: Option<ThemeStyleContent>,
122 /// The current icon theme selection.
123 pub icon_theme_selection: Option<IconThemeSelection>,
124 /// The active icon theme.
125 pub active_icon_theme: Arc<IconTheme>,
126 /// The density of the UI.
127 /// Note: This setting is still experimental. See [this tracking issue](
128 pub ui_density: UiDensity,
129 /// The amount of fading applied to unnecessary code.
130 pub unnecessary_code_fade: f32,
131}
132
133impl ThemeSettings {
134 const DEFAULT_LIGHT_THEME: &'static str = "One Light";
135 const DEFAULT_DARK_THEME: &'static str = "One Dark";
136
137 /// Returns the name of the default theme for the given [`Appearance`].
138 pub fn default_theme(appearance: Appearance) -> &'static str {
139 match appearance {
140 Appearance::Light => Self::DEFAULT_LIGHT_THEME,
141 Appearance::Dark => Self::DEFAULT_DARK_THEME,
142 }
143 }
144
145 /// Reloads the current theme.
146 ///
147 /// Reads the [`ThemeSettings`] to know which theme should be loaded,
148 /// taking into account the current [`SystemAppearance`].
149 pub fn reload_current_theme(cx: &mut App) {
150 let mut theme_settings = ThemeSettings::get_global(cx).clone();
151 let system_appearance = SystemAppearance::global(cx);
152
153 if let Some(theme_selection) = theme_settings.theme_selection.clone() {
154 let mut theme_name = theme_selection.theme(*system_appearance);
155
156 // If the selected theme doesn't exist, fall back to a default theme
157 // based on the system appearance.
158 let theme_registry = ThemeRegistry::global(cx);
159 if theme_registry.get(theme_name).ok().is_none() {
160 theme_name = Self::default_theme(*system_appearance);
161 };
162
163 if let Some(_theme) = theme_settings.switch_theme(theme_name, cx) {
164 ThemeSettings::override_global(theme_settings, cx);
165 }
166 }
167 }
168
169 /// Reloads the current icon theme.
170 ///
171 /// Reads the [`ThemeSettings`] to know which icon theme should be loaded,
172 /// taking into account the current [`SystemAppearance`].
173 pub fn reload_current_icon_theme(cx: &mut App) {
174 let mut theme_settings = ThemeSettings::get_global(cx).clone();
175 let system_appearance = SystemAppearance::global(cx);
176
177 if let Some(icon_theme_selection) = theme_settings.icon_theme_selection.clone() {
178 let mut icon_theme_name = icon_theme_selection.icon_theme(*system_appearance);
179
180 // If the selected icon theme doesn't exist, fall back to the default theme.
181 let theme_registry = ThemeRegistry::global(cx);
182 if theme_registry
183 .get_icon_theme(icon_theme_name)
184 .ok()
185 .is_none()
186 {
187 icon_theme_name = DEFAULT_ICON_THEME_NAME;
188 };
189
190 if let Some(_theme) = theme_settings.switch_icon_theme(icon_theme_name, cx) {
191 ThemeSettings::override_global(theme_settings, cx);
192 }
193 }
194 }
195}
196
197/// The appearance of the system.
198#[derive(Debug, Clone, Copy, Deref)]
199pub struct SystemAppearance(pub Appearance);
200
201impl Default for SystemAppearance {
202 fn default() -> Self {
203 Self(Appearance::Dark)
204 }
205}
206
207#[derive(Deref, DerefMut, Default)]
208struct GlobalSystemAppearance(SystemAppearance);
209
210impl Global for GlobalSystemAppearance {}
211
212impl SystemAppearance {
213 /// Initializes the [`SystemAppearance`] for the application.
214 pub fn init(cx: &mut App) {
215 *cx.default_global::<GlobalSystemAppearance>() =
216 GlobalSystemAppearance(SystemAppearance(cx.window_appearance().into()));
217 }
218
219 /// Returns the global [`SystemAppearance`].
220 ///
221 /// Inserts a default [`SystemAppearance`] if one does not yet exist.
222 pub(crate) fn default_global(cx: &mut App) -> Self {
223 cx.default_global::<GlobalSystemAppearance>().0
224 }
225
226 /// Returns the global [`SystemAppearance`].
227 pub fn global(cx: &App) -> Self {
228 cx.global::<GlobalSystemAppearance>().0
229 }
230
231 /// Returns a mutable reference to the global [`SystemAppearance`].
232 pub fn global_mut(cx: &mut App) -> &mut Self {
233 cx.global_mut::<GlobalSystemAppearance>()
234 }
235}
236
237/// Represents the selection of a theme, which can be either static or dynamic.
238#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
239#[serde(untagged)]
240pub enum ThemeSelection {
241 /// A static theme selection, represented by a single theme name.
242 Static(#[schemars(schema_with = "theme_name_ref")] String),
243 /// A dynamic theme selection, which can change based the [ThemeMode].
244 Dynamic {
245 /// The mode used to determine which theme to use.
246 #[serde(default)]
247 mode: ThemeMode,
248 /// The theme to use for light mode.
249 #[schemars(schema_with = "theme_name_ref")]
250 light: String,
251 /// The theme to use for dark mode.
252 #[schemars(schema_with = "theme_name_ref")]
253 dark: String,
254 },
255}
256
257fn theme_name_ref(_: &mut SchemaGenerator) -> Schema {
258 Schema::new_ref("#/definitions/ThemeName".into())
259}
260
261// TODO: Rename ThemeMode -> ThemeAppearanceMode
262/// The mode use to select a theme.
263///
264/// `Light` and `Dark` will select their respective themes.
265///
266/// `System` will select the theme based on the system's appearance.
267#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Serialize, Deserialize, JsonSchema)]
268#[serde(rename_all = "snake_case")]
269pub enum ThemeMode {
270 /// Use the specified `light` theme.
271 Light,
272
273 /// Use the specified `dark` theme.
274 Dark,
275
276 /// Use the theme based on the system's appearance.
277 #[default]
278 System,
279}
280
281impl ThemeSelection {
282 /// Returns the theme name for the selected [ThemeMode].
283 pub fn theme(&self, system_appearance: Appearance) -> &str {
284 match self {
285 Self::Static(theme) => theme,
286 Self::Dynamic { mode, light, dark } => match mode {
287 ThemeMode::Light => light,
288 ThemeMode::Dark => dark,
289 ThemeMode::System => match system_appearance {
290 Appearance::Light => light,
291 Appearance::Dark => dark,
292 },
293 },
294 }
295 }
296
297 /// Returns the [ThemeMode] for the [ThemeSelection].
298 pub fn mode(&self) -> Option<ThemeMode> {
299 match self {
300 ThemeSelection::Static(_) => None,
301 ThemeSelection::Dynamic { mode, .. } => Some(*mode),
302 }
303 }
304}
305
306fn icon_theme_name_ref(_: &mut SchemaGenerator) -> Schema {
307 Schema::new_ref("#/definitions/IconThemeName".into())
308}
309
310/// Represents the selection of an icon theme, which can be either static or dynamic.
311#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
312#[serde(untagged)]
313pub enum IconThemeSelection {
314 /// A static icon theme selection, represented by a single icon theme name.
315 Static(#[schemars(schema_with = "icon_theme_name_ref")] String),
316 /// A dynamic icon theme selection, which can change based on the [`ThemeMode`].
317 Dynamic {
318 /// The mode used to determine which theme to use.
319 #[serde(default)]
320 mode: ThemeMode,
321 /// The icon theme to use for light mode.
322 #[schemars(schema_with = "icon_theme_name_ref")]
323 light: String,
324 /// The icon theme to use for dark mode.
325 #[schemars(schema_with = "icon_theme_name_ref")]
326 dark: String,
327 },
328}
329
330impl IconThemeSelection {
331 /// Returns the icon theme name based on the given [`Appearance`].
332 pub fn icon_theme(&self, system_appearance: Appearance) -> &str {
333 match self {
334 Self::Static(theme) => theme,
335 Self::Dynamic { mode, light, dark } => match mode {
336 ThemeMode::Light => light,
337 ThemeMode::Dark => dark,
338 ThemeMode::System => match system_appearance {
339 Appearance::Light => light,
340 Appearance::Dark => dark,
341 },
342 },
343 }
344 }
345
346 /// Returns the [`ThemeMode`] for the [`IconThemeSelection`].
347 pub fn mode(&self) -> Option<ThemeMode> {
348 match self {
349 IconThemeSelection::Static(_) => None,
350 IconThemeSelection::Dynamic { mode, .. } => Some(*mode),
351 }
352 }
353}
354
355/// Settings for rendering text in UI and text buffers.
356#[derive(Clone, Debug, Default, Serialize, Deserialize, JsonSchema)]
357pub struct ThemeSettingsContent {
358 /// The default font size for text in the UI.
359 #[serde(default)]
360 pub ui_font_size: Option<f32>,
361 /// The name of a font to use for rendering in the UI.
362 #[serde(default)]
363 pub ui_font_family: Option<String>,
364 /// The font fallbacks to use for rendering in the UI.
365 #[serde(default)]
366 #[schemars(default = "default_font_fallbacks")]
367 pub ui_font_fallbacks: Option<Vec<String>>,
368 /// The OpenType features to enable for text in the UI.
369 #[serde(default)]
370 #[schemars(default = "default_font_features")]
371 pub ui_font_features: Option<FontFeatures>,
372 /// The weight of the UI font in CSS units from 100 to 900.
373 #[serde(default)]
374 pub ui_font_weight: Option<f32>,
375 /// The name of a font to use for rendering in text buffers.
376 #[serde(default)]
377 pub buffer_font_family: Option<String>,
378 /// The font fallbacks to use for rendering in text buffers.
379 #[serde(default)]
380 #[schemars(default = "default_font_fallbacks")]
381 pub buffer_font_fallbacks: Option<Vec<String>>,
382 /// The default font size for rendering in text buffers.
383 #[serde(default)]
384 pub buffer_font_size: Option<f32>,
385 /// The weight of the editor font in CSS units from 100 to 900.
386 #[serde(default)]
387 pub buffer_font_weight: Option<f32>,
388 /// The buffer's line height.
389 #[serde(default)]
390 pub buffer_line_height: Option<BufferLineHeight>,
391 /// The OpenType features to enable for rendering in text buffers.
392 #[serde(default)]
393 #[schemars(default = "default_font_features")]
394 pub buffer_font_features: Option<FontFeatures>,
395 /// The name of the Zed theme to use.
396 #[serde(default)]
397 pub theme: Option<ThemeSelection>,
398 /// The name of the icon theme to use.
399 #[serde(default)]
400 pub icon_theme: Option<IconThemeSelection>,
401
402 /// UNSTABLE: Expect many elements to be broken.
403 ///
404 // Controls the density of the UI.
405 #[serde(rename = "unstable.ui_density", default)]
406 pub ui_density: Option<UiDensity>,
407
408 /// How much to fade out unused code.
409 #[serde(default)]
410 pub unnecessary_code_fade: Option<f32>,
411
412 /// EXPERIMENTAL: Overrides for the current theme.
413 ///
414 /// These values will override the ones on the current theme specified in `theme`.
415 #[serde(rename = "experimental.theme_overrides", default)]
416 pub theme_overrides: Option<ThemeStyleContent>,
417}
418
419fn default_font_features() -> Option<FontFeatures> {
420 Some(FontFeatures::default())
421}
422
423fn default_font_fallbacks() -> Option<FontFallbacks> {
424 Some(FontFallbacks::default())
425}
426
427impl ThemeSettingsContent {
428 /// Sets the theme for the given appearance to the theme with the specified name.
429 pub fn set_theme(&mut self, theme_name: String, appearance: Appearance) {
430 if let Some(selection) = self.theme.as_mut() {
431 let theme_to_update = match selection {
432 ThemeSelection::Static(theme) => theme,
433 ThemeSelection::Dynamic { mode, light, dark } => match mode {
434 ThemeMode::Light => light,
435 ThemeMode::Dark => dark,
436 ThemeMode::System => match appearance {
437 Appearance::Light => light,
438 Appearance::Dark => dark,
439 },
440 },
441 };
442
443 *theme_to_update = theme_name.to_string();
444 } else {
445 self.theme = Some(ThemeSelection::Static(theme_name.to_string()));
446 }
447 }
448
449 /// Sets the icon theme for the given appearance to the icon theme with the specified name.
450 pub fn set_icon_theme(&mut self, icon_theme_name: String, appearance: Appearance) {
451 if let Some(selection) = self.icon_theme.as_mut() {
452 let icon_theme_to_update = match selection {
453 IconThemeSelection::Static(theme) => theme,
454 IconThemeSelection::Dynamic { mode, light, dark } => match mode {
455 ThemeMode::Light => light,
456 ThemeMode::Dark => dark,
457 ThemeMode::System => match appearance {
458 Appearance::Light => light,
459 Appearance::Dark => dark,
460 },
461 },
462 };
463
464 *icon_theme_to_update = icon_theme_name.to_string();
465 } else {
466 self.theme = Some(ThemeSelection::Static(icon_theme_name.to_string()));
467 }
468 }
469
470 /// Sets the mode for the theme.
471 pub fn set_mode(&mut self, mode: ThemeMode) {
472 if let Some(selection) = self.theme.as_mut() {
473 match selection {
474 ThemeSelection::Static(theme) => {
475 // If the theme was previously set to a single static theme,
476 // we don't know whether it was a light or dark theme, so we
477 // just use it for both.
478 self.theme = Some(ThemeSelection::Dynamic {
479 mode,
480 light: theme.clone(),
481 dark: theme.clone(),
482 });
483 }
484 ThemeSelection::Dynamic {
485 mode: mode_to_update,
486 ..
487 } => *mode_to_update = mode,
488 }
489 } else {
490 self.theme = Some(ThemeSelection::Dynamic {
491 mode,
492 light: ThemeSettings::DEFAULT_LIGHT_THEME.into(),
493 dark: ThemeSettings::DEFAULT_DARK_THEME.into(),
494 });
495 }
496
497 if let Some(selection) = self.icon_theme.as_mut() {
498 match selection {
499 IconThemeSelection::Static(icon_theme) => {
500 // If the icon theme was previously set to a single static
501 // theme, we don't know whether it was a light or dark
502 // theme, so we just use it for both.
503 self.icon_theme = Some(IconThemeSelection::Dynamic {
504 mode,
505 light: icon_theme.clone(),
506 dark: icon_theme.clone(),
507 });
508 }
509 IconThemeSelection::Dynamic {
510 mode: mode_to_update,
511 ..
512 } => *mode_to_update = mode,
513 }
514 } else {
515 self.icon_theme = Some(IconThemeSelection::Static(DEFAULT_ICON_THEME_NAME.into()));
516 }
517 }
518}
519
520/// The buffer's line height.
521#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, JsonSchema, Default)]
522#[serde(rename_all = "snake_case")]
523pub enum BufferLineHeight {
524 /// A less dense line height.
525 #[default]
526 Comfortable,
527 /// The default line height.
528 Standard,
529 /// A custom line height.
530 ///
531 /// A line height of 1.0 is the height of the buffer's font size.
532 Custom(f32),
533}
534
535impl BufferLineHeight {
536 /// Returns the value of the line height.
537 pub fn value(&self) -> f32 {
538 match self {
539 BufferLineHeight::Comfortable => 1.618,
540 BufferLineHeight::Standard => 1.3,
541 BufferLineHeight::Custom(line_height) => *line_height,
542 }
543 }
544}
545
546impl ThemeSettings {
547 /// Returns the buffer font size.
548 pub fn buffer_font_size(&self) -> Pixels {
549 Self::clamp_font_size(self.buffer_font_size)
550 }
551
552 /// Ensures that the font size is within the valid range.
553 pub fn clamp_font_size(size: Pixels) -> Pixels {
554 size.max(MIN_FONT_SIZE)
555 }
556
557 // TODO: Rename: `line_height` -> `buffer_line_height`
558 /// Returns the buffer's line height.
559 pub fn line_height(&self) -> f32 {
560 f32::max(self.buffer_line_height.value(), MIN_LINE_HEIGHT)
561 }
562
563 /// Switches to the theme with the given name, if it exists.
564 ///
565 /// Returns a `Some` containing the new theme if it was successful.
566 /// Returns `None` otherwise.
567 pub fn switch_theme(&mut self, theme: &str, cx: &mut App) -> Option<Arc<Theme>> {
568 let themes = ThemeRegistry::default_global(cx);
569
570 let mut new_theme = None;
571
572 if let Some(theme) = themes.get(theme).log_err() {
573 self.active_theme = theme.clone();
574 new_theme = Some(theme);
575 }
576
577 self.apply_theme_overrides();
578
579 new_theme
580 }
581
582 /// Applies the theme overrides, if there are any, to the current theme.
583 pub fn apply_theme_overrides(&mut self) {
584 if let Some(theme_overrides) = &self.theme_overrides {
585 let mut base_theme = (*self.active_theme).clone();
586
587 if let Some(window_background_appearance) = theme_overrides.window_background_appearance
588 {
589 base_theme.styles.window_background_appearance =
590 window_background_appearance.into();
591 }
592
593 base_theme
594 .styles
595 .colors
596 .refine(&theme_overrides.theme_colors_refinement());
597 base_theme
598 .styles
599 .status
600 .refine(&theme_overrides.status_colors_refinement());
601 base_theme.styles.player.merge(&theme_overrides.players);
602 base_theme.styles.accents.merge(&theme_overrides.accents);
603 base_theme.styles.syntax =
604 SyntaxTheme::merge(base_theme.styles.syntax, theme_overrides.syntax_overrides());
605
606 self.active_theme = Arc::new(base_theme);
607 }
608 }
609
610 /// Switches to the icon theme with the given name, if it exists.
611 ///
612 /// Returns a `Some` containing the new icon theme if it was successful.
613 /// Returns `None` otherwise.
614 pub fn switch_icon_theme(&mut self, icon_theme: &str, cx: &mut App) -> Option<Arc<IconTheme>> {
615 let themes = ThemeRegistry::default_global(cx);
616
617 let mut new_icon_theme = None;
618
619 if let Some(icon_theme) = themes.get_icon_theme(icon_theme).log_err() {
620 self.active_icon_theme = icon_theme.clone();
621 new_icon_theme = Some(icon_theme);
622 cx.refresh_windows();
623 }
624
625 new_icon_theme
626 }
627}
628
629// TODO: Make private, change usages to use `get_ui_font_size` instead.
630#[allow(missing_docs)]
631pub fn setup_ui_font(window: &mut Window, cx: &mut App) -> gpui::Font {
632 let (ui_font, ui_font_size) = {
633 let theme_settings = ThemeSettings::get_global(cx);
634 let font = theme_settings.ui_font.clone();
635 (font, theme_settings.ui_font_size)
636 };
637
638 window.set_rem_size(ui_font_size);
639 ui_font
640}
641
642fn clamp_font_weight(weight: f32) -> FontWeight {
643 FontWeight(weight.clamp(100., 950.))
644}
645
646impl settings::Settings for ThemeSettings {
647 const KEY: Option<&'static str> = None;
648
649 type FileContent = ThemeSettingsContent;
650
651 fn load(sources: SettingsSources<Self::FileContent>, cx: &mut App) -> Result<Self> {
652 let themes = ThemeRegistry::default_global(cx);
653 let system_appearance = SystemAppearance::default_global(cx);
654
655 let defaults = sources.default;
656 let mut this = Self {
657 ui_font_size: defaults.ui_font_size.unwrap().into(),
658 ui_font: Font {
659 family: defaults.ui_font_family.as_ref().unwrap().clone().into(),
660 features: defaults.ui_font_features.clone().unwrap(),
661 fallbacks: defaults
662 .ui_font_fallbacks
663 .as_ref()
664 .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
665 weight: defaults.ui_font_weight.map(FontWeight).unwrap(),
666 style: Default::default(),
667 },
668 buffer_font: Font {
669 family: defaults.buffer_font_family.as_ref().unwrap().clone().into(),
670 features: defaults.buffer_font_features.clone().unwrap(),
671 fallbacks: defaults
672 .buffer_font_fallbacks
673 .as_ref()
674 .map(|fallbacks| FontFallbacks::from_fonts(fallbacks.clone())),
675 weight: defaults.buffer_font_weight.map(FontWeight).unwrap(),
676 style: FontStyle::default(),
677 },
678 buffer_font_size: defaults.buffer_font_size.unwrap().into(),
679 buffer_line_height: defaults.buffer_line_height.unwrap(),
680 theme_selection: defaults.theme.clone(),
681 active_theme: themes
682 .get(defaults.theme.as_ref().unwrap().theme(*system_appearance))
683 .or(themes.get(&zed_default_dark().name))
684 .unwrap(),
685 theme_overrides: None,
686 icon_theme_selection: defaults.icon_theme.clone(),
687 active_icon_theme: defaults
688 .icon_theme
689 .as_ref()
690 .and_then(|selection| {
691 themes
692 .get_icon_theme(selection.icon_theme(*system_appearance))
693 .ok()
694 })
695 .unwrap_or_else(|| themes.get_icon_theme(DEFAULT_ICON_THEME_NAME).unwrap()),
696 ui_density: defaults.ui_density.unwrap_or(UiDensity::Default),
697 unnecessary_code_fade: defaults.unnecessary_code_fade.unwrap_or(0.0),
698 };
699
700 for value in sources
701 .user
702 .into_iter()
703 .chain(sources.release_channel)
704 .chain(sources.server)
705 {
706 if let Some(value) = value.ui_density {
707 this.ui_density = value;
708 }
709
710 if let Some(value) = value.buffer_font_family.clone() {
711 this.buffer_font.family = value.into();
712 }
713 if let Some(value) = value.buffer_font_features.clone() {
714 this.buffer_font.features = value;
715 }
716 if let Some(value) = value.buffer_font_fallbacks.clone() {
717 this.buffer_font.fallbacks = Some(FontFallbacks::from_fonts(value));
718 }
719 if let Some(value) = value.buffer_font_weight {
720 this.buffer_font.weight = clamp_font_weight(value);
721 }
722
723 if let Some(value) = value.ui_font_family.clone() {
724 this.ui_font.family = value.into();
725 }
726 if let Some(value) = value.ui_font_features.clone() {
727 this.ui_font.features = value;
728 }
729 if let Some(value) = value.ui_font_fallbacks.clone() {
730 this.ui_font.fallbacks = Some(FontFallbacks::from_fonts(value));
731 }
732 if let Some(value) = value.ui_font_weight {
733 this.ui_font.weight = clamp_font_weight(value);
734 }
735
736 if let Some(value) = &value.theme {
737 this.theme_selection = Some(value.clone());
738
739 let theme_name = value.theme(*system_appearance);
740
741 if let Some(theme) = themes.get(theme_name).log_err() {
742 this.active_theme = theme;
743 }
744 }
745
746 this.theme_overrides.clone_from(&value.theme_overrides);
747 this.apply_theme_overrides();
748
749 if let Some(value) = &value.icon_theme {
750 this.icon_theme_selection = Some(value.clone());
751
752 let icon_theme_name = value.icon_theme(*system_appearance);
753
754 if let Some(icon_theme) = themes.get_icon_theme(icon_theme_name).log_err() {
755 this.active_icon_theme = icon_theme;
756 }
757 }
758
759 merge(&mut this.ui_font_size, value.ui_font_size.map(Into::into));
760 this.ui_font_size = this.ui_font_size.clamp(px(6.), px(100.));
761
762 merge(
763 &mut this.buffer_font_size,
764 value.buffer_font_size.map(Into::into),
765 );
766 this.buffer_font_size = this.buffer_font_size.clamp(px(6.), px(100.));
767
768 merge(&mut this.buffer_line_height, value.buffer_line_height);
769
770 // Clamp the `unnecessary_code_fade` to ensure text can't disappear entirely.
771 merge(&mut this.unnecessary_code_fade, value.unnecessary_code_fade);
772 this.unnecessary_code_fade = this.unnecessary_code_fade.clamp(0.0, 0.9);
773 }
774
775 Ok(this)
776 }
777
778 fn json_schema(
779 generator: &mut SchemaGenerator,
780 params: &SettingsJsonSchemaParams,
781 cx: &App,
782 ) -> schemars::schema::RootSchema {
783 let mut root_schema = generator.root_schema_for::<ThemeSettingsContent>();
784 let theme_names = ThemeRegistry::global(cx)
785 .list_names()
786 .into_iter()
787 .map(|theme_name| Value::String(theme_name.to_string()))
788 .collect();
789
790 let theme_name_schema = SchemaObject {
791 instance_type: Some(InstanceType::String.into()),
792 enum_values: Some(theme_names),
793 ..Default::default()
794 };
795
796 let icon_theme_names = ThemeRegistry::global(cx)
797 .list_icon_themes()
798 .into_iter()
799 .map(|icon_theme| Value::String(icon_theme.name.to_string()))
800 .collect();
801
802 let icon_theme_name_schema = SchemaObject {
803 instance_type: Some(InstanceType::String.into()),
804 enum_values: Some(icon_theme_names),
805 ..Default::default()
806 };
807
808 root_schema.definitions.extend([
809 ("ThemeName".into(), theme_name_schema.into()),
810 ("IconThemeName".into(), icon_theme_name_schema.into()),
811 ("FontFamilies".into(), params.font_family_schema()),
812 ("FontFallbacks".into(), params.font_fallback_schema()),
813 ]);
814
815 add_references_to_properties(
816 &mut root_schema,
817 &[
818 ("buffer_font_family", "#/definitions/FontFamilies"),
819 ("buffer_font_fallbacks", "#/definitions/FontFallbacks"),
820 ("ui_font_family", "#/definitions/FontFamilies"),
821 ("ui_font_fallbacks", "#/definitions/FontFallbacks"),
822 ],
823 );
824
825 root_schema
826 }
827}
828
829fn merge<T: Copy>(target: &mut T, value: Option<T>) {
830 if let Some(value) = value {
831 *target = value;
832 }
833}