extension_store.rs

  1use anyhow::Result;
  2use collections::{HashMap, HashSet};
  3use fs::Fs;
  4use futures::StreamExt as _;
  5use gpui::{actions, AppContext, Context, Global, Model, ModelContext, Task};
  6use language::{
  7    LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
  8};
  9use parking_lot::RwLock;
 10use serde::{Deserialize, Serialize};
 11use std::{
 12    ffi::OsStr,
 13    path::{Path, PathBuf},
 14    sync::Arc,
 15    time::Duration,
 16};
 17use theme::ThemeRegistry;
 18use util::{paths::EXTENSIONS_DIR, ResultExt};
 19
 20#[cfg(test)]
 21mod extension_store_test;
 22
 23pub struct ExtensionStore {
 24    manifest: Arc<RwLock<Manifest>>,
 25    fs: Arc<dyn Fs>,
 26    extensions_dir: PathBuf,
 27    manifest_path: PathBuf,
 28    language_registry: Arc<LanguageRegistry>,
 29    theme_registry: Arc<ThemeRegistry>,
 30    _watch_extensions_dir: Task<()>,
 31}
 32
 33struct GlobalExtensionStore(Model<ExtensionStore>);
 34
 35impl Global for GlobalExtensionStore {}
 36
 37#[derive(Deserialize, Serialize, Default)]
 38pub struct Manifest {
 39    pub grammars: HashMap<String, GrammarManifestEntry>,
 40    pub languages: HashMap<Arc<str>, LanguageManifestEntry>,
 41    pub themes: HashMap<String, ThemeManifestEntry>,
 42}
 43
 44#[derive(PartialEq, Eq, Debug, PartialOrd, Ord, Deserialize, Serialize)]
 45pub struct GrammarManifestEntry {
 46    extension: String,
 47    path: PathBuf,
 48}
 49
 50#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
 51pub struct LanguageManifestEntry {
 52    extension: String,
 53    path: PathBuf,
 54    matcher: LanguageMatcher,
 55}
 56
 57#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
 58pub struct ThemeManifestEntry {
 59    extension: String,
 60    path: PathBuf,
 61}
 62
 63actions!(zed, [ReloadExtensions]);
 64
 65pub fn init(
 66    fs: Arc<fs::RealFs>,
 67    language_registry: Arc<LanguageRegistry>,
 68    theme_registry: Arc<ThemeRegistry>,
 69    cx: &mut AppContext,
 70) {
 71    let store = cx.new_model(|cx| {
 72        ExtensionStore::new(
 73            EXTENSIONS_DIR.clone(),
 74            fs.clone(),
 75            language_registry.clone(),
 76            theme_registry,
 77            cx,
 78        )
 79    });
 80
 81    cx.on_action(|_: &ReloadExtensions, cx| {
 82        let store = cx.global::<GlobalExtensionStore>().0.clone();
 83        store
 84            .update(cx, |store, cx| store.reload(cx))
 85            .detach_and_log_err(cx);
 86    });
 87
 88    cx.set_global(GlobalExtensionStore(store));
 89}
 90
 91impl ExtensionStore {
 92    pub fn new(
 93        extensions_dir: PathBuf,
 94        fs: Arc<dyn Fs>,
 95        language_registry: Arc<LanguageRegistry>,
 96        theme_registry: Arc<ThemeRegistry>,
 97        cx: &mut ModelContext<Self>,
 98    ) -> Self {
 99        let mut this = Self {
100            manifest: Default::default(),
101            extensions_dir: extensions_dir.join("installed"),
102            manifest_path: extensions_dir.join("manifest.json"),
103            fs,
104            language_registry,
105            theme_registry,
106            _watch_extensions_dir: Task::ready(()),
107        };
108        this._watch_extensions_dir = this.watch_extensions_dir(cx);
109        this.load(cx);
110        this
111    }
112
113    pub fn load(&mut self, cx: &mut ModelContext<Self>) {
114        let (manifest_content, manifest_metadata, extensions_metadata) =
115            cx.background_executor().block(async {
116                futures::join!(
117                    self.fs.load(&self.manifest_path),
118                    self.fs.metadata(&self.manifest_path),
119                    self.fs.metadata(&self.extensions_dir),
120                )
121            });
122
123        if let Some(manifest_content) = manifest_content.log_err() {
124            if let Some(manifest) = serde_json::from_str(&manifest_content).log_err() {
125                self.manifest_updated(manifest, cx);
126            }
127        }
128
129        let should_reload = if let (Ok(Some(manifest_metadata)), Ok(Some(extensions_metadata))) =
130            (manifest_metadata, extensions_metadata)
131        {
132            extensions_metadata.mtime > manifest_metadata.mtime
133        } else {
134            true
135        };
136
137        if should_reload {
138            self.reload(cx).detach_and_log_err(cx);
139        }
140    }
141
142    fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
143        for (grammar_name, grammar) in &manifest.grammars {
144            let mut grammar_path = self.extensions_dir.clone();
145            grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
146            self.language_registry
147                .register_grammar(grammar_name.clone(), grammar_path);
148        }
149        for (language_name, language) in &manifest.languages {
150            let mut language_path = self.extensions_dir.clone();
151            language_path.extend([language.extension.as_ref(), language.path.as_path()]);
152            self.language_registry.register_extension(
153                language_path.into(),
154                language_name.clone(),
155                language.matcher.clone(),
156                load_plugin_queries,
157            );
158        }
159        let fs = self.fs.clone();
160        let root_dir = self.extensions_dir.clone();
161        let theme_registry = self.theme_registry.clone();
162        let themes = manifest.themes.clone();
163        cx.background_executor()
164            .spawn(async move {
165                for theme in themes.values() {
166                    let mut theme_path = root_dir.clone();
167                    theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
168
169                    theme_registry
170                        .load_user_theme(&theme_path, fs.clone())
171                        .await
172                        .log_err();
173                }
174            })
175            .detach();
176        *self.manifest.write() = manifest;
177    }
178
179    fn watch_extensions_dir(&self, cx: &mut ModelContext<Self>) -> Task<()> {
180        let manifest = self.manifest.clone();
181        let fs = self.fs.clone();
182        let language_registry = self.language_registry.clone();
183        let extensions_dir = self.extensions_dir.clone();
184        cx.background_executor().spawn(async move {
185            let mut changed_languages = HashSet::default();
186            let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await;
187            while let Some(events) = events.next().await {
188                changed_languages.clear();
189                let manifest = manifest.read();
190                for event in events {
191                    for (language_name, language) in &manifest.languages {
192                        let mut language_path = extensions_dir.clone();
193                        language_path
194                            .extend([language.extension.as_ref(), language.path.as_path()]);
195                        if event.path.starts_with(&language_path) || event.path == language_path {
196                            changed_languages.insert(language_name.clone());
197                        }
198                    }
199                }
200                language_registry.reload_languages(&changed_languages);
201            }
202        })
203    }
204
205    pub fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
206        let fs = self.fs.clone();
207        let extensions_dir = self.extensions_dir.clone();
208        let manifest_path = self.manifest_path.clone();
209        cx.spawn(|this, mut cx| async move {
210            let manifest = cx
211                .background_executor()
212                .spawn(async move {
213                    let mut manifest = Manifest::default();
214
215                    let mut extension_paths = fs.read_dir(&extensions_dir).await?;
216                    while let Some(extension_dir) = extension_paths.next().await {
217                        let extension_dir = extension_dir?;
218                        let Some(extension_name) =
219                            extension_dir.file_name().and_then(OsStr::to_str)
220                        else {
221                            continue;
222                        };
223
224                        if let Ok(mut grammar_paths) =
225                            fs.read_dir(&extension_dir.join("grammars")).await
226                        {
227                            while let Some(grammar_path) = grammar_paths.next().await {
228                                let grammar_path = grammar_path?;
229                                let Ok(relative_path) = grammar_path.strip_prefix(&extension_dir)
230                                else {
231                                    continue;
232                                };
233                                let Some(grammar_name) =
234                                    grammar_path.file_stem().and_then(OsStr::to_str)
235                                else {
236                                    continue;
237                                };
238
239                                manifest.grammars.insert(
240                                    grammar_name.into(),
241                                    GrammarManifestEntry {
242                                        extension: extension_name.into(),
243                                        path: relative_path.into(),
244                                    },
245                                );
246                            }
247                        }
248
249                        if let Ok(mut language_paths) =
250                            fs.read_dir(&extension_dir.join("languages")).await
251                        {
252                            while let Some(language_path) = language_paths.next().await {
253                                let language_path = language_path?;
254                                let Ok(relative_path) = language_path.strip_prefix(&extension_dir)
255                                else {
256                                    continue;
257                                };
258                                let config = fs.load(&language_path.join("config.toml")).await?;
259                                let config = ::toml::from_str::<LanguageConfig>(&config)?;
260
261                                manifest.languages.insert(
262                                    config.name.clone(),
263                                    LanguageManifestEntry {
264                                        extension: extension_name.into(),
265                                        path: relative_path.into(),
266                                        matcher: config.matcher,
267                                    },
268                                );
269                            }
270                        }
271
272                        if let Ok(mut theme_paths) =
273                            fs.read_dir(&extension_dir.join("themes")).await
274                        {
275                            while let Some(theme_path) = theme_paths.next().await {
276                                let theme_path = theme_path?;
277                                let Ok(relative_path) = theme_path.strip_prefix(&extension_dir)
278                                else {
279                                    continue;
280                                };
281
282                                let Some(theme_family) =
283                                    ThemeRegistry::read_user_theme(&theme_path, fs.clone())
284                                        .await
285                                        .log_err()
286                                else {
287                                    continue;
288                                };
289
290                                for theme in theme_family.themes {
291                                    let location = ThemeManifestEntry {
292                                        extension: extension_name.into(),
293                                        path: relative_path.into(),
294                                    };
295
296                                    manifest.themes.insert(theme.name, location);
297                                }
298                            }
299                        }
300                    }
301
302                    fs.save(
303                        &manifest_path,
304                        &serde_json::to_string_pretty(&manifest)?.as_str().into(),
305                        Default::default(),
306                    )
307                    .await?;
308
309                    anyhow::Ok(manifest)
310                })
311                .await?;
312            this.update(&mut cx, |this, cx| this.manifest_updated(manifest, cx))
313        })
314    }
315}
316
317fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
318    let mut result = LanguageQueries::default();
319    if let Some(entries) = std::fs::read_dir(root_path).log_err() {
320        for entry in entries {
321            let Some(entry) = entry.log_err() else {
322                continue;
323            };
324            let path = entry.path();
325            if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
326                if !remainder.ends_with(".scm") {
327                    continue;
328                }
329                for (name, query) in QUERY_FILENAME_PREFIXES {
330                    if remainder.starts_with(name) {
331                        if let Some(contents) = std::fs::read_to_string(&path).log_err() {
332                            match query(&mut result) {
333                                None => *query(&mut result) = Some(contents.into()),
334                                Some(r) => r.to_mut().push_str(contents.as_ref()),
335                            }
336                        }
337                        break;
338                    }
339                }
340            }
341        }
342    }
343    result
344}