1mod build_extension;
2mod extension_lsp_adapter;
3mod extension_manifest;
4mod wasm_host;
5
6#[cfg(test)]
7mod extension_store_test;
8
9use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
10use anyhow::{anyhow, bail, Context as _, Result};
11use async_compression::futures::bufread::GzipDecoder;
12use async_tar::Archive;
13use build_extension::{CompileExtensionOptions, ExtensionBuilder};
14use collections::{hash_map, BTreeMap, HashMap, HashSet};
15use extension_manifest::ExtensionLibraryKind;
16use fs::{Fs, RemoveOptions};
17use futures::{
18 channel::{
19 mpsc::{unbounded, UnboundedSender},
20 oneshot,
21 },
22 io::BufReader,
23 select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
24};
25use gpui::{actions, AppContext, Context, EventEmitter, Global, Model, ModelContext, Task};
26use language::{
27 LanguageConfig, LanguageMatcher, LanguageQueries, LanguageRegistry, QUERY_FILENAME_PREFIXES,
28};
29use node_runtime::NodeRuntime;
30use serde::{Deserialize, Serialize};
31use std::{
32 cmp::Ordering,
33 ffi::OsStr,
34 path::{self, Path, PathBuf},
35 sync::Arc,
36 time::{Duration, Instant},
37};
38use theme::{ThemeRegistry, ThemeSettings};
39use util::{
40 http::{AsyncBody, HttpClient, HttpClientWithUrl},
41 paths::EXTENSIONS_DIR,
42 ResultExt,
43};
44use wasm_host::{WasmExtension, WasmHost};
45
46pub use extension_manifest::{ExtensionManifest, GrammarManifestEntry, OldExtensionManifest};
47
48const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
49const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
50
51#[derive(Deserialize)]
52pub struct ExtensionsApiResponse {
53 pub data: Vec<ExtensionApiResponse>,
54}
55
56#[derive(Clone, Deserialize)]
57pub struct ExtensionApiResponse {
58 pub id: Arc<str>,
59 pub name: String,
60 pub version: Arc<str>,
61 pub description: Option<String>,
62 pub authors: Vec<String>,
63 pub repository: String,
64 pub download_count: usize,
65}
66
67pub struct ExtensionStore {
68 builder: Arc<ExtensionBuilder>,
69 extension_index: ExtensionIndex,
70 fs: Arc<dyn Fs>,
71 http_client: Arc<HttpClientWithUrl>,
72 reload_tx: UnboundedSender<Option<Arc<str>>>,
73 reload_complete_senders: Vec<oneshot::Sender<()>>,
74 installed_dir: PathBuf,
75 outstanding_operations: HashMap<Arc<str>, ExtensionOperation>,
76 index_path: PathBuf,
77 language_registry: Arc<LanguageRegistry>,
78 theme_registry: Arc<ThemeRegistry>,
79 modified_extensions: HashSet<Arc<str>>,
80 wasm_host: Arc<WasmHost>,
81 wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
82 tasks: Vec<Task<()>>,
83}
84
85#[derive(Clone)]
86pub enum ExtensionStatus {
87 NotInstalled,
88 Installing,
89 Upgrading,
90 Installed(Arc<str>),
91 Removing,
92}
93
94enum ExtensionOperation {
95 Upgrade,
96 Install,
97 Remove,
98}
99
100#[derive(Copy, Clone)]
101pub enum Event {
102 ExtensionsUpdated,
103 StartedReloading,
104}
105
106impl EventEmitter<Event> for ExtensionStore {}
107
108struct GlobalExtensionStore(Model<ExtensionStore>);
109
110impl Global for GlobalExtensionStore {}
111
112#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
113pub struct ExtensionIndex {
114 pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
115 pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
116 pub languages: BTreeMap<Arc<str>, ExtensionIndexLanguageEntry>,
117}
118
119#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
120pub struct ExtensionIndexEntry {
121 manifest: Arc<ExtensionManifest>,
122 dev: bool,
123}
124
125#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
126pub struct ExtensionIndexThemeEntry {
127 extension: Arc<str>,
128 path: PathBuf,
129}
130
131#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
132pub struct ExtensionIndexLanguageEntry {
133 extension: Arc<str>,
134 path: PathBuf,
135 matcher: LanguageMatcher,
136 grammar: Option<Arc<str>>,
137}
138
139actions!(zed, [ReloadExtensions]);
140
141pub fn init(
142 fs: Arc<fs::RealFs>,
143 http_client: Arc<HttpClientWithUrl>,
144 node_runtime: Arc<dyn NodeRuntime>,
145 language_registry: Arc<LanguageRegistry>,
146 theme_registry: Arc<ThemeRegistry>,
147 cx: &mut AppContext,
148) {
149 let store = cx.new_model(move |cx| {
150 ExtensionStore::new(
151 EXTENSIONS_DIR.clone(),
152 None,
153 fs,
154 http_client,
155 node_runtime,
156 language_registry,
157 theme_registry,
158 cx,
159 )
160 });
161
162 cx.on_action(|_: &ReloadExtensions, cx| {
163 let store = cx.global::<GlobalExtensionStore>().0.clone();
164 store.update(cx, |store, cx| drop(store.reload(None, cx)));
165 });
166
167 cx.set_global(GlobalExtensionStore(store));
168}
169
170impl ExtensionStore {
171 pub fn global(cx: &AppContext) -> Model<Self> {
172 cx.global::<GlobalExtensionStore>().0.clone()
173 }
174
175 #[allow(clippy::too_many_arguments)]
176 pub fn new(
177 extensions_dir: PathBuf,
178 build_dir: Option<PathBuf>,
179 fs: Arc<dyn Fs>,
180 http_client: Arc<HttpClientWithUrl>,
181 node_runtime: Arc<dyn NodeRuntime>,
182 language_registry: Arc<LanguageRegistry>,
183 theme_registry: Arc<ThemeRegistry>,
184 cx: &mut ModelContext<Self>,
185 ) -> Self {
186 let work_dir = extensions_dir.join("work");
187 let build_dir = build_dir.unwrap_or_else(|| extensions_dir.join("build"));
188 let installed_dir = extensions_dir.join("installed");
189 let index_path = extensions_dir.join("index.json");
190
191 let (reload_tx, mut reload_rx) = unbounded();
192 let mut this = Self {
193 extension_index: Default::default(),
194 installed_dir,
195 index_path,
196 builder: Arc::new(ExtensionBuilder::new(build_dir)),
197 outstanding_operations: Default::default(),
198 modified_extensions: Default::default(),
199 reload_complete_senders: Vec::new(),
200 wasm_host: WasmHost::new(
201 fs.clone(),
202 http_client.clone(),
203 node_runtime,
204 language_registry.clone(),
205 work_dir,
206 ),
207 wasm_extensions: Vec::new(),
208 fs,
209 http_client,
210 language_registry,
211 theme_registry,
212 reload_tx,
213 tasks: Vec::new(),
214 };
215
216 // The extensions store maintains an index file, which contains a complete
217 // list of the installed extensions and the resources that they provide.
218 // This index is loaded synchronously on startup.
219 let (index_content, index_metadata, extensions_metadata) =
220 cx.background_executor().block(async {
221 futures::join!(
222 this.fs.load(&this.index_path),
223 this.fs.metadata(&this.index_path),
224 this.fs.metadata(&this.installed_dir),
225 )
226 });
227
228 // Normally, there is no need to rebuild the index. But if the index file
229 // is invalid or is out-of-date according to the filesystem mtimes, then
230 // it must be asynchronously rebuilt.
231 let mut extension_index = ExtensionIndex::default();
232 let mut extension_index_needs_rebuild = true;
233 if let Some(index_content) = index_content.ok() {
234 if let Some(index) = serde_json::from_str(&index_content).log_err() {
235 extension_index = index;
236 if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) =
237 (index_metadata, extensions_metadata)
238 {
239 if index_metadata.mtime > extensions_metadata.mtime {
240 extension_index_needs_rebuild = false;
241 }
242 }
243 }
244 }
245
246 // Immediately load all of the extensions in the initial manifest. If the
247 // index needs to be rebuild, then enqueue
248 let load_initial_extensions = this.extensions_updated(extension_index, cx);
249 if extension_index_needs_rebuild {
250 let _ = this.reload(None, cx);
251 }
252
253 // Perform all extension loading in a single task to ensure that we
254 // never attempt to simultaneously load/unload extensions from multiple
255 // parallel tasks.
256 this.tasks.push(cx.spawn(|this, mut cx| {
257 async move {
258 load_initial_extensions.await;
259
260 let mut debounce_timer = cx
261 .background_executor()
262 .spawn(futures::future::pending())
263 .fuse();
264 loop {
265 select_biased! {
266 _ = debounce_timer => {
267 let index = this
268 .update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
269 .await;
270 this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
271 .await;
272 }
273 extension_id = reload_rx.next() => {
274 let Some(extension_id) = extension_id else { break; };
275 this.update(&mut cx, |this, _| {
276 this.modified_extensions.extend(extension_id);
277 })?;
278 debounce_timer = cx
279 .background_executor()
280 .timer(RELOAD_DEBOUNCE_DURATION)
281 .fuse();
282 }
283 }
284 }
285
286 anyhow::Ok(())
287 }
288 .map(drop)
289 }));
290
291 // Watch the installed extensions directory for changes. Whenever changes are
292 // detected, rebuild the extension index, and load/unload any extensions that
293 // have been added, removed, or modified.
294 this.tasks.push(cx.background_executor().spawn({
295 let fs = this.fs.clone();
296 let reload_tx = this.reload_tx.clone();
297 let installed_dir = this.installed_dir.clone();
298 async move {
299 let mut paths = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
300 while let Some(paths) = paths.next().await {
301 for path in paths {
302 let Ok(event_path) = path.strip_prefix(&installed_dir) else {
303 continue;
304 };
305
306 if let Some(path::Component::Normal(extension_dir_name)) =
307 event_path.components().next()
308 {
309 if let Some(extension_id) = extension_dir_name.to_str() {
310 reload_tx.unbounded_send(Some(extension_id.into())).ok();
311 }
312 }
313 }
314 }
315 }
316 }));
317
318 this
319 }
320
321 fn reload(
322 &mut self,
323 modified_extension: Option<Arc<str>>,
324 cx: &mut ModelContext<Self>,
325 ) -> impl Future<Output = ()> {
326 let (tx, rx) = oneshot::channel();
327 self.reload_complete_senders.push(tx);
328 self.reload_tx
329 .unbounded_send(modified_extension)
330 .expect("reload task exited");
331 cx.emit(Event::StartedReloading);
332 async move {
333 rx.await.ok();
334 }
335 }
336
337 fn extensions_dir(&self) -> PathBuf {
338 self.installed_dir.clone()
339 }
340
341 pub fn extension_status(&self, extension_id: &str) -> ExtensionStatus {
342 match self.outstanding_operations.get(extension_id) {
343 Some(ExtensionOperation::Install) => ExtensionStatus::Installing,
344 Some(ExtensionOperation::Remove) => ExtensionStatus::Removing,
345 Some(ExtensionOperation::Upgrade) => ExtensionStatus::Upgrading,
346 None => match self.extension_index.extensions.get(extension_id) {
347 Some(extension) => ExtensionStatus::Installed(extension.manifest.version.clone()),
348 None => ExtensionStatus::NotInstalled,
349 },
350 }
351 }
352
353 pub fn dev_extensions(&self) -> impl Iterator<Item = &Arc<ExtensionManifest>> {
354 self.extension_index
355 .extensions
356 .values()
357 .filter_map(|extension| extension.dev.then_some(&extension.manifest))
358 }
359
360 pub fn fetch_extensions(
361 &self,
362 search: Option<&str>,
363 cx: &mut ModelContext<Self>,
364 ) -> Task<Result<Vec<ExtensionApiResponse>>> {
365 let url = self.http_client.build_zed_api_url(&format!(
366 "/extensions{query}",
367 query = search
368 .map(|search| format!("?filter={search}"))
369 .unwrap_or_default()
370 ));
371 let http_client = self.http_client.clone();
372 cx.spawn(move |_, _| async move {
373 let mut response = http_client.get(&url, AsyncBody::empty(), true).await?;
374
375 let mut body = Vec::new();
376 response
377 .body_mut()
378 .read_to_end(&mut body)
379 .await
380 .context("error reading extensions")?;
381
382 if response.status().is_client_error() {
383 let text = String::from_utf8_lossy(body.as_slice());
384 bail!(
385 "status error {}, response: {text:?}",
386 response.status().as_u16()
387 );
388 }
389
390 let response: ExtensionsApiResponse = serde_json::from_slice(&body)?;
391
392 Ok(response.data)
393 })
394 }
395
396 pub fn install_extension(
397 &mut self,
398 extension_id: Arc<str>,
399 version: Arc<str>,
400 cx: &mut ModelContext<Self>,
401 ) {
402 self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Install, cx)
403 }
404
405 pub fn upgrade_extension(
406 &mut self,
407 extension_id: Arc<str>,
408 version: Arc<str>,
409 cx: &mut ModelContext<Self>,
410 ) {
411 self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Upgrade, cx)
412 }
413
414 fn install_or_upgrade_extension(
415 &mut self,
416 extension_id: Arc<str>,
417 version: Arc<str>,
418 operation: ExtensionOperation,
419 cx: &mut ModelContext<Self>,
420 ) {
421 log::info!("installing extension {extension_id} {version}");
422 let url = self
423 .http_client
424 .build_zed_api_url(&format!("/extensions/{extension_id}/{version}/download"));
425
426 let extensions_dir = self.extensions_dir();
427 let http_client = self.http_client.clone();
428
429 match self.outstanding_operations.entry(extension_id.clone()) {
430 hash_map::Entry::Occupied(_) => return,
431 hash_map::Entry::Vacant(e) => e.insert(operation),
432 };
433
434 cx.spawn(move |this, mut cx| async move {
435 let _finish = util::defer({
436 let this = this.clone();
437 let mut cx = cx.clone();
438 let extension_id = extension_id.clone();
439 move || {
440 this.update(&mut cx, |this, cx| {
441 this.outstanding_operations.remove(extension_id.as_ref());
442 cx.notify();
443 })
444 .ok();
445 }
446 });
447
448 let mut response = http_client
449 .get(&url, Default::default(), true)
450 .await
451 .map_err(|err| anyhow!("error downloading extension: {}", err))?;
452 let decompressed_bytes = GzipDecoder::new(BufReader::new(response.body_mut()));
453 let archive = Archive::new(decompressed_bytes);
454 archive
455 .unpack(extensions_dir.join(extension_id.as_ref()))
456 .await?;
457 this.update(&mut cx, |this, cx| this.reload(Some(extension_id), cx))?
458 .await;
459 anyhow::Ok(())
460 })
461 .detach_and_log_err(cx);
462 }
463
464 pub fn uninstall_extension(&mut self, extension_id: Arc<str>, cx: &mut ModelContext<Self>) {
465 let extensions_dir = self.extensions_dir();
466 let fs = self.fs.clone();
467
468 match self.outstanding_operations.entry(extension_id.clone()) {
469 hash_map::Entry::Occupied(_) => return,
470 hash_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove),
471 };
472
473 cx.spawn(move |this, mut cx| async move {
474 let _finish = util::defer({
475 let this = this.clone();
476 let mut cx = cx.clone();
477 let extension_id = extension_id.clone();
478 move || {
479 this.update(&mut cx, |this, cx| {
480 this.outstanding_operations.remove(extension_id.as_ref());
481 cx.notify();
482 })
483 .ok();
484 }
485 });
486
487 fs.remove_dir(
488 &extensions_dir.join(extension_id.as_ref()),
489 RemoveOptions {
490 recursive: true,
491 ignore_if_not_exists: true,
492 },
493 )
494 .await?;
495
496 this.update(&mut cx, |this, cx| this.reload(None, cx))?
497 .await;
498 anyhow::Ok(())
499 })
500 .detach_and_log_err(cx)
501 }
502
503 pub fn install_dev_extension(
504 &mut self,
505 extension_source_path: PathBuf,
506 cx: &mut ModelContext<Self>,
507 ) -> Task<Result<()>> {
508 let extensions_dir = self.extensions_dir();
509 let fs = self.fs.clone();
510 let builder = self.builder.clone();
511
512 cx.spawn(move |this, mut cx| async move {
513 let extension_manifest =
514 Self::load_extension_manifest(fs.clone(), &extension_source_path).await?;
515 let extension_id = extension_manifest.id.clone();
516
517 if !this.update(&mut cx, |this, cx| {
518 match this.outstanding_operations.entry(extension_id.clone()) {
519 hash_map::Entry::Occupied(_) => return false,
520 hash_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove),
521 };
522 cx.notify();
523 true
524 })? {
525 return Ok(());
526 }
527
528 let _finish = util::defer({
529 let this = this.clone();
530 let mut cx = cx.clone();
531 let extension_id = extension_id.clone();
532 move || {
533 this.update(&mut cx, |this, cx| {
534 this.outstanding_operations.remove(extension_id.as_ref());
535 cx.notify();
536 })
537 .ok();
538 }
539 });
540
541 cx.background_executor()
542 .spawn({
543 let extension_source_path = extension_source_path.clone();
544 async move {
545 builder
546 .compile_extension(
547 &extension_source_path,
548 CompileExtensionOptions { release: false },
549 )
550 .await
551 }
552 })
553 .await?;
554
555 let output_path = &extensions_dir.join(extension_id.as_ref());
556 if let Some(metadata) = fs.metadata(&output_path).await? {
557 if metadata.is_symlink {
558 fs.remove_file(
559 &output_path,
560 RemoveOptions {
561 recursive: false,
562 ignore_if_not_exists: true,
563 },
564 )
565 .await?;
566 } else {
567 bail!("extension {extension_id} is already installed");
568 }
569 }
570
571 fs.create_symlink(output_path, extension_source_path)
572 .await?;
573
574 this.update(&mut cx, |this, cx| this.reload(None, cx))?
575 .await;
576 Ok(())
577 })
578 }
579
580 pub fn rebuild_dev_extension(&mut self, extension_id: Arc<str>, cx: &mut ModelContext<Self>) {
581 let path = self.installed_dir.join(extension_id.as_ref());
582 let builder = self.builder.clone();
583
584 match self.outstanding_operations.entry(extension_id.clone()) {
585 hash_map::Entry::Occupied(_) => return,
586 hash_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Upgrade),
587 };
588
589 cx.notify();
590 let compile = cx.background_executor().spawn(async move {
591 builder
592 .compile_extension(&path, CompileExtensionOptions { release: true })
593 .await
594 });
595
596 cx.spawn(|this, mut cx| async move {
597 let result = compile.await;
598
599 this.update(&mut cx, |this, cx| {
600 this.outstanding_operations.remove(&extension_id);
601 cx.notify();
602 })?;
603
604 if result.is_ok() {
605 this.update(&mut cx, |this, cx| this.reload(Some(extension_id), cx))?
606 .await;
607 }
608
609 result
610 })
611 .detach_and_log_err(cx)
612 }
613
614 /// Updates the set of installed extensions.
615 ///
616 /// First, this unloads any themes, languages, or grammars that are
617 /// no longer in the manifest, or whose files have changed on disk.
618 /// Then it loads any themes, languages, or grammars that are newly
619 /// added to the manifest, or whose files have changed on disk.
620 fn extensions_updated(
621 &mut self,
622 new_index: ExtensionIndex,
623 cx: &mut ModelContext<Self>,
624 ) -> Task<()> {
625 let old_index = &self.extension_index;
626
627 // Determine which extensions need to be loaded and unloaded, based
628 // on the changes to the manifest and the extensions that we know have been
629 // modified.
630 let mut extensions_to_unload = Vec::default();
631 let mut extensions_to_load = Vec::default();
632 {
633 let mut old_keys = old_index.extensions.iter().peekable();
634 let mut new_keys = new_index.extensions.iter().peekable();
635 loop {
636 match (old_keys.peek(), new_keys.peek()) {
637 (None, None) => break,
638 (None, Some(_)) => {
639 extensions_to_load.push(new_keys.next().unwrap().0.clone());
640 }
641 (Some(_), None) => {
642 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
643 }
644 (Some((old_key, _)), Some((new_key, _))) => match old_key.cmp(&new_key) {
645 Ordering::Equal => {
646 let (old_key, old_value) = old_keys.next().unwrap();
647 let (new_key, new_value) = new_keys.next().unwrap();
648 if old_value != new_value || self.modified_extensions.contains(old_key)
649 {
650 extensions_to_unload.push(old_key.clone());
651 extensions_to_load.push(new_key.clone());
652 }
653 }
654 Ordering::Less => {
655 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
656 }
657 Ordering::Greater => {
658 extensions_to_load.push(new_keys.next().unwrap().0.clone());
659 }
660 },
661 }
662 }
663 self.modified_extensions.clear();
664 }
665
666 if extensions_to_load.is_empty() && extensions_to_unload.is_empty() {
667 return Task::ready(());
668 }
669
670 let reload_count = extensions_to_unload
671 .iter()
672 .filter(|id| extensions_to_load.contains(id))
673 .count();
674
675 log::info!(
676 "extensions updated. loading {}, reloading {}, unloading {}",
677 extensions_to_load.len() - reload_count,
678 reload_count,
679 extensions_to_unload.len() - reload_count
680 );
681
682 let themes_to_remove = old_index
683 .themes
684 .iter()
685 .filter_map(|(name, entry)| {
686 if extensions_to_unload.contains(&entry.extension) {
687 Some(name.clone().into())
688 } else {
689 None
690 }
691 })
692 .collect::<Vec<_>>();
693 let languages_to_remove = old_index
694 .languages
695 .iter()
696 .filter_map(|(name, entry)| {
697 if extensions_to_unload.contains(&entry.extension) {
698 Some(name.clone())
699 } else {
700 None
701 }
702 })
703 .collect::<Vec<_>>();
704 let mut grammars_to_remove = Vec::new();
705 for extension_id in &extensions_to_unload {
706 let Some(extension) = old_index.extensions.get(extension_id) else {
707 continue;
708 };
709 grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
710 for (language_server_name, config) in extension.manifest.language_servers.iter() {
711 self.language_registry
712 .remove_lsp_adapter(config.language.as_ref(), language_server_name);
713 }
714 }
715
716 self.wasm_extensions
717 .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
718 self.theme_registry.remove_user_themes(&themes_to_remove);
719 self.language_registry
720 .remove_languages(&languages_to_remove, &grammars_to_remove);
721
722 let languages_to_add = new_index
723 .languages
724 .iter()
725 .filter(|(_, entry)| extensions_to_load.contains(&entry.extension))
726 .collect::<Vec<_>>();
727 let mut grammars_to_add = Vec::new();
728 let mut themes_to_add = Vec::new();
729 for extension_id in &extensions_to_load {
730 let Some(extension) = new_index.extensions.get(extension_id) else {
731 continue;
732 };
733
734 grammars_to_add.extend(extension.manifest.grammars.keys().map(|grammar_name| {
735 let mut grammar_path = self.installed_dir.clone();
736 grammar_path.extend([extension_id.as_ref(), "grammars"]);
737 grammar_path.push(grammar_name.as_ref());
738 grammar_path.set_extension("wasm");
739 (grammar_name.clone(), grammar_path)
740 }));
741 themes_to_add.extend(extension.manifest.themes.iter().map(|theme_path| {
742 let mut path = self.installed_dir.clone();
743 path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
744 path
745 }));
746 }
747
748 self.language_registry
749 .register_wasm_grammars(grammars_to_add);
750
751 for (language_name, language) in languages_to_add {
752 let mut language_path = self.installed_dir.clone();
753 language_path.extend([
754 Path::new(language.extension.as_ref()),
755 language.path.as_path(),
756 ]);
757 self.language_registry.register_language(
758 language_name.clone(),
759 language.grammar.clone(),
760 language.matcher.clone(),
761 None,
762 move || {
763 let config = std::fs::read_to_string(language_path.join("config.toml"))?;
764 let config: LanguageConfig = ::toml::from_str(&config)?;
765 let queries = load_plugin_queries(&language_path);
766 Ok((config, queries))
767 },
768 );
769 }
770
771 let fs = self.fs.clone();
772 let wasm_host = self.wasm_host.clone();
773 let root_dir = self.installed_dir.clone();
774 let theme_registry = self.theme_registry.clone();
775 let extension_entries = extensions_to_load
776 .iter()
777 .filter_map(|name| new_index.extensions.get(name).cloned())
778 .collect::<Vec<_>>();
779
780 self.extension_index = new_index;
781 cx.notify();
782 cx.emit(Event::ExtensionsUpdated);
783
784 cx.spawn(|this, mut cx| async move {
785 cx.background_executor()
786 .spawn({
787 let fs = fs.clone();
788 async move {
789 for theme_path in &themes_to_add {
790 theme_registry
791 .load_user_theme(&theme_path, fs.clone())
792 .await
793 .log_err();
794 }
795 }
796 })
797 .await;
798
799 let mut wasm_extensions = Vec::new();
800 for extension in extension_entries {
801 if extension.manifest.lib.kind.is_none() {
802 continue;
803 };
804
805 let mut path = root_dir.clone();
806 path.extend([extension.manifest.id.as_ref(), "extension.wasm"]);
807 let Some(mut wasm_file) = fs
808 .open_sync(&path)
809 .await
810 .context("failed to open wasm file")
811 .log_err()
812 else {
813 continue;
814 };
815
816 let mut wasm_bytes = Vec::new();
817 if wasm_file
818 .read_to_end(&mut wasm_bytes)
819 .context("failed to read wasm")
820 .log_err()
821 .is_none()
822 {
823 continue;
824 }
825
826 let Some(wasm_extension) = wasm_host
827 .load_extension(
828 wasm_bytes,
829 extension.manifest.clone(),
830 cx.background_executor().clone(),
831 )
832 .await
833 .context("failed to load wasm extension")
834 .log_err()
835 else {
836 continue;
837 };
838
839 wasm_extensions.push((extension.manifest.clone(), wasm_extension));
840 }
841
842 this.update(&mut cx, |this, cx| {
843 this.reload_complete_senders.clear();
844
845 for (manifest, wasm_extension) in &wasm_extensions {
846 for (language_server_name, language_server_config) in &manifest.language_servers
847 {
848 this.language_registry.register_lsp_adapter(
849 language_server_config.language.clone(),
850 Arc::new(ExtensionLspAdapter {
851 extension: wasm_extension.clone(),
852 host: this.wasm_host.clone(),
853 config: wit::LanguageServerConfig {
854 name: language_server_name.0.to_string(),
855 language_name: language_server_config.language.to_string(),
856 },
857 }),
858 );
859 }
860 }
861 this.wasm_extensions.extend(wasm_extensions);
862 ThemeSettings::reload_current_theme(cx)
863 })
864 .ok();
865 })
866 }
867
868 fn rebuild_extension_index(&self, cx: &mut ModelContext<Self>) -> Task<ExtensionIndex> {
869 let fs = self.fs.clone();
870 let work_dir = self.wasm_host.work_dir.clone();
871 let extensions_dir = self.installed_dir.clone();
872 let index_path = self.index_path.clone();
873 cx.background_executor().spawn(async move {
874 let start_time = Instant::now();
875 let mut index = ExtensionIndex::default();
876
877 fs.create_dir(&work_dir).await.log_err();
878 fs.create_dir(&extensions_dir).await.log_err();
879
880 let extension_paths = fs.read_dir(&extensions_dir).await;
881 if let Ok(mut extension_paths) = extension_paths {
882 while let Some(extension_dir) = extension_paths.next().await {
883 let Ok(extension_dir) = extension_dir else {
884 continue;
885 };
886 Self::add_extension_to_index(fs.clone(), extension_dir, &mut index)
887 .await
888 .log_err();
889 }
890 }
891
892 if let Ok(index_json) = serde_json::to_string_pretty(&index) {
893 fs.save(&index_path, &index_json.as_str().into(), Default::default())
894 .await
895 .context("failed to save extension index")
896 .log_err();
897 }
898
899 log::info!("rebuilt extension index in {:?}", start_time.elapsed());
900 index
901 })
902 }
903
904 async fn add_extension_to_index(
905 fs: Arc<dyn Fs>,
906 extension_dir: PathBuf,
907 index: &mut ExtensionIndex,
908 ) -> Result<()> {
909 let mut extension_manifest =
910 Self::load_extension_manifest(fs.clone(), &extension_dir).await?;
911 let extension_id = extension_manifest.id.clone();
912
913 // TODO: distinguish dev extensions more explicitly, by the absence
914 // of a checksum file that we'll create when downloading normal extensions.
915 let is_dev = fs
916 .metadata(&extension_dir)
917 .await?
918 .ok_or_else(|| anyhow!("directory does not exist"))?
919 .is_symlink;
920
921 if let Ok(mut language_paths) = fs.read_dir(&extension_dir.join("languages")).await {
922 while let Some(language_path) = language_paths.next().await {
923 let language_path = language_path?;
924 let Ok(relative_path) = language_path.strip_prefix(&extension_dir) else {
925 continue;
926 };
927 let Ok(Some(fs_metadata)) = fs.metadata(&language_path).await else {
928 continue;
929 };
930 if !fs_metadata.is_dir {
931 continue;
932 }
933 let config = fs.load(&language_path.join("config.toml")).await?;
934 let config = ::toml::from_str::<LanguageConfig>(&config)?;
935
936 let relative_path = relative_path.to_path_buf();
937 if !extension_manifest.languages.contains(&relative_path) {
938 extension_manifest.languages.push(relative_path.clone());
939 }
940
941 index.languages.insert(
942 config.name.clone(),
943 ExtensionIndexLanguageEntry {
944 extension: extension_id.clone(),
945 path: relative_path,
946 matcher: config.matcher,
947 grammar: config.grammar,
948 },
949 );
950 }
951 }
952
953 if let Ok(mut theme_paths) = fs.read_dir(&extension_dir.join("themes")).await {
954 while let Some(theme_path) = theme_paths.next().await {
955 let theme_path = theme_path?;
956 let Ok(relative_path) = theme_path.strip_prefix(&extension_dir) else {
957 continue;
958 };
959
960 let Some(theme_family) = ThemeRegistry::read_user_theme(&theme_path, fs.clone())
961 .await
962 .log_err()
963 else {
964 continue;
965 };
966
967 let relative_path = relative_path.to_path_buf();
968 if !extension_manifest.themes.contains(&relative_path) {
969 extension_manifest.themes.push(relative_path.clone());
970 }
971
972 for theme in theme_family.themes {
973 index.themes.insert(
974 theme.name.into(),
975 ExtensionIndexThemeEntry {
976 extension: extension_id.clone(),
977 path: relative_path.clone(),
978 },
979 );
980 }
981 }
982 }
983
984 let extension_wasm_path = extension_dir.join("extension.wasm");
985 if fs.is_file(&extension_wasm_path).await {
986 extension_manifest
987 .lib
988 .kind
989 .get_or_insert(ExtensionLibraryKind::Rust);
990 }
991
992 index.extensions.insert(
993 extension_id.clone(),
994 ExtensionIndexEntry {
995 dev: is_dev,
996 manifest: Arc::new(extension_manifest),
997 },
998 );
999
1000 Ok(())
1001 }
1002
1003 async fn load_extension_manifest(
1004 fs: Arc<dyn Fs>,
1005 extension_dir: &Path,
1006 ) -> Result<ExtensionManifest> {
1007 let extension_name = extension_dir
1008 .file_name()
1009 .and_then(OsStr::to_str)
1010 .ok_or_else(|| anyhow!("invalid extension name"))?;
1011
1012 let mut extension_manifest_path = extension_dir.join("extension.json");
1013 if fs.is_file(&extension_manifest_path).await {
1014 let manifest_content = fs
1015 .load(&extension_manifest_path)
1016 .await
1017 .with_context(|| format!("failed to load {extension_name} extension.json"))?;
1018 let manifest_json = serde_json::from_str::<OldExtensionManifest>(&manifest_content)
1019 .with_context(|| {
1020 format!("invalid extension.json for extension {extension_name}")
1021 })?;
1022
1023 Ok(manifest_from_old_manifest(manifest_json, extension_name))
1024 } else {
1025 extension_manifest_path.set_extension("toml");
1026 let manifest_content = fs
1027 .load(&extension_manifest_path)
1028 .await
1029 .with_context(|| format!("failed to load {extension_name} extension.toml"))?;
1030 toml::from_str(&manifest_content)
1031 .with_context(|| format!("invalid extension.json for extension {extension_name}"))
1032 }
1033 }
1034}
1035
1036fn manifest_from_old_manifest(
1037 manifest_json: OldExtensionManifest,
1038 extension_id: &str,
1039) -> ExtensionManifest {
1040 ExtensionManifest {
1041 id: extension_id.into(),
1042 name: manifest_json.name,
1043 version: manifest_json.version,
1044 description: manifest_json.description,
1045 repository: manifest_json.repository,
1046 authors: manifest_json.authors,
1047 lib: Default::default(),
1048 themes: {
1049 let mut themes = manifest_json.themes.into_values().collect::<Vec<_>>();
1050 themes.sort();
1051 themes.dedup();
1052 themes
1053 },
1054 languages: {
1055 let mut languages = manifest_json.languages.into_values().collect::<Vec<_>>();
1056 languages.sort();
1057 languages.dedup();
1058 languages
1059 },
1060 grammars: manifest_json
1061 .grammars
1062 .into_keys()
1063 .map(|grammar_name| (grammar_name, Default::default()))
1064 .collect(),
1065 language_servers: Default::default(),
1066 }
1067}
1068
1069fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
1070 let mut result = LanguageQueries::default();
1071 if let Some(entries) = std::fs::read_dir(root_path).log_err() {
1072 for entry in entries {
1073 let Some(entry) = entry.log_err() else {
1074 continue;
1075 };
1076 let path = entry.path();
1077 if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
1078 if !remainder.ends_with(".scm") {
1079 continue;
1080 }
1081 for (name, query) in QUERY_FILENAME_PREFIXES {
1082 if remainder.starts_with(name) {
1083 if let Some(contents) = std::fs::read_to_string(&path).log_err() {
1084 match query(&mut result) {
1085 None => *query(&mut result) = Some(contents.into()),
1086 Some(r) => r.to_mut().push_str(contents.as_ref()),
1087 }
1088 }
1089 break;
1090 }
1091 }
1092 }
1093 }
1094 }
1095 result
1096}