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}