1pub mod extension_lsp_adapter;
2pub mod extension_settings;
3pub mod wasm_host;
4
5use crate::{extension_lsp_adapter::ExtensionLspAdapter, wasm_host::wit};
6use anyhow::{anyhow, bail, Context as _, Result};
7use async_compression::futures::bufread::GzipDecoder;
8use async_tar::Archive;
9use client::{telemetry::Telemetry, Client, ExtensionMetadata, GetExtensionsResponse};
10use collections::{btree_map, BTreeMap, HashSet};
11use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
12pub use extension::ExtensionManifest;
13use fs::{Fs, RemoveOptions};
14use futures::{
15 channel::{
16 mpsc::{unbounded, UnboundedSender},
17 oneshot,
18 },
19 io::BufReader,
20 select_biased, AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
21};
22use gpui::{
23 actions, AppContext, AsyncAppContext, Context, EventEmitter, Global, Model, ModelContext,
24 SharedString, Task, WeakModel,
25};
26use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
27use language::{
28 LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
29 QUERY_FILENAME_PREFIXES,
30};
31use node_runtime::NodeRuntime;
32use project::ContextProviderWithTasks;
33use release_channel::ReleaseChannel;
34use semantic_version::SemanticVersion;
35use serde::{Deserialize, Serialize};
36use settings::Settings;
37use std::ops::RangeInclusive;
38use std::str::FromStr;
39use std::{
40 cmp::Ordering,
41 path::{self, Path, PathBuf},
42 sync::Arc,
43 time::{Duration, Instant},
44};
45use url::Url;
46use util::ResultExt;
47use wasm_host::{
48 wit::{is_supported_wasm_api_version, wasm_api_version_range},
49 WasmExtension, WasmHost,
50};
51
52pub use extension::{
53 ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion,
54};
55pub use extension_settings::ExtensionSettings;
56
57pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
58const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
59
60/// The current extension [`SchemaVersion`] supported by Zed.
61const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1);
62
63/// Returns the [`SchemaVersion`] range that is compatible with this version of Zed.
64pub fn schema_version_range() -> RangeInclusive<SchemaVersion> {
65 SchemaVersion::ZERO..=CURRENT_SCHEMA_VERSION
66}
67
68/// Returns whether the given extension version is compatible with this version of Zed.
69pub fn is_version_compatible(
70 release_channel: ReleaseChannel,
71 extension_version: &ExtensionMetadata,
72) -> bool {
73 let schema_version = extension_version.manifest.schema_version.unwrap_or(0);
74 if CURRENT_SCHEMA_VERSION.0 < schema_version {
75 return false;
76 }
77
78 if let Some(wasm_api_version) = extension_version
79 .manifest
80 .wasm_api_version
81 .as_ref()
82 .and_then(|wasm_api_version| SemanticVersion::from_str(wasm_api_version).ok())
83 {
84 if !is_supported_wasm_api_version(release_channel, wasm_api_version) {
85 return false;
86 }
87 }
88
89 true
90}
91
92pub trait DocsDatabase: Send + Sync + 'static {
93 fn insert(&self, key: String, docs: String) -> Task<Result<()>>;
94}
95
96pub trait ExtensionRegistrationHooks: Send + Sync + 'static {
97 fn remove_user_themes(&self, _themes: Vec<SharedString>) {}
98
99 fn load_user_theme(&self, _theme_path: PathBuf, _fs: Arc<dyn Fs>) -> Task<Result<()>> {
100 Task::ready(Ok(()))
101 }
102
103 fn list_theme_names(
104 &self,
105 _theme_path: PathBuf,
106 _fs: Arc<dyn Fs>,
107 ) -> Task<Result<Vec<String>>> {
108 Task::ready(Ok(Vec::new()))
109 }
110
111 fn reload_current_theme(&self, _cx: &mut AppContext) {}
112
113 fn register_language(
114 &self,
115 _language: LanguageName,
116 _grammar: Option<Arc<str>>,
117 _matcher: language::LanguageMatcher,
118 _load: Arc<dyn Fn() -> Result<LoadedLanguage> + 'static + Send + Sync>,
119 ) {
120 }
121
122 fn register_lsp_adapter(&self, _language: LanguageName, _adapter: ExtensionLspAdapter) {}
123
124 fn remove_lsp_adapter(
125 &self,
126 _language: &LanguageName,
127 _server_name: &language::LanguageServerName,
128 ) {
129 }
130
131 fn register_wasm_grammars(&self, _grammars: Vec<(Arc<str>, PathBuf)>) {}
132
133 fn remove_languages(
134 &self,
135 _languages_to_remove: &[LanguageName],
136 _grammars_to_remove: &[Arc<str>],
137 ) {
138 }
139
140 fn register_slash_command(
141 &self,
142 _slash_command: wit::SlashCommand,
143 _extension: WasmExtension,
144 _host: Arc<WasmHost>,
145 ) {
146 }
147
148 fn register_docs_provider(
149 &self,
150 _extension: WasmExtension,
151 _host: Arc<WasmHost>,
152 _provider_id: Arc<str>,
153 ) {
154 }
155
156 fn register_snippets(&self, _path: &PathBuf, _snippet_contents: &str) -> Result<()> {
157 Ok(())
158 }
159
160 fn update_lsp_status(
161 &self,
162 _server_name: language::LanguageServerName,
163 _status: language::LanguageServerBinaryStatus,
164 ) {
165 }
166}
167
168pub struct ExtensionStore {
169 pub registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
170 pub builder: Arc<ExtensionBuilder>,
171 pub extension_index: ExtensionIndex,
172 pub fs: Arc<dyn Fs>,
173 pub http_client: Arc<HttpClientWithUrl>,
174 pub telemetry: Option<Arc<Telemetry>>,
175 pub reload_tx: UnboundedSender<Option<Arc<str>>>,
176 pub reload_complete_senders: Vec<oneshot::Sender<()>>,
177 pub installed_dir: PathBuf,
178 pub outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
179 pub index_path: PathBuf,
180 pub modified_extensions: HashSet<Arc<str>>,
181 pub wasm_host: Arc<WasmHost>,
182 pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
183 pub tasks: Vec<Task<()>>,
184}
185
186#[derive(Clone, Copy)]
187pub enum ExtensionOperation {
188 Upgrade,
189 Install,
190 Remove,
191}
192
193#[derive(Clone)]
194pub enum Event {
195 ExtensionsUpdated,
196 StartedReloading,
197 ExtensionInstalled(Arc<str>),
198 ExtensionFailedToLoad(Arc<str>),
199}
200
201impl EventEmitter<Event> for ExtensionStore {}
202
203struct GlobalExtensionStore(Model<ExtensionStore>);
204
205impl Global for GlobalExtensionStore {}
206
207#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
208pub struct ExtensionIndex {
209 pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
210 pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
211 pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,
212}
213
214#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
215pub struct ExtensionIndexEntry {
216 pub manifest: Arc<ExtensionManifest>,
217 pub dev: bool,
218}
219
220#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
221pub struct ExtensionIndexThemeEntry {
222 pub extension: Arc<str>,
223 pub path: PathBuf,
224}
225
226#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
227pub struct ExtensionIndexLanguageEntry {
228 pub extension: Arc<str>,
229 pub path: PathBuf,
230 pub matcher: LanguageMatcher,
231 pub grammar: Option<Arc<str>>,
232}
233
234actions!(zed, [ReloadExtensions]);
235
236pub fn init(
237 registration_hooks: Arc<dyn ExtensionRegistrationHooks>,
238 fs: Arc<dyn Fs>,
239 client: Arc<Client>,
240 node_runtime: NodeRuntime,
241 cx: &mut AppContext,
242) {
243 ExtensionSettings::register(cx);
244
245 let store = cx.new_model(move |cx| {
246 ExtensionStore::new(
247 paths::extensions_dir().clone(),
248 None,
249 registration_hooks,
250 fs,
251 client.http_client().clone(),
252 client.http_client().clone(),
253 Some(client.telemetry().clone()),
254 node_runtime,
255 cx,
256 )
257 });
258
259 cx.on_action(|_: &ReloadExtensions, cx| {
260 let store = cx.global::<GlobalExtensionStore>().0.clone();
261 store.update(cx, |store, cx| drop(store.reload(None, cx)));
262 });
263
264 cx.set_global(GlobalExtensionStore(store));
265}
266
267impl ExtensionStore {
268 pub fn try_global(cx: &AppContext) -> Option<Model<Self>> {
269 cx.try_global::<GlobalExtensionStore>()
270 .map(|store| store.0.clone())
271 }
272
273 pub fn global(cx: &AppContext) -> Model<Self> {
274 cx.global::<GlobalExtensionStore>().0.clone()
275 }
276
277 #[allow(clippy::too_many_arguments)]
278 pub fn new(
279 extensions_dir: PathBuf,
280 build_dir: Option<PathBuf>,
281 extension_api: Arc<dyn ExtensionRegistrationHooks>,
282 fs: Arc<dyn Fs>,
283 http_client: Arc<HttpClientWithUrl>,
284 builder_client: Arc<dyn HttpClient>,
285 telemetry: Option<Arc<Telemetry>>,
286 node_runtime: NodeRuntime,
287 cx: &mut ModelContext<Self>,
288 ) -> Self {
289 let work_dir = extensions_dir.join("work");
290 let build_dir = build_dir.unwrap_or_else(|| extensions_dir.join("build"));
291 let installed_dir = extensions_dir.join("installed");
292 let index_path = extensions_dir.join("index.json");
293
294 let (reload_tx, mut reload_rx) = unbounded();
295 let mut this = Self {
296 registration_hooks: extension_api.clone(),
297 extension_index: Default::default(),
298 installed_dir,
299 index_path,
300 builder: Arc::new(ExtensionBuilder::new(builder_client, build_dir)),
301 outstanding_operations: Default::default(),
302 modified_extensions: Default::default(),
303 reload_complete_senders: Vec::new(),
304 wasm_host: WasmHost::new(
305 fs.clone(),
306 http_client.clone(),
307 node_runtime,
308 extension_api,
309 work_dir,
310 cx,
311 ),
312 wasm_extensions: Vec::new(),
313 fs,
314 http_client,
315 telemetry,
316 reload_tx,
317 tasks: Vec::new(),
318 };
319
320 // The extensions store maintains an index file, which contains a complete
321 // list of the installed extensions and the resources that they provide.
322 // This index is loaded synchronously on startup.
323 let (index_content, index_metadata, extensions_metadata) =
324 cx.background_executor().block(async {
325 futures::join!(
326 this.fs.load(&this.index_path),
327 this.fs.metadata(&this.index_path),
328 this.fs.metadata(&this.installed_dir),
329 )
330 });
331
332 // Normally, there is no need to rebuild the index. But if the index file
333 // is invalid or is out-of-date according to the filesystem mtimes, then
334 // it must be asynchronously rebuilt.
335 let mut extension_index = ExtensionIndex::default();
336 let mut extension_index_needs_rebuild = true;
337 if let Ok(index_content) = index_content {
338 if let Some(index) = serde_json::from_str(&index_content).log_err() {
339 extension_index = index;
340 if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) =
341 (index_metadata, extensions_metadata)
342 {
343 if index_metadata.mtime > extensions_metadata.mtime {
344 extension_index_needs_rebuild = false;
345 }
346 }
347 }
348 }
349
350 // Immediately load all of the extensions in the initial manifest. If the
351 // index needs to be rebuild, then enqueue
352 let load_initial_extensions = this.extensions_updated(extension_index, cx);
353 let mut reload_future = None;
354 if extension_index_needs_rebuild {
355 reload_future = Some(this.reload(None, cx));
356 }
357
358 cx.spawn(|this, mut cx| async move {
359 if let Some(future) = reload_future {
360 future.await;
361 }
362 this.update(&mut cx, |this, cx| this.auto_install_extensions(cx))
363 .ok();
364 this.update(&mut cx, |this, cx| this.check_for_updates(cx))
365 .ok();
366 })
367 .detach();
368
369 // Perform all extension loading in a single task to ensure that we
370 // never attempt to simultaneously load/unload extensions from multiple
371 // parallel tasks.
372 this.tasks.push(cx.spawn(|this, mut cx| {
373 async move {
374 load_initial_extensions.await;
375
376 let mut index_changed = false;
377 let mut debounce_timer = cx
378 .background_executor()
379 .spawn(futures::future::pending())
380 .fuse();
381 loop {
382 select_biased! {
383 _ = debounce_timer => {
384 if index_changed {
385 let index = this
386 .update(&mut cx, |this, cx| this.rebuild_extension_index(cx))?
387 .await;
388 this.update(&mut cx, |this, cx| this.extensions_updated(index, cx))?
389 .await;
390 index_changed = false;
391 }
392 }
393 extension_id = reload_rx.next() => {
394 let Some(extension_id) = extension_id else { break; };
395 this.update(&mut cx, |this, _| {
396 this.modified_extensions.extend(extension_id);
397 })?;
398 index_changed = true;
399 debounce_timer = cx
400 .background_executor()
401 .timer(RELOAD_DEBOUNCE_DURATION)
402 .fuse();
403 }
404 }
405 }
406
407 anyhow::Ok(())
408 }
409 .map(drop)
410 }));
411
412 // Watch the installed extensions directory for changes. Whenever changes are
413 // detected, rebuild the extension index, and load/unload any extensions that
414 // have been added, removed, or modified.
415 this.tasks.push(cx.background_executor().spawn({
416 let fs = this.fs.clone();
417 let reload_tx = this.reload_tx.clone();
418 let installed_dir = this.installed_dir.clone();
419 async move {
420 let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
421 while let Some(events) = paths.next().await {
422 for event in events {
423 let Ok(event_path) = event.path.strip_prefix(&installed_dir) else {
424 continue;
425 };
426
427 if let Some(path::Component::Normal(extension_dir_name)) =
428 event_path.components().next()
429 {
430 if let Some(extension_id) = extension_dir_name.to_str() {
431 reload_tx.unbounded_send(Some(extension_id.into())).ok();
432 }
433 }
434 }
435 }
436 }
437 }));
438
439 this
440 }
441
442 pub fn reload(
443 &mut self,
444 modified_extension: Option<Arc<str>>,
445 cx: &mut ModelContext<Self>,
446 ) -> impl Future<Output = ()> {
447 let (tx, rx) = oneshot::channel();
448 self.reload_complete_senders.push(tx);
449 self.reload_tx
450 .unbounded_send(modified_extension)
451 .expect("reload task exited");
452 cx.emit(Event::StartedReloading);
453
454 async move {
455 rx.await.ok();
456 }
457 }
458
459 fn extensions_dir(&self) -> PathBuf {
460 self.installed_dir.clone()
461 }
462
463 pub fn outstanding_operations(&self) -> &BTreeMap<Arc<str>, ExtensionOperation> {
464 &self.outstanding_operations
465 }
466
467 pub fn installed_extensions(&self) -> &BTreeMap<Arc<str>, ExtensionIndexEntry> {
468 &self.extension_index.extensions
469 }
470
471 pub fn dev_extensions(&self) -> impl Iterator<Item = &Arc<ExtensionManifest>> {
472 self.extension_index
473 .extensions
474 .values()
475 .filter_map(|extension| extension.dev.then_some(&extension.manifest))
476 }
477
478 /// Returns the names of themes provided by extensions.
479 pub fn extension_themes<'a>(
480 &'a self,
481 extension_id: &'a str,
482 ) -> impl Iterator<Item = &'a Arc<str>> {
483 self.extension_index
484 .themes
485 .iter()
486 .filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
487 }
488
489 pub fn fetch_extensions(
490 &self,
491 search: Option<&str>,
492 cx: &mut ModelContext<Self>,
493 ) -> Task<Result<Vec<ExtensionMetadata>>> {
494 let version = CURRENT_SCHEMA_VERSION.to_string();
495 let mut query = vec![("max_schema_version", version.as_str())];
496 if let Some(search) = search {
497 query.push(("filter", search));
498 }
499
500 self.fetch_extensions_from_api("/extensions", &query, cx)
501 }
502
503 pub fn fetch_extensions_with_update_available(
504 &mut self,
505 cx: &mut ModelContext<Self>,
506 ) -> Task<Result<Vec<ExtensionMetadata>>> {
507 let schema_versions = schema_version_range();
508 let wasm_api_versions = wasm_api_version_range(ReleaseChannel::global(cx));
509 let extension_settings = ExtensionSettings::get_global(cx);
510 let extension_ids = self
511 .extension_index
512 .extensions
513 .iter()
514 .filter(|(id, entry)| !entry.dev && extension_settings.should_auto_update(id))
515 .map(|(id, _)| id.as_ref())
516 .collect::<Vec<_>>()
517 .join(",");
518 let task = self.fetch_extensions_from_api(
519 "/extensions/updates",
520 &[
521 ("min_schema_version", &schema_versions.start().to_string()),
522 ("max_schema_version", &schema_versions.end().to_string()),
523 (
524 "min_wasm_api_version",
525 &wasm_api_versions.start().to_string(),
526 ),
527 ("max_wasm_api_version", &wasm_api_versions.end().to_string()),
528 ("ids", &extension_ids),
529 ],
530 cx,
531 );
532 cx.spawn(move |this, mut cx| async move {
533 let extensions = task.await?;
534 this.update(&mut cx, |this, _cx| {
535 extensions
536 .into_iter()
537 .filter(|extension| {
538 this.extension_index.extensions.get(&extension.id).map_or(
539 true,
540 |installed_extension| {
541 installed_extension.manifest.version != extension.manifest.version
542 },
543 )
544 })
545 .collect()
546 })
547 })
548 }
549
550 pub fn fetch_extension_versions(
551 &self,
552 extension_id: &str,
553 cx: &mut ModelContext<Self>,
554 ) -> Task<Result<Vec<ExtensionMetadata>>> {
555 self.fetch_extensions_from_api(&format!("/extensions/{extension_id}"), &[], cx)
556 }
557
558 /// Installs any extensions that should be included with Zed by default.
559 ///
560 /// This can be used to make certain functionality provided by extensions
561 /// available out-of-the-box.
562 pub fn auto_install_extensions(&mut self, cx: &mut ModelContext<Self>) {
563 let extension_settings = ExtensionSettings::get_global(cx);
564
565 let extensions_to_install = extension_settings
566 .auto_install_extensions
567 .keys()
568 .filter(|extension_id| extension_settings.should_auto_install(extension_id))
569 .filter(|extension_id| {
570 let is_already_installed = self
571 .extension_index
572 .extensions
573 .contains_key(extension_id.as_ref());
574 !is_already_installed
575 })
576 .cloned()
577 .collect::<Vec<_>>();
578
579 cx.spawn(move |this, mut cx| async move {
580 for extension_id in extensions_to_install {
581 this.update(&mut cx, |this, cx| {
582 this.install_latest_extension(extension_id.clone(), cx);
583 })
584 .ok();
585 }
586 })
587 .detach();
588 }
589
590 pub fn check_for_updates(&mut self, cx: &mut ModelContext<Self>) {
591 let task = self.fetch_extensions_with_update_available(cx);
592 cx.spawn(move |this, mut cx| async move {
593 Self::upgrade_extensions(this, task.await?, &mut cx).await
594 })
595 .detach();
596 }
597
598 async fn upgrade_extensions(
599 this: WeakModel<Self>,
600 extensions: Vec<ExtensionMetadata>,
601 cx: &mut AsyncAppContext,
602 ) -> Result<()> {
603 for extension in extensions {
604 let task = this.update(cx, |this, cx| {
605 if let Some(installed_extension) =
606 this.extension_index.extensions.get(&extension.id)
607 {
608 let installed_version =
609 SemanticVersion::from_str(&installed_extension.manifest.version).ok()?;
610 let latest_version =
611 SemanticVersion::from_str(&extension.manifest.version).ok()?;
612
613 if installed_version >= latest_version {
614 return None;
615 }
616 }
617
618 Some(this.upgrade_extension(extension.id, extension.manifest.version, cx))
619 })?;
620
621 if let Some(task) = task {
622 task.await.log_err();
623 }
624 }
625 anyhow::Ok(())
626 }
627
628 fn fetch_extensions_from_api(
629 &self,
630 path: &str,
631 query: &[(&str, &str)],
632 cx: &mut ModelContext<'_, ExtensionStore>,
633 ) -> Task<Result<Vec<ExtensionMetadata>>> {
634 let url = self.http_client.build_zed_api_url(path, query);
635 let http_client = self.http_client.clone();
636 cx.spawn(move |_, _| async move {
637 let mut response = http_client
638 .get(url?.as_ref(), AsyncBody::empty(), true)
639 .await?;
640
641 let mut body = Vec::new();
642 response
643 .body_mut()
644 .read_to_end(&mut body)
645 .await
646 .context("error reading extensions")?;
647
648 if response.status().is_client_error() {
649 let text = String::from_utf8_lossy(body.as_slice());
650 bail!(
651 "status error {}, response: {text:?}",
652 response.status().as_u16()
653 );
654 }
655
656 let response: GetExtensionsResponse = serde_json::from_slice(&body)?;
657 Ok(response.data)
658 })
659 }
660
661 pub fn install_extension(
662 &mut self,
663 extension_id: Arc<str>,
664 version: Arc<str>,
665 cx: &mut ModelContext<Self>,
666 ) {
667 self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Install, cx)
668 .detach_and_log_err(cx);
669 }
670
671 fn install_or_upgrade_extension_at_endpoint(
672 &mut self,
673 extension_id: Arc<str>,
674 url: Url,
675 operation: ExtensionOperation,
676 cx: &mut ModelContext<Self>,
677 ) -> Task<Result<()>> {
678 let extension_dir = self.installed_dir.join(extension_id.as_ref());
679 let http_client = self.http_client.clone();
680 let fs = self.fs.clone();
681
682 match self.outstanding_operations.entry(extension_id.clone()) {
683 btree_map::Entry::Occupied(_) => return Task::ready(Ok(())),
684 btree_map::Entry::Vacant(e) => e.insert(operation),
685 };
686 cx.notify();
687
688 cx.spawn(move |this, mut cx| async move {
689 let _finish = util::defer({
690 let this = this.clone();
691 let mut cx = cx.clone();
692 let extension_id = extension_id.clone();
693 move || {
694 this.update(&mut cx, |this, cx| {
695 this.outstanding_operations.remove(extension_id.as_ref());
696 cx.notify();
697 })
698 .ok();
699 }
700 });
701
702 let mut response = http_client
703 .get(url.as_ref(), Default::default(), true)
704 .await
705 .map_err(|err| anyhow!("error downloading extension: {}", err))?;
706
707 fs.remove_dir(
708 &extension_dir,
709 RemoveOptions {
710 recursive: true,
711 ignore_if_not_exists: true,
712 },
713 )
714 .await?;
715
716 let content_length = response
717 .headers()
718 .get(http_client::http::header::CONTENT_LENGTH)
719 .and_then(|value| value.to_str().ok()?.parse::<usize>().ok());
720
721 let mut body = BufReader::new(response.body_mut());
722 let mut tar_gz_bytes = Vec::new();
723 body.read_to_end(&mut tar_gz_bytes).await?;
724
725 if let Some(content_length) = content_length {
726 let actual_len = tar_gz_bytes.len();
727 if content_length != actual_len {
728 bail!("downloaded extension size {actual_len} does not match content length {content_length}");
729 }
730 }
731 let decompressed_bytes = GzipDecoder::new(BufReader::new(tar_gz_bytes.as_slice()));
732 let archive = Archive::new(decompressed_bytes);
733 archive.unpack(extension_dir).await?;
734 this.update(&mut cx, |this, cx| {
735 this.reload(Some(extension_id.clone()), cx)
736 })?
737 .await;
738
739 if let ExtensionOperation::Install = operation {
740 this.update(&mut cx, |_, cx| {
741 cx.emit(Event::ExtensionInstalled(extension_id));
742 })
743 .ok();
744 }
745
746 anyhow::Ok(())
747 })
748 }
749
750 pub fn install_latest_extension(
751 &mut self,
752 extension_id: Arc<str>,
753 cx: &mut ModelContext<Self>,
754 ) {
755 log::info!("installing extension {extension_id} latest version");
756
757 let schema_versions = schema_version_range();
758 let wasm_api_versions = wasm_api_version_range(ReleaseChannel::global(cx));
759
760 let Some(url) = self
761 .http_client
762 .build_zed_api_url(
763 &format!("/extensions/{extension_id}/download"),
764 &[
765 ("min_schema_version", &schema_versions.start().to_string()),
766 ("max_schema_version", &schema_versions.end().to_string()),
767 (
768 "min_wasm_api_version",
769 &wasm_api_versions.start().to_string(),
770 ),
771 ("max_wasm_api_version", &wasm_api_versions.end().to_string()),
772 ],
773 )
774 .log_err()
775 else {
776 return;
777 };
778
779 self.install_or_upgrade_extension_at_endpoint(
780 extension_id,
781 url,
782 ExtensionOperation::Install,
783 cx,
784 )
785 .detach_and_log_err(cx);
786 }
787
788 pub fn upgrade_extension(
789 &mut self,
790 extension_id: Arc<str>,
791 version: Arc<str>,
792 cx: &mut ModelContext<Self>,
793 ) -> Task<Result<()>> {
794 self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Upgrade, cx)
795 }
796
797 fn install_or_upgrade_extension(
798 &mut self,
799 extension_id: Arc<str>,
800 version: Arc<str>,
801 operation: ExtensionOperation,
802 cx: &mut ModelContext<Self>,
803 ) -> Task<Result<()>> {
804 log::info!("installing extension {extension_id} {version}");
805 let Some(url) = self
806 .http_client
807 .build_zed_api_url(
808 &format!("/extensions/{extension_id}/{version}/download"),
809 &[],
810 )
811 .log_err()
812 else {
813 return Task::ready(Ok(()));
814 };
815
816 self.install_or_upgrade_extension_at_endpoint(extension_id, url, operation, cx)
817 }
818
819 pub fn uninstall_extension(&mut self, extension_id: Arc<str>, cx: &mut ModelContext<Self>) {
820 let extension_dir = self.installed_dir.join(extension_id.as_ref());
821 let work_dir = self.wasm_host.work_dir.join(extension_id.as_ref());
822 let fs = self.fs.clone();
823
824 match self.outstanding_operations.entry(extension_id.clone()) {
825 btree_map::Entry::Occupied(_) => return,
826 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove),
827 };
828
829 cx.spawn(move |this, mut cx| async move {
830 let _finish = util::defer({
831 let this = this.clone();
832 let mut cx = cx.clone();
833 let extension_id = extension_id.clone();
834 move || {
835 this.update(&mut cx, |this, cx| {
836 this.outstanding_operations.remove(extension_id.as_ref());
837 cx.notify();
838 })
839 .ok();
840 }
841 });
842
843 fs.remove_dir(
844 &work_dir,
845 RemoveOptions {
846 recursive: true,
847 ignore_if_not_exists: true,
848 },
849 )
850 .await?;
851
852 fs.remove_dir(
853 &extension_dir,
854 RemoveOptions {
855 recursive: true,
856 ignore_if_not_exists: true,
857 },
858 )
859 .await?;
860
861 this.update(&mut cx, |this, cx| this.reload(None, cx))?
862 .await;
863 anyhow::Ok(())
864 })
865 .detach_and_log_err(cx)
866 }
867
868 pub fn install_dev_extension(
869 &mut self,
870 extension_source_path: PathBuf,
871 cx: &mut ModelContext<Self>,
872 ) -> Task<Result<()>> {
873 let extensions_dir = self.extensions_dir();
874 let fs = self.fs.clone();
875 let builder = self.builder.clone();
876
877 cx.spawn(move |this, mut cx| async move {
878 let mut extension_manifest =
879 ExtensionManifest::load(fs.clone(), &extension_source_path).await?;
880 let extension_id = extension_manifest.id.clone();
881
882 if !this.update(&mut cx, |this, cx| {
883 match this.outstanding_operations.entry(extension_id.clone()) {
884 btree_map::Entry::Occupied(_) => return false,
885 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove),
886 };
887 cx.notify();
888 true
889 })? {
890 return Ok(());
891 }
892
893 let _finish = util::defer({
894 let this = this.clone();
895 let mut cx = cx.clone();
896 let extension_id = extension_id.clone();
897 move || {
898 this.update(&mut cx, |this, cx| {
899 this.outstanding_operations.remove(extension_id.as_ref());
900 cx.notify();
901 })
902 .ok();
903 }
904 });
905
906 cx.background_executor()
907 .spawn({
908 let extension_source_path = extension_source_path.clone();
909 async move {
910 builder
911 .compile_extension(
912 &extension_source_path,
913 &mut extension_manifest,
914 CompileExtensionOptions { release: false },
915 )
916 .await
917 }
918 })
919 .await?;
920
921 let output_path = &extensions_dir.join(extension_id.as_ref());
922 if let Some(metadata) = fs.metadata(output_path).await? {
923 if metadata.is_symlink {
924 fs.remove_file(
925 output_path,
926 RemoveOptions {
927 recursive: false,
928 ignore_if_not_exists: true,
929 },
930 )
931 .await?;
932 } else {
933 bail!("extension {extension_id} is already installed");
934 }
935 }
936
937 fs.create_symlink(output_path, extension_source_path)
938 .await?;
939
940 this.update(&mut cx, |this, cx| this.reload(None, cx))?
941 .await;
942 Ok(())
943 })
944 }
945
946 pub fn rebuild_dev_extension(&mut self, extension_id: Arc<str>, cx: &mut ModelContext<Self>) {
947 let path = self.installed_dir.join(extension_id.as_ref());
948 let builder = self.builder.clone();
949 let fs = self.fs.clone();
950
951 match self.outstanding_operations.entry(extension_id.clone()) {
952 btree_map::Entry::Occupied(_) => return,
953 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Upgrade),
954 };
955
956 cx.notify();
957 let compile = cx.background_executor().spawn(async move {
958 let mut manifest = ExtensionManifest::load(fs, &path).await?;
959 builder
960 .compile_extension(
961 &path,
962 &mut manifest,
963 CompileExtensionOptions { release: true },
964 )
965 .await
966 });
967
968 cx.spawn(|this, mut cx| async move {
969 let result = compile.await;
970
971 this.update(&mut cx, |this, cx| {
972 this.outstanding_operations.remove(&extension_id);
973 cx.notify();
974 })?;
975
976 if result.is_ok() {
977 this.update(&mut cx, |this, cx| this.reload(Some(extension_id), cx))?
978 .await;
979 }
980
981 result
982 })
983 .detach_and_log_err(cx)
984 }
985
986 /// Updates the set of installed extensions.
987 ///
988 /// First, this unloads any themes, languages, or grammars that are
989 /// no longer in the manifest, or whose files have changed on disk.
990 /// Then it loads any themes, languages, or grammars that are newly
991 /// added to the manifest, or whose files have changed on disk.
992 fn extensions_updated(
993 &mut self,
994 new_index: ExtensionIndex,
995 cx: &mut ModelContext<Self>,
996 ) -> Task<()> {
997 let old_index = &self.extension_index;
998
999 // Determine which extensions need to be loaded and unloaded, based
1000 // on the changes to the manifest and the extensions that we know have been
1001 // modified.
1002 let mut extensions_to_unload = Vec::default();
1003 let mut extensions_to_load = Vec::default();
1004 {
1005 let mut old_keys = old_index.extensions.iter().peekable();
1006 let mut new_keys = new_index.extensions.iter().peekable();
1007 loop {
1008 match (old_keys.peek(), new_keys.peek()) {
1009 (None, None) => break,
1010 (None, Some(_)) => {
1011 extensions_to_load.push(new_keys.next().unwrap().0.clone());
1012 }
1013 (Some(_), None) => {
1014 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
1015 }
1016 (Some((old_key, _)), Some((new_key, _))) => match old_key.cmp(new_key) {
1017 Ordering::Equal => {
1018 let (old_key, old_value) = old_keys.next().unwrap();
1019 let (new_key, new_value) = new_keys.next().unwrap();
1020 if old_value != new_value || self.modified_extensions.contains(old_key)
1021 {
1022 extensions_to_unload.push(old_key.clone());
1023 extensions_to_load.push(new_key.clone());
1024 }
1025 }
1026 Ordering::Less => {
1027 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
1028 }
1029 Ordering::Greater => {
1030 extensions_to_load.push(new_keys.next().unwrap().0.clone());
1031 }
1032 },
1033 }
1034 }
1035 self.modified_extensions.clear();
1036 }
1037
1038 if extensions_to_load.is_empty() && extensions_to_unload.is_empty() {
1039 return Task::ready(());
1040 }
1041
1042 let reload_count = extensions_to_unload
1043 .iter()
1044 .filter(|id| extensions_to_load.contains(id))
1045 .count();
1046
1047 log::info!(
1048 "extensions updated. loading {}, reloading {}, unloading {}",
1049 extensions_to_load.len() - reload_count,
1050 reload_count,
1051 extensions_to_unload.len() - reload_count
1052 );
1053
1054 if let Some(telemetry) = &self.telemetry {
1055 for extension_id in &extensions_to_load {
1056 if let Some(extension) = new_index.extensions.get(extension_id) {
1057 telemetry.report_extension_event(
1058 extension_id.clone(),
1059 extension.manifest.version.clone(),
1060 );
1061 }
1062 }
1063 }
1064
1065 let themes_to_remove = old_index
1066 .themes
1067 .iter()
1068 .filter_map(|(name, entry)| {
1069 if extensions_to_unload.contains(&entry.extension) {
1070 Some(name.clone().into())
1071 } else {
1072 None
1073 }
1074 })
1075 .collect::<Vec<_>>();
1076 let languages_to_remove = old_index
1077 .languages
1078 .iter()
1079 .filter_map(|(name, entry)| {
1080 if extensions_to_unload.contains(&entry.extension) {
1081 Some(name.clone())
1082 } else {
1083 None
1084 }
1085 })
1086 .collect::<Vec<_>>();
1087 let mut grammars_to_remove = Vec::new();
1088 for extension_id in &extensions_to_unload {
1089 let Some(extension) = old_index.extensions.get(extension_id) else {
1090 continue;
1091 };
1092 grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
1093 for (language_server_name, config) in extension.manifest.language_servers.iter() {
1094 for language in config.languages() {
1095 self.registration_hooks
1096 .remove_lsp_adapter(&language, language_server_name);
1097 }
1098 }
1099 }
1100
1101 self.wasm_extensions
1102 .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
1103 self.registration_hooks.remove_user_themes(themes_to_remove);
1104 self.registration_hooks
1105 .remove_languages(&languages_to_remove, &grammars_to_remove);
1106
1107 let languages_to_add = new_index
1108 .languages
1109 .iter()
1110 .filter(|(_, entry)| extensions_to_load.contains(&entry.extension))
1111 .collect::<Vec<_>>();
1112 let mut grammars_to_add = Vec::new();
1113 let mut themes_to_add = Vec::new();
1114 let mut snippets_to_add = Vec::new();
1115 for extension_id in &extensions_to_load {
1116 let Some(extension) = new_index.extensions.get(extension_id) else {
1117 continue;
1118 };
1119
1120 grammars_to_add.extend(extension.manifest.grammars.keys().map(|grammar_name| {
1121 let mut grammar_path = self.installed_dir.clone();
1122 grammar_path.extend([extension_id.as_ref(), "grammars"]);
1123 grammar_path.push(grammar_name.as_ref());
1124 grammar_path.set_extension("wasm");
1125 (grammar_name.clone(), grammar_path)
1126 }));
1127 themes_to_add.extend(extension.manifest.themes.iter().map(|theme_path| {
1128 let mut path = self.installed_dir.clone();
1129 path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
1130 path
1131 }));
1132 snippets_to_add.extend(extension.manifest.snippets.iter().map(|snippets_path| {
1133 let mut path = self.installed_dir.clone();
1134 path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
1135 path
1136 }));
1137 }
1138
1139 self.registration_hooks
1140 .register_wasm_grammars(grammars_to_add);
1141
1142 for (language_name, language) in languages_to_add {
1143 let mut language_path = self.installed_dir.clone();
1144 language_path.extend([
1145 Path::new(language.extension.as_ref()),
1146 language.path.as_path(),
1147 ]);
1148 self.registration_hooks.register_language(
1149 language_name.clone(),
1150 language.grammar.clone(),
1151 language.matcher.clone(),
1152 Arc::new(move || {
1153 let config = std::fs::read_to_string(language_path.join("config.toml"))?;
1154 let config: LanguageConfig = ::toml::from_str(&config)?;
1155 let queries = load_plugin_queries(&language_path);
1156 let context_provider =
1157 std::fs::read_to_string(language_path.join("tasks.json"))
1158 .ok()
1159 .and_then(|contents| {
1160 let definitions =
1161 serde_json_lenient::from_str(&contents).log_err()?;
1162 Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
1163 });
1164
1165 Ok(LoadedLanguage {
1166 config,
1167 queries,
1168 context_provider,
1169 toolchain_provider: None,
1170 })
1171 }),
1172 );
1173 }
1174
1175 let fs = self.fs.clone();
1176 let wasm_host = self.wasm_host.clone();
1177 let root_dir = self.installed_dir.clone();
1178 let api = self.registration_hooks.clone();
1179 let extension_entries = extensions_to_load
1180 .iter()
1181 .filter_map(|name| new_index.extensions.get(name).cloned())
1182 .collect::<Vec<_>>();
1183
1184 self.extension_index = new_index;
1185 cx.notify();
1186 cx.emit(Event::ExtensionsUpdated);
1187
1188 cx.spawn(|this, mut cx| async move {
1189 cx.background_executor()
1190 .spawn({
1191 let fs = fs.clone();
1192 async move {
1193 for theme_path in themes_to_add.into_iter() {
1194 api.load_user_theme(theme_path, fs.clone()).await.log_err();
1195 }
1196
1197 for snippets_path in &snippets_to_add {
1198 if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
1199 {
1200 api.register_snippets(snippets_path, &snippets_contents)
1201 .log_err();
1202 }
1203 }
1204 }
1205 })
1206 .await;
1207
1208 let mut wasm_extensions = Vec::new();
1209 for extension in extension_entries {
1210 if extension.manifest.lib.kind.is_none() {
1211 continue;
1212 };
1213
1214 let extension_path = root_dir.join(extension.manifest.id.as_ref());
1215 let wasm_extension = WasmExtension::load(
1216 extension_path,
1217 &extension.manifest,
1218 wasm_host.clone(),
1219 &cx,
1220 )
1221 .await;
1222
1223 if let Some(wasm_extension) = wasm_extension.log_err() {
1224 wasm_extensions.push((extension.manifest.clone(), wasm_extension));
1225 } else {
1226 this.update(&mut cx, |_, cx| {
1227 cx.emit(Event::ExtensionFailedToLoad(extension.manifest.id.clone()))
1228 })
1229 .ok();
1230 }
1231 }
1232
1233 this.update(&mut cx, |this, cx| {
1234 this.reload_complete_senders.clear();
1235
1236 for (manifest, wasm_extension) in &wasm_extensions {
1237 for (language_server_id, language_server_config) in &manifest.language_servers {
1238 for language in language_server_config.languages() {
1239 this.registration_hooks.register_lsp_adapter(
1240 language.clone(),
1241 ExtensionLspAdapter {
1242 extension: wasm_extension.clone(),
1243 host: this.wasm_host.clone(),
1244 language_server_id: language_server_id.clone(),
1245 config: wit::LanguageServerConfig {
1246 name: language_server_id.0.to_string(),
1247 language_name: language.to_string(),
1248 },
1249 },
1250 );
1251 }
1252 }
1253
1254 for (slash_command_name, slash_command) in &manifest.slash_commands {
1255 this.registration_hooks.register_slash_command(
1256 crate::wit::SlashCommand {
1257 name: slash_command_name.to_string(),
1258 description: slash_command.description.to_string(),
1259 // We don't currently expose this as a configurable option, as it currently drives
1260 // the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
1261 // defined in extensions, as they are not able to be added to the menu.
1262 tooltip_text: String::new(),
1263 requires_argument: slash_command.requires_argument,
1264 },
1265 wasm_extension.clone(),
1266 this.wasm_host.clone(),
1267 );
1268 }
1269
1270 for (provider_id, _provider) in &manifest.indexed_docs_providers {
1271 this.registration_hooks.register_docs_provider(
1272 wasm_extension.clone(),
1273 this.wasm_host.clone(),
1274 provider_id.clone(),
1275 );
1276 }
1277 }
1278
1279 this.wasm_extensions.extend(wasm_extensions);
1280 this.registration_hooks.reload_current_theme(cx);
1281 })
1282 .ok();
1283 })
1284 }
1285
1286 fn rebuild_extension_index(&self, cx: &mut ModelContext<Self>) -> Task<ExtensionIndex> {
1287 let fs = self.fs.clone();
1288 let work_dir = self.wasm_host.work_dir.clone();
1289 let extensions_dir = self.installed_dir.clone();
1290 let index_path = self.index_path.clone();
1291 let extension_api = self.registration_hooks.clone();
1292 cx.background_executor().spawn(async move {
1293 let start_time = Instant::now();
1294 let mut index = ExtensionIndex::default();
1295
1296 fs.create_dir(&work_dir).await.log_err();
1297 fs.create_dir(&extensions_dir).await.log_err();
1298
1299 let extension_paths = fs.read_dir(&extensions_dir).await;
1300 if let Ok(mut extension_paths) = extension_paths {
1301 while let Some(extension_dir) = extension_paths.next().await {
1302 let Ok(extension_dir) = extension_dir else {
1303 continue;
1304 };
1305
1306 if extension_dir
1307 .file_name()
1308 .map_or(false, |file_name| file_name == ".DS_Store")
1309 {
1310 continue;
1311 }
1312
1313 Self::add_extension_to_index(
1314 fs.clone(),
1315 extension_dir,
1316 &mut index,
1317 extension_api.clone(),
1318 )
1319 .await
1320 .log_err();
1321 }
1322 }
1323
1324 if let Ok(index_json) = serde_json::to_string_pretty(&index) {
1325 fs.save(&index_path, &index_json.as_str().into(), Default::default())
1326 .await
1327 .context("failed to save extension index")
1328 .log_err();
1329 }
1330
1331 log::info!("rebuilt extension index in {:?}", start_time.elapsed());
1332 index
1333 })
1334 }
1335
1336 async fn add_extension_to_index(
1337 fs: Arc<dyn Fs>,
1338 extension_dir: PathBuf,
1339 index: &mut ExtensionIndex,
1340 extension_api: Arc<dyn ExtensionRegistrationHooks>,
1341 ) -> Result<()> {
1342 let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
1343 let extension_id = extension_manifest.id.clone();
1344
1345 // TODO: distinguish dev extensions more explicitly, by the absence
1346 // of a checksum file that we'll create when downloading normal extensions.
1347 let is_dev = fs
1348 .metadata(&extension_dir)
1349 .await?
1350 .ok_or_else(|| anyhow!("directory does not exist"))?
1351 .is_symlink;
1352
1353 if let Ok(mut language_paths) = fs.read_dir(&extension_dir.join("languages")).await {
1354 while let Some(language_path) = language_paths.next().await {
1355 let language_path = language_path?;
1356 let Ok(relative_path) = language_path.strip_prefix(&extension_dir) else {
1357 continue;
1358 };
1359 let Ok(Some(fs_metadata)) = fs.metadata(&language_path).await else {
1360 continue;
1361 };
1362 if !fs_metadata.is_dir {
1363 continue;
1364 }
1365 let config = fs.load(&language_path.join("config.toml")).await?;
1366 let config = ::toml::from_str::<LanguageConfig>(&config)?;
1367
1368 let relative_path = relative_path.to_path_buf();
1369 if !extension_manifest.languages.contains(&relative_path) {
1370 extension_manifest.languages.push(relative_path.clone());
1371 }
1372
1373 index.languages.insert(
1374 config.name.clone(),
1375 ExtensionIndexLanguageEntry {
1376 extension: extension_id.clone(),
1377 path: relative_path,
1378 matcher: config.matcher,
1379 grammar: config.grammar,
1380 },
1381 );
1382 }
1383 }
1384
1385 if let Ok(mut theme_paths) = fs.read_dir(&extension_dir.join("themes")).await {
1386 while let Some(theme_path) = theme_paths.next().await {
1387 let theme_path = theme_path?;
1388 let Ok(relative_path) = theme_path.strip_prefix(&extension_dir) else {
1389 continue;
1390 };
1391
1392 let Some(theme_families) = extension_api
1393 .list_theme_names(theme_path.clone(), fs.clone())
1394 .await
1395 .log_err()
1396 else {
1397 continue;
1398 };
1399
1400 let relative_path = relative_path.to_path_buf();
1401 if !extension_manifest.themes.contains(&relative_path) {
1402 extension_manifest.themes.push(relative_path.clone());
1403 }
1404
1405 for theme_name in theme_families {
1406 index.themes.insert(
1407 theme_name.into(),
1408 ExtensionIndexThemeEntry {
1409 extension: extension_id.clone(),
1410 path: relative_path.clone(),
1411 },
1412 );
1413 }
1414 }
1415 }
1416
1417 let extension_wasm_path = extension_dir.join("extension.wasm");
1418 if fs.is_file(&extension_wasm_path).await {
1419 extension_manifest
1420 .lib
1421 .kind
1422 .get_or_insert(ExtensionLibraryKind::Rust);
1423 }
1424
1425 index.extensions.insert(
1426 extension_id.clone(),
1427 ExtensionIndexEntry {
1428 dev: is_dev,
1429 manifest: Arc::new(extension_manifest),
1430 },
1431 );
1432
1433 Ok(())
1434 }
1435}
1436
1437fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
1438 let mut result = LanguageQueries::default();
1439 if let Some(entries) = std::fs::read_dir(root_path).log_err() {
1440 for entry in entries {
1441 let Some(entry) = entry.log_err() else {
1442 continue;
1443 };
1444 let path = entry.path();
1445 if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
1446 if !remainder.ends_with(".scm") {
1447 continue;
1448 }
1449 for (name, query) in QUERY_FILENAME_PREFIXES {
1450 if remainder.starts_with(name) {
1451 if let Some(contents) = std::fs::read_to_string(&path).log_err() {
1452 match query(&mut result) {
1453 None => *query(&mut result) = Some(contents.into()),
1454 Some(r) => r.to_mut().push_str(contents.as_ref()),
1455 }
1456 }
1457 break;
1458 }
1459 }
1460 }
1461 }
1462 }
1463 result
1464}