extension_store.rs

  1use anyhow::{Context as _, Result};
  2use collections::HashMap;
  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, ThemeSettings};
 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<()>; 2],
 31}
 32
 33struct GlobalExtensionStore(Model<ExtensionStore>);
 34
 35impl Global for GlobalExtensionStore {}
 36
 37#[derive(Deserialize, Serialize, Default)]
 38pub struct Manifest {
 39    pub grammars: HashMap<Arc<str>, 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    grammar: Option<Arc<str>>,
 56}
 57
 58#[derive(Clone, PartialEq, Debug, Deserialize, Serialize)]
 59pub struct ThemeManifestEntry {
 60    extension: String,
 61    path: PathBuf,
 62}
 63
 64actions!(zed, [ReloadExtensions]);
 65
 66pub fn init(
 67    fs: Arc<fs::RealFs>,
 68    language_registry: Arc<LanguageRegistry>,
 69    theme_registry: Arc<ThemeRegistry>,
 70    cx: &mut AppContext,
 71) {
 72    let store = cx.new_model(|cx| {
 73        ExtensionStore::new(
 74            EXTENSIONS_DIR.clone(),
 75            fs.clone(),
 76            language_registry.clone(),
 77            theme_registry,
 78            cx,
 79        )
 80    });
 81
 82    cx.on_action(|_: &ReloadExtensions, cx| {
 83        let store = cx.global::<GlobalExtensionStore>().0.clone();
 84        store
 85            .update(cx, |store, cx| store.reload(cx))
 86            .detach_and_log_err(cx);
 87    });
 88
 89    cx.set_global(GlobalExtensionStore(store));
 90}
 91
 92impl ExtensionStore {
 93    pub fn new(
 94        extensions_dir: PathBuf,
 95        fs: Arc<dyn Fs>,
 96        language_registry: Arc<LanguageRegistry>,
 97        theme_registry: Arc<ThemeRegistry>,
 98        cx: &mut ModelContext<Self>,
 99    ) -> Self {
100        let mut this = Self {
101            manifest: Default::default(),
102            extensions_dir: extensions_dir.join("installed"),
103            manifest_path: extensions_dir.join("manifest.json"),
104            fs,
105            language_registry,
106            theme_registry,
107            _watch_extensions_dir: [Task::ready(()), Task::ready(())],
108        };
109        this._watch_extensions_dir = this.watch_extensions_dir(cx);
110        this.load(cx);
111        this
112    }
113
114    pub fn load(&mut self, cx: &mut ModelContext<Self>) {
115        let (manifest_content, manifest_metadata, extensions_metadata) =
116            cx.background_executor().block(async {
117                futures::join!(
118                    self.fs.load(&self.manifest_path),
119                    self.fs.metadata(&self.manifest_path),
120                    self.fs.metadata(&self.extensions_dir),
121                )
122            });
123
124        if let Some(manifest_content) = manifest_content.log_err() {
125            if let Some(manifest) = serde_json::from_str(&manifest_content).log_err() {
126                self.manifest_updated(manifest, cx);
127            }
128        }
129
130        let should_reload = if let (Ok(Some(manifest_metadata)), Ok(Some(extensions_metadata))) =
131            (manifest_metadata, extensions_metadata)
132        {
133            extensions_metadata.mtime > manifest_metadata.mtime
134        } else {
135            true
136        };
137
138        if should_reload {
139            self.reload(cx).detach_and_log_err(cx);
140        }
141    }
142
143    fn manifest_updated(&mut self, manifest: Manifest, cx: &mut ModelContext<Self>) {
144        self.language_registry
145            .register_wasm_grammars(manifest.grammars.iter().map(|(grammar_name, grammar)| {
146                let mut grammar_path = self.extensions_dir.clone();
147                grammar_path.extend([grammar.extension.as_ref(), grammar.path.as_path()]);
148                (grammar_name.clone(), grammar_path)
149            }));
150
151        for (language_name, language) in &manifest.languages {
152            let mut language_path = self.extensions_dir.clone();
153            language_path.extend([language.extension.as_ref(), language.path.as_path()]);
154            self.language_registry.register_language(
155                language_name.clone(),
156                language.grammar.clone(),
157                language.matcher.clone(),
158                vec![],
159                move || {
160                    let config = std::fs::read(language_path.join("config.toml"))?;
161                    let config: LanguageConfig = ::toml::from_slice(&config)?;
162                    let queries = load_plugin_queries(&language_path);
163                    Ok((config, queries))
164                },
165            );
166        }
167        let fs = self.fs.clone();
168        let root_dir = self.extensions_dir.clone();
169        let theme_registry = self.theme_registry.clone();
170        let themes = manifest.themes.clone();
171        cx.background_executor()
172            .spawn(async move {
173                for theme in themes.values() {
174                    let mut theme_path = root_dir.clone();
175                    theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
176
177                    theme_registry
178                        .load_user_theme(&theme_path, fs.clone())
179                        .await
180                        .log_err();
181                }
182            })
183            .detach();
184        *self.manifest.write() = manifest;
185    }
186
187    fn watch_extensions_dir(&self, cx: &mut ModelContext<Self>) -> [Task<()>; 2] {
188        let manifest = self.manifest.clone();
189        let fs = self.fs.clone();
190        let language_registry = self.language_registry.clone();
191        let theme_registry = self.theme_registry.clone();
192        let extensions_dir = self.extensions_dir.clone();
193
194        let (reload_theme_tx, mut reload_theme_rx) = futures::channel::mpsc::unbounded();
195
196        let events_task = cx.background_executor().spawn(async move {
197            let mut events = fs.watch(&extensions_dir, Duration::from_millis(250)).await;
198            while let Some(events) = events.next().await {
199                let mut changed_grammars = Vec::default();
200                let mut changed_languages = Vec::default();
201                let mut changed_themes = Vec::default();
202
203                {
204                    let manifest = manifest.read();
205                    for event in events {
206                        for (grammar_name, grammar) in &manifest.grammars {
207                            let mut grammar_path = extensions_dir.clone();
208                            grammar_path
209                                .extend([grammar.extension.as_ref(), grammar.path.as_path()]);
210                            if event.path.starts_with(&grammar_path) || event.path == grammar_path {
211                                changed_grammars.push(grammar_name.clone());
212                            }
213                        }
214
215                        for (language_name, language) in &manifest.languages {
216                            let mut language_path = extensions_dir.clone();
217                            language_path
218                                .extend([language.extension.as_ref(), language.path.as_path()]);
219                            if event.path.starts_with(&language_path) || event.path == language_path
220                            {
221                                changed_languages.push(language_name.clone());
222                            }
223                        }
224
225                        for (_theme_name, theme) in &manifest.themes {
226                            let mut theme_path = extensions_dir.clone();
227                            theme_path.extend([theme.extension.as_ref(), theme.path.as_path()]);
228                            if event.path.starts_with(&theme_path) || event.path == theme_path {
229                                changed_themes.push(theme_path.clone());
230                            }
231                        }
232                    }
233                }
234
235                language_registry.reload_languages(&changed_languages, &changed_grammars);
236
237                for theme_path in &changed_themes {
238                    theme_registry
239                        .load_user_theme(&theme_path, fs.clone())
240                        .await
241                        .context("failed to load user theme")
242                        .log_err();
243                }
244
245                if !changed_themes.is_empty() {
246                    reload_theme_tx.unbounded_send(()).ok();
247                }
248            }
249        });
250
251        let reload_theme_task = cx.spawn(|_, cx| async move {
252            while let Some(_) = reload_theme_rx.next().await {
253                if cx
254                    .update(|cx| ThemeSettings::reload_current_theme(cx))
255                    .is_err()
256                {
257                    break;
258                }
259            }
260        });
261
262        [events_task, reload_theme_task]
263    }
264
265    pub fn reload(&mut self, cx: &mut ModelContext<Self>) -> Task<Result<()>> {
266        let fs = self.fs.clone();
267        let extensions_dir = self.extensions_dir.clone();
268        let manifest_path = self.manifest_path.clone();
269        cx.spawn(|this, mut cx| async move {
270            let manifest = cx
271                .background_executor()
272                .spawn(async move {
273                    let mut manifest = Manifest::default();
274
275                    let mut extension_paths = fs
276                        .read_dir(&extensions_dir)
277                        .await
278                        .context("failed to read extensions directory")?;
279                    while let Some(extension_dir) = extension_paths.next().await {
280                        let extension_dir = extension_dir?;
281                        let Some(extension_name) =
282                            extension_dir.file_name().and_then(OsStr::to_str)
283                        else {
284                            continue;
285                        };
286
287                        if let Ok(mut grammar_paths) =
288                            fs.read_dir(&extension_dir.join("grammars")).await
289                        {
290                            while let Some(grammar_path) = grammar_paths.next().await {
291                                let grammar_path = grammar_path?;
292                                let Ok(relative_path) = grammar_path.strip_prefix(&extension_dir)
293                                else {
294                                    continue;
295                                };
296                                let Some(grammar_name) =
297                                    grammar_path.file_stem().and_then(OsStr::to_str)
298                                else {
299                                    continue;
300                                };
301
302                                manifest.grammars.insert(
303                                    grammar_name.into(),
304                                    GrammarManifestEntry {
305                                        extension: extension_name.into(),
306                                        path: relative_path.into(),
307                                    },
308                                );
309                            }
310                        }
311
312                        if let Ok(mut language_paths) =
313                            fs.read_dir(&extension_dir.join("languages")).await
314                        {
315                            while let Some(language_path) = language_paths.next().await {
316                                let language_path = language_path?;
317                                let Ok(relative_path) = language_path.strip_prefix(&extension_dir)
318                                else {
319                                    continue;
320                                };
321                                let config = fs.load(&language_path.join("config.toml")).await?;
322                                let config = ::toml::from_str::<LanguageConfig>(&config)?;
323
324                                manifest.languages.insert(
325                                    config.name.clone(),
326                                    LanguageManifestEntry {
327                                        extension: extension_name.into(),
328                                        path: relative_path.into(),
329                                        matcher: config.matcher,
330                                        grammar: config.grammar,
331                                    },
332                                );
333                            }
334                        }
335
336                        if let Ok(mut theme_paths) =
337                            fs.read_dir(&extension_dir.join("themes")).await
338                        {
339                            while let Some(theme_path) = theme_paths.next().await {
340                                let theme_path = theme_path?;
341                                let Ok(relative_path) = theme_path.strip_prefix(&extension_dir)
342                                else {
343                                    continue;
344                                };
345
346                                let Some(theme_family) =
347                                    ThemeRegistry::read_user_theme(&theme_path, fs.clone())
348                                        .await
349                                        .log_err()
350                                else {
351                                    continue;
352                                };
353
354                                for theme in theme_family.themes {
355                                    let location = ThemeManifestEntry {
356                                        extension: extension_name.into(),
357                                        path: relative_path.into(),
358                                    };
359
360                                    manifest.themes.insert(theme.name, location);
361                                }
362                            }
363                        }
364                    }
365
366                    fs.save(
367                        &manifest_path,
368                        &serde_json::to_string_pretty(&manifest)?.as_str().into(),
369                        Default::default(),
370                    )
371                    .await
372                    .context("failed to save extension manifest")?;
373
374                    anyhow::Ok(manifest)
375                })
376                .await?;
377            this.update(&mut cx, |this, cx| this.manifest_updated(manifest, cx))
378        })
379    }
380}
381
382fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
383    let mut result = LanguageQueries::default();
384    if let Some(entries) = std::fs::read_dir(root_path).log_err() {
385        for entry in entries {
386            let Some(entry) = entry.log_err() else {
387                continue;
388            };
389            let path = entry.path();
390            if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
391                if !remainder.ends_with(".scm") {
392                    continue;
393                }
394                for (name, query) in QUERY_FILENAME_PREFIXES {
395                    if remainder.starts_with(name) {
396                        if let Some(contents) = std::fs::read_to_string(&path).log_err() {
397                            match query(&mut result) {
398                                None => *query(&mut result) = Some(contents.into()),
399                                Some(r) => r.to_mut().push_str(contents.as_ref()),
400                            }
401                        }
402                        break;
403                    }
404                }
405            }
406        }
407    }
408    result
409}