registry.rs

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