registry.rs

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