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