1use std::sync::Arc;
2use std::time::Instant;
3
4use gpui::{App, Global, ReadGlobal, SharedString};
5use parking_lot::RwLock;
6
7#[derive(Default)]
8struct FontFamilyCacheState {
9 loaded_at: Option<Instant>,
10 font_families: Vec<SharedString>,
11}
12
13/// A cache for the list of font families.
14///
15/// Listing the available font families from the text system is expensive,
16/// so we do it once and then use the cached values each render.
17#[derive(Default)]
18pub struct FontFamilyCache {
19 state: Arc<RwLock<FontFamilyCacheState>>,
20}
21
22#[derive(Default)]
23struct GlobalFontFamilyCache(Arc<FontFamilyCache>);
24
25impl Global for GlobalFontFamilyCache {}
26
27impl FontFamilyCache {
28 /// Initializes the global font family cache.
29 pub fn init_global(cx: &mut App) {
30 cx.default_global::<GlobalFontFamilyCache>();
31 }
32
33 /// Returns the global font family cache.
34 pub fn global(cx: &App) -> Arc<Self> {
35 GlobalFontFamilyCache::global(cx).0.clone()
36 }
37
38 /// Returns the list of font families.
39 pub fn list_font_families(&self, cx: &App) -> Vec<SharedString> {
40 if self.state.read().loaded_at.is_some() {
41 return self.state.read().font_families.clone();
42 }
43
44 let mut lock = self.state.write();
45 lock.font_families = cx
46 .text_system()
47 .all_font_names()
48 .into_iter()
49 .map(SharedString::from)
50 .collect();
51 lock.loaded_at = Some(Instant::now());
52
53 lock.font_families.clone()
54 }
55
56 /// Returns the list of font families if they have been loaded
57 pub fn try_list_font_families(&self) -> Option<Vec<SharedString>> {
58 self.state
59 .try_read()
60 .filter(|state| state.loaded_at.is_some())
61 .map(|state| state.font_families.clone())
62 }
63
64 /// Prefetch all font names in the background
65 pub async fn prefetch(&self, cx: &gpui::AsyncApp) {
66 if self
67 .state
68 .try_read()
69 .is_none_or(|state| state.loaded_at.is_some())
70 {
71 return;
72 }
73
74 let Ok(text_system) = cx.update(|cx| App::text_system(cx).clone()) else {
75 return;
76 };
77
78 let state = self.state.clone();
79
80 cx.background_executor()
81 .spawn(async move {
82 // We take this lock in the background executor to ensure that synchronous calls to `list_font_families` are blocked while we are prefetching,
83 // while not blocking the main thread and risking deadlocks
84 let mut lock = state.write();
85 let all_font_names = text_system
86 .all_font_names()
87 .into_iter()
88 .map(SharedString::from)
89 .collect();
90 lock.font_families = all_font_names;
91 lock.loaded_at = Some(Instant::now());
92 })
93 .await;
94 }
95}