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}