registry.rs

  1use std::collections::HashMap;
  2use std::path::Path;
  3use std::sync::Arc;
  4
  5use anyhow::{anyhow, Context, Result};
  6use derive_more::{Deref, DerefMut};
  7use fs::Fs;
  8use futures::StreamExt;
  9use gpui::{AppContext, AssetSource, HighlightStyle, SharedString};
 10use parking_lot::RwLock;
 11use refineable::Refineable;
 12use util::ResultExt;
 13
 14use crate::{
 15    try_parse_color, Appearance, AppearanceContent, PlayerColor, PlayerColors, StatusColors,
 16    SyntaxTheme, SystemColors, Theme, ThemeColors, ThemeContent, ThemeFamily, ThemeFamilyContent,
 17    ThemeStyles,
 18};
 19
 20#[derive(Debug, Clone)]
 21pub struct ThemeMeta {
 22    pub name: SharedString,
 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
 35/// Initializes the theme registry.
 36pub fn init(assets: Box<dyn AssetSource>, cx: &mut AppContext) {
 37    cx.set_global(GlobalThemeRegistry(Arc::new(ThemeRegistry::new(assets))));
 38}
 39
 40struct ThemeRegistryState {
 41    themes: HashMap<SharedString, Arc<Theme>>,
 42}
 43
 44pub struct ThemeRegistry {
 45    state: RwLock<ThemeRegistryState>,
 46    assets: Box<dyn AssetSource>,
 47}
 48
 49impl ThemeRegistry {
 50    /// Returns the global [`ThemeRegistry`].
 51    pub fn global(cx: &AppContext) -> Arc<Self> {
 52        cx.global::<GlobalThemeRegistry>().0.clone()
 53    }
 54
 55    /// Returns the global [`ThemeRegistry`].
 56    ///
 57    /// Inserts a default [`ThemeRegistry`] if one does not yet exist.
 58    pub fn default_global(cx: &mut AppContext) -> Arc<Self> {
 59        cx.default_global::<GlobalThemeRegistry>().0.clone()
 60    }
 61
 62    pub fn new(assets: Box<dyn AssetSource>) -> Self {
 63        let registry = Self {
 64            state: RwLock::new(ThemeRegistryState {
 65                themes: HashMap::new(),
 66            }),
 67            assets,
 68        };
 69
 70        // We're loading our new versions of the One themes by default, as
 71        // we need them to be loaded for tests.
 72        //
 73        // These themes will get overwritten when `load_user_themes` is called
 74        // when Zed starts, so the One variants used will be the ones ported from Zed1.
 75        registry.insert_theme_families([crate::one_themes::one_family()]);
 76
 77        registry
 78    }
 79
 80    fn insert_theme_families(&self, families: impl IntoIterator<Item = ThemeFamily>) {
 81        for family in families.into_iter() {
 82            self.insert_themes(family.themes);
 83        }
 84    }
 85
 86    fn insert_themes(&self, themes: impl IntoIterator<Item = Theme>) {
 87        let mut state = self.state.write();
 88        for theme in themes.into_iter() {
 89            state.themes.insert(theme.name.clone(), Arc::new(theme));
 90        }
 91    }
 92
 93    #[allow(unused)]
 94    fn insert_user_theme_families(&self, families: impl IntoIterator<Item = ThemeFamilyContent>) {
 95        for family in families.into_iter() {
 96            self.insert_user_themes(family.themes);
 97        }
 98    }
 99
100    pub fn insert_user_themes(&self, themes: impl IntoIterator<Item = ThemeContent>) {
101        self.insert_themes(themes.into_iter().map(|user_theme| {
102            let mut theme_colors = match user_theme.appearance {
103                AppearanceContent::Light => ThemeColors::light(),
104                AppearanceContent::Dark => ThemeColors::dark(),
105            };
106            theme_colors.refine(&user_theme.style.theme_colors_refinement());
107
108            let mut status_colors = match user_theme.appearance {
109                AppearanceContent::Light => StatusColors::light(),
110                AppearanceContent::Dark => StatusColors::dark(),
111            };
112            status_colors.refine(&user_theme.style.status_colors_refinement());
113
114            let mut player_colors = match user_theme.appearance {
115                AppearanceContent::Light => PlayerColors::light(),
116                AppearanceContent::Dark => PlayerColors::dark(),
117            };
118            if !user_theme.style.players.is_empty() {
119                player_colors = PlayerColors(
120                    user_theme
121                        .style
122                        .players
123                        .into_iter()
124                        .map(|player| PlayerColor {
125                            cursor: player
126                                .cursor
127                                .as_ref()
128                                .and_then(|color| try_parse_color(&color).ok())
129                                .unwrap_or_default(),
130                            background: player
131                                .background
132                                .as_ref()
133                                .and_then(|color| try_parse_color(&color).ok())
134                                .unwrap_or_default(),
135                            selection: player
136                                .selection
137                                .as_ref()
138                                .and_then(|color| try_parse_color(&color).ok())
139                                .unwrap_or_default(),
140                        })
141                        .collect(),
142                );
143            }
144
145            let mut syntax_colors = match user_theme.appearance {
146                AppearanceContent::Light => SyntaxTheme::light(),
147                AppearanceContent::Dark => SyntaxTheme::dark(),
148            };
149            if !user_theme.style.syntax.is_empty() {
150                syntax_colors.highlights = user_theme
151                    .style
152                    .syntax
153                    .iter()
154                    .map(|(syntax_token, highlight)| {
155                        (
156                            syntax_token.clone(),
157                            HighlightStyle {
158                                color: highlight
159                                    .color
160                                    .as_ref()
161                                    .and_then(|color| try_parse_color(&color).ok()),
162                                font_style: highlight.font_style.map(Into::into),
163                                font_weight: highlight.font_weight.map(Into::into),
164                                ..Default::default()
165                            },
166                        )
167                    })
168                    .collect::<Vec<_>>();
169            }
170
171            Theme {
172                id: uuid::Uuid::new_v4().to_string(),
173                name: user_theme.name.into(),
174                appearance: match user_theme.appearance {
175                    AppearanceContent::Light => Appearance::Light,
176                    AppearanceContent::Dark => Appearance::Dark,
177                },
178                styles: ThemeStyles {
179                    system: SystemColors::default(),
180                    colors: theme_colors,
181                    status: status_colors,
182                    player: player_colors,
183                    syntax: Arc::new(syntax_colors),
184                    accents: Vec::new(),
185                },
186            }
187        }));
188    }
189
190    pub fn clear(&mut self) {
191        self.state.write().themes.clear();
192    }
193
194    pub fn list_names(&self, _staff: bool) -> Vec<SharedString> {
195        self.state.read().themes.keys().cloned().collect()
196    }
197
198    pub fn list(&self, _staff: bool) -> Vec<ThemeMeta> {
199        self.state
200            .read()
201            .themes
202            .values()
203            .map(|theme| ThemeMeta {
204                name: theme.name.clone(),
205                appearance: theme.appearance(),
206            })
207            .collect()
208    }
209
210    pub fn get(&self, name: &str) -> Result<Arc<Theme>> {
211        self.state
212            .read()
213            .themes
214            .get(name)
215            .ok_or_else(|| anyhow!("theme not found: {}", name))
216            .cloned()
217    }
218
219    /// Loads the themes bundled with the Zed binary and adds them to the registry.
220    pub fn load_bundled_themes(&self) {
221        let theme_paths = self
222            .assets
223            .list("themes/")
224            .expect("failed to list theme assets")
225            .into_iter()
226            .filter(|path| path.ends_with(".json"));
227
228        for path in theme_paths {
229            let Some(theme) = self.assets.load(&path).log_err() else {
230                continue;
231            };
232
233            let Some(theme_family) = serde_json::from_slice(&theme)
234                .with_context(|| format!("failed to parse theme at path \"{path}\""))
235                .log_err()
236            else {
237                continue;
238            };
239
240            self.insert_user_theme_families([theme_family]);
241        }
242    }
243
244    /// Loads the user themes from the specified directory and adds them to the registry.
245    pub async fn load_user_themes(&self, themes_path: &Path, fs: Arc<dyn Fs>) -> Result<()> {
246        let mut theme_paths = fs
247            .read_dir(themes_path)
248            .await
249            .with_context(|| format!("reading themes from {themes_path:?}"))?;
250
251        while let Some(theme_path) = theme_paths.next().await {
252            let Some(theme_path) = theme_path.log_err() else {
253                continue;
254            };
255
256            let Some(reader) = fs.open_sync(&theme_path).await.log_err() else {
257                continue;
258            };
259
260            let Some(theme) = serde_json::from_reader(reader).log_err() else {
261                continue;
262            };
263
264            self.insert_user_theme_families([theme]);
265        }
266
267        Ok(())
268    }
269}
270
271impl Default for ThemeRegistry {
272    fn default() -> Self {
273        Self::new(Box::new(()))
274    }
275}