1use std::sync::Arc;
2use std::{fmt::Debug, path::Path};
3
4use anyhow::{anyhow, Context, Result};
5use collections::HashMap;
6use derive_more::{Deref, DerefMut};
7use fs::Fs;
8use futures::StreamExt;
9use gpui::{AppContext, AssetSource, Global, SharedString};
10use parking_lot::RwLock;
11use util::ResultExt;
12
13use crate::{refine_theme_family, Appearance, Theme, ThemeFamily, ThemeFamilyContent};
14
15/// The metadata for a theme.
16#[derive(Debug, Clone)]
17pub struct ThemeMeta {
18 /// The name of the theme.
19 pub name: SharedString,
20 /// The appearance of the theme.
21 pub appearance: Appearance,
22}
23
24/// The global [`ThemeRegistry`].
25///
26/// This newtype exists for obtaining a unique [`TypeId`](std::any::TypeId) when
27/// inserting the [`ThemeRegistry`] into the context as a global.
28///
29/// This should not be exposed outside of this module.
30#[derive(Default, Deref, DerefMut)]
31struct GlobalThemeRegistry(Arc<ThemeRegistry>);
32
33impl Global for GlobalThemeRegistry {}
34
35struct ThemeRegistryState {
36 themes: HashMap<SharedString, Arc<Theme>>,
37}
38
39/// The registry for themes.
40pub struct ThemeRegistry {
41 state: RwLock<ThemeRegistryState>,
42 assets: Box<dyn AssetSource>,
43}
44
45impl ThemeRegistry {
46 /// Returns the global [`ThemeRegistry`].
47 pub fn global(cx: &AppContext) -> Arc<Self> {
48 cx.global::<GlobalThemeRegistry>().0.clone()
49 }
50
51 /// Returns the global [`ThemeRegistry`].
52 ///
53 /// Inserts a default [`ThemeRegistry`] if one does not yet exist.
54 pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
55 cx.default_global::<GlobalThemeRegistry>().0.clone()
56 }
57
58 /// Sets the global [`ThemeRegistry`].
59 pub(crate) fn set_global(assets: Box<dyn AssetSource>, cx: &mut AppContext) {
60 cx.set_global(GlobalThemeRegistry(Arc::new(ThemeRegistry::new(assets))));
61 }
62
63 /// Creates a new [`ThemeRegistry`] with the given [`AssetSource`].
64 pub fn new(assets: Box<dyn AssetSource>) -> Self {
65 let registry = Self {
66 state: RwLock::new(ThemeRegistryState {
67 themes: HashMap::default(),
68 }),
69 assets,
70 };
71
72 // We're loading the Zed default theme, as we need a theme to be loaded
73 // for tests.
74 registry.insert_theme_families([crate::fallback_themes::zed_default_themes()]);
75
76 registry
77 }
78
79 fn insert_theme_families(&self, families: impl IntoIterator<Item = ThemeFamily>) {
80 for family in families.into_iter() {
81 self.insert_themes(family.themes);
82 }
83 }
84
85 fn insert_themes(&self, themes: impl IntoIterator<Item = Theme>) {
86 let mut state = self.state.write();
87 for theme in themes.into_iter() {
88 state.themes.insert(theme.name.clone(), Arc::new(theme));
89 }
90 }
91
92 #[allow(unused)]
93 fn insert_user_theme_families(&self, families: impl IntoIterator<Item = ThemeFamilyContent>) {
94 for family in families.into_iter() {
95 let refined_family = refine_theme_family(family);
96
97 self.insert_themes(refined_family.themes);
98 }
99 }
100
101 /// Removes the themes with the given names from the registry.
102 pub fn remove_user_themes(&self, themes_to_remove: &[SharedString]) {
103 self.state
104 .write()
105 .themes
106 .retain(|name, _| !themes_to_remove.contains(name))
107 }
108
109 /// Removes all themes from the registry.
110 pub fn clear(&self) {
111 self.state.write().themes.clear();
112 }
113
114 /// Returns the names of all themes in the registry.
115 pub fn list_names(&self, _staff: bool) -> Vec<SharedString> {
116 let mut names = self.state.read().themes.keys().cloned().collect::<Vec<_>>();
117 names.sort();
118 names
119 }
120
121 /// Returns the metadata of all themes in the registry.
122 pub fn list(&self, _staff: bool) -> Vec<ThemeMeta> {
123 self.state
124 .read()
125 .themes
126 .values()
127 .map(|theme| ThemeMeta {
128 name: theme.name.clone(),
129 appearance: theme.appearance(),
130 })
131 .collect()
132 }
133
134 /// Returns the theme with the given name.
135 pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
136 self.state
137 .read()
138 .themes
139 .get(name)
140 .ok_or_else(|| anyhow!("theme not found: {}", name))
141 .cloned()
142 }
143
144 /// Loads the themes bundled with the Zed binary and adds them to the registry.
145 pub fn load_bundled_themes(&self) {
146 let theme_paths = self
147 .assets
148 .list("themes/")
149 .expect("failed to list theme assets")
150 .into_iter()
151 .filter(|path| path.ends_with(".json"));
152
153 for path in theme_paths {
154 let Some(theme) = self.assets.load(&path).log_err().flatten() else {
155 continue;
156 };
157
158 let Some(theme_family) = serde_json::from_slice(&theme)
159 .with_context(|| format!("failed to parse theme at path \"{path}\""))
160 .log_err()
161 else {
162 continue;
163 };
164
165 self.insert_user_theme_families([theme_family]);
166 }
167 }
168
169 /// Loads the user themes from the specified directory and adds them to the registry.
170 pub async fn load_user_themes(&self, themes_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
171 let mut theme_paths = fs
172 .read_dir(themes_path)
173 .await
174 .with_context(|| format!("reading themes from {themes_path:?}"))?;
175
176 while let Some(theme_path) = theme_paths.next().await {
177 let Some(theme_path) = theme_path.log_err() else {
178 continue;
179 };
180
181 self.load_user_theme(&theme_path, fs.clone())
182 .await
183 .log_err();
184 }
185
186 Ok(())
187 }
188
189 /// Asynchronously reads the user theme from the specified path.
190 pub async fn read_user_theme(theme_path: &Path, fs: Arc<dyn Fs>) -> Result<ThemeFamilyContent> {
191 let reader = fs.open_sync(theme_path).await?;
192 let theme_family: ThemeFamilyContent = serde_json_lenient::from_reader(reader)?;
193
194 for theme in &theme_family.themes {
195 if theme
196 .style
197 .colors
198 .deprecated_scrollbar_thumb_background
199 .is_some()
200 {
201 log::warn!(
202 r#"Theme "{theme_name}" is using a deprecated style property: scrollbar_thumb.background. Use `scrollbar.thumb.background` instead."#,
203 theme_name = theme.name
204 )
205 }
206 }
207
208 Ok(theme_family)
209 }
210
211 /// Loads the user theme from the specified path and adds it to the registry.
212 pub async fn load_user_theme(&self, theme_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
213 let theme = Self::read_user_theme(theme_path, fs).await?;
214
215 self.insert_user_theme_families([theme]);
216
217 Ok(())
218 }
219}
220
221impl Default for ThemeRegistry {
222 fn default() -> Self {
223 Self::new(Box::new(()))
224 }
225}