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