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