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 for extension_id in &extensions_to_load {
1005 if let Some(extension) = new_index.extensions.get(extension_id) {
1006 telemetry::event!(
1007 "Extension Loaded",
1008 extension_id,
1009 version = extension.manifest.version
1010 );
1011 }
1012 }
1013
1014 let themes_to_remove = old_index
1015 .themes
1016 .iter()
1017 .filter_map(|(name, entry)| {
1018 if extensions_to_unload.contains(&entry.extension) {
1019 Some(name.clone().into())
1020 } else {
1021 None
1022 }
1023 })
1024 .collect::<Vec<_>>();
1025 let languages_to_remove = old_index
1026 .languages
1027 .iter()
1028 .filter_map(|(name, entry)| {
1029 if extensions_to_unload.contains(&entry.extension) {
1030 Some(name.clone())
1031 } else {
1032 None
1033 }
1034 })
1035 .collect::<Vec<_>>();
1036 let mut grammars_to_remove = Vec::new();
1037 for extension_id in &extensions_to_unload {
1038 let Some(extension) = old_index.extensions.get(extension_id) else {
1039 continue;
1040 };
1041 grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
1042 for (language_server_name, config) in extension.manifest.language_servers.iter() {
1043 for language in config.languages() {
1044 self.proxy
1045 .remove_language_server(&language, language_server_name);
1046 }
1047 }
1048 }
1049
1050 self.wasm_extensions
1051 .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
1052 self.proxy.remove_user_themes(themes_to_remove);
1053 self.proxy
1054 .remove_languages(&languages_to_remove, &grammars_to_remove);
1055
1056 let languages_to_add = new_index
1057 .languages
1058 .iter()
1059 .filter(|(_, entry)| extensions_to_load.contains(&entry.extension))
1060 .collect::<Vec<_>>();
1061 let mut grammars_to_add = Vec::new();
1062 let mut themes_to_add = Vec::new();
1063 let mut snippets_to_add = Vec::new();
1064 for extension_id in &extensions_to_load {
1065 let Some(extension) = new_index.extensions.get(extension_id) else {
1066 continue;
1067 };
1068
1069 grammars_to_add.extend(extension.manifest.grammars.keys().map(|grammar_name| {
1070 let mut grammar_path = self.installed_dir.clone();
1071 grammar_path.extend([extension_id.as_ref(), "grammars"]);
1072 grammar_path.push(grammar_name.as_ref());
1073 grammar_path.set_extension("wasm");
1074 (grammar_name.clone(), grammar_path)
1075 }));
1076 themes_to_add.extend(extension.manifest.themes.iter().map(|theme_path| {
1077 let mut path = self.installed_dir.clone();
1078 path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
1079 path
1080 }));
1081 snippets_to_add.extend(extension.manifest.snippets.iter().map(|snippets_path| {
1082 let mut path = self.installed_dir.clone();
1083 path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
1084 path
1085 }));
1086 }
1087
1088 self.proxy.register_grammars(grammars_to_add);
1089
1090 for (language_name, language) in languages_to_add {
1091 let mut language_path = self.installed_dir.clone();
1092 language_path.extend([
1093 Path::new(language.extension.as_ref()),
1094 language.path.as_path(),
1095 ]);
1096 self.proxy.register_language(
1097 language_name.clone(),
1098 language.grammar.clone(),
1099 language.matcher.clone(),
1100 language.hidden,
1101 Arc::new(move || {
1102 let config = std::fs::read_to_string(language_path.join("config.toml"))?;
1103 let config: LanguageConfig = ::toml::from_str(&config)?;
1104 let queries = load_plugin_queries(&language_path);
1105 let context_provider =
1106 std::fs::read_to_string(language_path.join("tasks.json"))
1107 .ok()
1108 .and_then(|contents| {
1109 let definitions =
1110 serde_json_lenient::from_str(&contents).log_err()?;
1111 Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
1112 });
1113
1114 Ok(LoadedLanguage {
1115 config,
1116 queries,
1117 context_provider,
1118 toolchain_provider: None,
1119 })
1120 }),
1121 );
1122 }
1123
1124 let fs = self.fs.clone();
1125 let wasm_host = self.wasm_host.clone();
1126 let root_dir = self.installed_dir.clone();
1127 let proxy = self.proxy.clone();
1128 let extension_entries = extensions_to_load
1129 .iter()
1130 .filter_map(|name| new_index.extensions.get(name).cloned())
1131 .collect::<Vec<_>>();
1132
1133 self.extension_index = new_index;
1134 cx.notify();
1135 cx.emit(Event::ExtensionsUpdated);
1136
1137 cx.spawn(|this, mut cx| async move {
1138 cx.background_executor()
1139 .spawn({
1140 let fs = fs.clone();
1141 async move {
1142 for theme_path in themes_to_add.into_iter() {
1143 proxy
1144 .load_user_theme(theme_path, fs.clone())
1145 .await
1146 .log_err();
1147 }
1148
1149 for snippets_path in &snippets_to_add {
1150 if let Some(snippets_contents) = fs.load(snippets_path).await.log_err()
1151 {
1152 proxy
1153 .register_snippet(snippets_path, &snippets_contents)
1154 .log_err();
1155 }
1156 }
1157 }
1158 })
1159 .await;
1160
1161 let mut wasm_extensions = Vec::new();
1162 for extension in extension_entries {
1163 if extension.manifest.lib.kind.is_none() {
1164 continue;
1165 };
1166
1167 let extension_path = root_dir.join(extension.manifest.id.as_ref());
1168 let wasm_extension = WasmExtension::load(
1169 extension_path,
1170 &extension.manifest,
1171 wasm_host.clone(),
1172 &cx,
1173 )
1174 .await;
1175
1176 if let Some(wasm_extension) = wasm_extension.log_err() {
1177 wasm_extensions.push((extension.manifest.clone(), wasm_extension));
1178 } else {
1179 this.update(&mut cx, |_, cx| {
1180 cx.emit(Event::ExtensionFailedToLoad(extension.manifest.id.clone()))
1181 })
1182 .ok();
1183 }
1184 }
1185
1186 this.update(&mut cx, |this, cx| {
1187 this.reload_complete_senders.clear();
1188
1189 for (manifest, wasm_extension) in &wasm_extensions {
1190 let extension = Arc::new(wasm_extension.clone());
1191
1192 for (language_server_id, language_server_config) in &manifest.language_servers {
1193 for language in language_server_config.languages() {
1194 this.proxy.register_language_server(
1195 extension.clone(),
1196 language_server_id.clone(),
1197 language.clone(),
1198 );
1199 }
1200 }
1201
1202 for (slash_command_name, slash_command) in &manifest.slash_commands {
1203 this.proxy.register_slash_command(
1204 extension.clone(),
1205 extension::SlashCommand {
1206 name: slash_command_name.to_string(),
1207 description: slash_command.description.to_string(),
1208 // We don't currently expose this as a configurable option, as it currently drives
1209 // the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
1210 // defined in extensions, as they are not able to be added to the menu.
1211 tooltip_text: String::new(),
1212 requires_argument: slash_command.requires_argument,
1213 },
1214 );
1215 }
1216
1217 for (id, _context_server_entry) in &manifest.context_servers {
1218 this.proxy
1219 .register_context_server(extension.clone(), id.clone(), cx);
1220 }
1221
1222 for (provider_id, _provider) in &manifest.indexed_docs_providers {
1223 this.proxy
1224 .register_indexed_docs_provider(extension.clone(), provider_id.clone());
1225 }
1226 }
1227
1228 this.wasm_extensions.extend(wasm_extensions);
1229 this.proxy.reload_current_theme(cx);
1230 })
1231 .ok();
1232 })
1233 }
1234
1235 fn rebuild_extension_index(&self, cx: &mut ModelContext<Self>) -> Task<ExtensionIndex> {
1236 let fs = self.fs.clone();
1237 let work_dir = self.wasm_host.work_dir.clone();
1238 let extensions_dir = self.installed_dir.clone();
1239 let index_path = self.index_path.clone();
1240 let proxy = self.proxy.clone();
1241 cx.background_executor().spawn(async move {
1242 let start_time = Instant::now();
1243 let mut index = ExtensionIndex::default();
1244
1245 fs.create_dir(&work_dir).await.log_err();
1246 fs.create_dir(&extensions_dir).await.log_err();
1247
1248 let extension_paths = fs.read_dir(&extensions_dir).await;
1249 if let Ok(mut extension_paths) = extension_paths {
1250 while let Some(extension_dir) = extension_paths.next().await {
1251 let Ok(extension_dir) = extension_dir else {
1252 continue;
1253 };
1254
1255 if extension_dir
1256 .file_name()
1257 .map_or(false, |file_name| file_name == ".DS_Store")
1258 {
1259 continue;
1260 }
1261
1262 Self::add_extension_to_index(
1263 fs.clone(),
1264 extension_dir,
1265 &mut index,
1266 proxy.clone(),
1267 )
1268 .await
1269 .log_err();
1270 }
1271 }
1272
1273 if let Ok(index_json) = serde_json::to_string_pretty(&index) {
1274 fs.save(&index_path, &index_json.as_str().into(), Default::default())
1275 .await
1276 .context("failed to save extension index")
1277 .log_err();
1278 }
1279
1280 log::info!("rebuilt extension index in {:?}", start_time.elapsed());
1281 index
1282 })
1283 }
1284
1285 async fn add_extension_to_index(
1286 fs: Arc<dyn Fs>,
1287 extension_dir: PathBuf,
1288 index: &mut ExtensionIndex,
1289 proxy: Arc<ExtensionHostProxy>,
1290 ) -> Result<()> {
1291 let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
1292 let extension_id = extension_manifest.id.clone();
1293
1294 // TODO: distinguish dev extensions more explicitly, by the absence
1295 // of a checksum file that we'll create when downloading normal extensions.
1296 let is_dev = fs
1297 .metadata(&extension_dir)
1298 .await?
1299 .ok_or_else(|| anyhow!("directory does not exist"))?
1300 .is_symlink;
1301
1302 if let Ok(mut language_paths) = fs.read_dir(&extension_dir.join("languages")).await {
1303 while let Some(language_path) = language_paths.next().await {
1304 let language_path = language_path?;
1305 let Ok(relative_path) = language_path.strip_prefix(&extension_dir) else {
1306 continue;
1307 };
1308 let Ok(Some(fs_metadata)) = fs.metadata(&language_path).await else {
1309 continue;
1310 };
1311 if !fs_metadata.is_dir {
1312 continue;
1313 }
1314 let config = fs.load(&language_path.join("config.toml")).await?;
1315 let config = ::toml::from_str::<LanguageConfig>(&config)?;
1316
1317 let relative_path = relative_path.to_path_buf();
1318 if !extension_manifest.languages.contains(&relative_path) {
1319 extension_manifest.languages.push(relative_path.clone());
1320 }
1321
1322 index.languages.insert(
1323 config.name.clone(),
1324 ExtensionIndexLanguageEntry {
1325 extension: extension_id.clone(),
1326 path: relative_path,
1327 matcher: config.matcher,
1328 hidden: config.hidden,
1329 grammar: config.grammar,
1330 },
1331 );
1332 }
1333 }
1334
1335 if let Ok(mut theme_paths) = fs.read_dir(&extension_dir.join("themes")).await {
1336 while let Some(theme_path) = theme_paths.next().await {
1337 let theme_path = theme_path?;
1338 let Ok(relative_path) = theme_path.strip_prefix(&extension_dir) else {
1339 continue;
1340 };
1341
1342 let Some(theme_families) = proxy
1343 .list_theme_names(theme_path.clone(), fs.clone())
1344 .await
1345 .log_err()
1346 else {
1347 continue;
1348 };
1349
1350 let relative_path = relative_path.to_path_buf();
1351 if !extension_manifest.themes.contains(&relative_path) {
1352 extension_manifest.themes.push(relative_path.clone());
1353 }
1354
1355 for theme_name in theme_families {
1356 index.themes.insert(
1357 theme_name.into(),
1358 ExtensionIndexThemeEntry {
1359 extension: extension_id.clone(),
1360 path: relative_path.clone(),
1361 },
1362 );
1363 }
1364 }
1365 }
1366
1367 let extension_wasm_path = extension_dir.join("extension.wasm");
1368 if fs.is_file(&extension_wasm_path).await {
1369 extension_manifest
1370 .lib
1371 .kind
1372 .get_or_insert(ExtensionLibraryKind::Rust);
1373 }
1374
1375 index.extensions.insert(
1376 extension_id.clone(),
1377 ExtensionIndexEntry {
1378 dev: is_dev,
1379 manifest: Arc::new(extension_manifest),
1380 },
1381 );
1382
1383 Ok(())
1384 }
1385
1386 fn prepare_remote_extension(
1387 &mut self,
1388 extension_id: Arc<str>,
1389 is_dev: bool,
1390 tmp_dir: PathBuf,
1391 cx: &mut ModelContext<Self>,
1392 ) -> Task<Result<()>> {
1393 let src_dir = self.extensions_dir().join(extension_id.as_ref());
1394 let Some(loaded_extension) = self.extension_index.extensions.get(&extension_id).cloned()
1395 else {
1396 return Task::ready(Err(anyhow!("extension no longer installed")));
1397 };
1398 let fs = self.fs.clone();
1399 cx.background_executor().spawn(async move {
1400 const EXTENSION_TOML: &str = "extension.toml";
1401 const EXTENSION_WASM: &str = "extension.wasm";
1402 const CONFIG_TOML: &str = "config.toml";
1403
1404 if is_dev {
1405 let manifest_toml = toml::to_string(&loaded_extension.manifest)?;
1406 fs.save(
1407 &tmp_dir.join(EXTENSION_TOML),
1408 &Rope::from(manifest_toml),
1409 language::LineEnding::Unix,
1410 )
1411 .await?;
1412 } else {
1413 fs.copy_file(
1414 &src_dir.join(EXTENSION_TOML),
1415 &tmp_dir.join(EXTENSION_TOML),
1416 fs::CopyOptions::default(),
1417 )
1418 .await?
1419 }
1420
1421 if fs.is_file(&src_dir.join(EXTENSION_WASM)).await {
1422 fs.copy_file(
1423 &src_dir.join(EXTENSION_WASM),
1424 &tmp_dir.join(EXTENSION_WASM),
1425 fs::CopyOptions::default(),
1426 )
1427 .await?
1428 }
1429
1430 for language_path in loaded_extension.manifest.languages.iter() {
1431 if fs
1432 .is_file(&src_dir.join(language_path).join(CONFIG_TOML))
1433 .await
1434 {
1435 fs.create_dir(&tmp_dir.join(language_path)).await?;
1436 fs.copy_file(
1437 &src_dir.join(language_path).join(CONFIG_TOML),
1438 &tmp_dir.join(language_path).join(CONFIG_TOML),
1439 fs::CopyOptions::default(),
1440 )
1441 .await?
1442 }
1443 }
1444
1445 Ok(())
1446 })
1447 }
1448
1449 async fn sync_extensions_over_ssh(
1450 this: &WeakModel<Self>,
1451 client: WeakModel<SshRemoteClient>,
1452 cx: &mut AsyncAppContext,
1453 ) -> Result<()> {
1454 let extensions = this.update(cx, |this, _cx| {
1455 this.extension_index
1456 .extensions
1457 .iter()
1458 .filter_map(|(id, entry)| {
1459 if entry.manifest.language_servers.is_empty() {
1460 return None;
1461 }
1462 Some(proto::Extension {
1463 id: id.to_string(),
1464 version: entry.manifest.version.to_string(),
1465 dev: entry.dev,
1466 })
1467 })
1468 .collect()
1469 })?;
1470
1471 let response = client
1472 .update(cx, |client, _cx| {
1473 client
1474 .proto_client()
1475 .request(proto::SyncExtensions { extensions })
1476 })?
1477 .await?;
1478
1479 for missing_extension in response.missing_extensions.into_iter() {
1480 let tmp_dir = tempfile::tempdir()?;
1481 this.update(cx, |this, cx| {
1482 this.prepare_remote_extension(
1483 missing_extension.id.clone().into(),
1484 missing_extension.dev,
1485 tmp_dir.path().to_owned(),
1486 cx,
1487 )
1488 })?
1489 .await?;
1490 let dest_dir = PathBuf::from(&response.tmp_dir).join(missing_extension.clone().id);
1491 log::info!("Uploading extension {}", missing_extension.clone().id);
1492
1493 client
1494 .update(cx, |client, cx| {
1495 client.upload_directory(tmp_dir.path().to_owned(), dest_dir.clone(), cx)
1496 })?
1497 .await?;
1498
1499 log::info!(
1500 "Finished uploading extension {}",
1501 missing_extension.clone().id
1502 );
1503
1504 client
1505 .update(cx, |client, _cx| {
1506 client.proto_client().request(proto::InstallExtension {
1507 tmp_dir: dest_dir.to_string_lossy().to_string(),
1508 extension: Some(missing_extension),
1509 })
1510 })?
1511 .await?;
1512 }
1513
1514 anyhow::Ok(())
1515 }
1516
1517 pub async fn update_ssh_clients(
1518 this: &WeakModel<Self>,
1519 cx: &mut AsyncAppContext,
1520 ) -> Result<()> {
1521 let clients = this.update(cx, |this, _cx| {
1522 this.ssh_clients.retain(|_k, v| v.upgrade().is_some());
1523 this.ssh_clients.values().cloned().collect::<Vec<_>>()
1524 })?;
1525
1526 for client in clients {
1527 Self::sync_extensions_over_ssh(&this, client, cx)
1528 .await
1529 .log_err();
1530 }
1531
1532 anyhow::Ok(())
1533 }
1534
1535 pub fn register_ssh_client(
1536 &mut self,
1537 client: Model<SshRemoteClient>,
1538 cx: &mut ModelContext<Self>,
1539 ) {
1540 let connection_options = client.read(cx).connection_options();
1541 if self.ssh_clients.contains_key(&connection_options.ssh_url()) {
1542 return;
1543 }
1544
1545 self.ssh_clients
1546 .insert(connection_options.ssh_url(), client.downgrade());
1547 self.ssh_registered_tx.unbounded_send(()).ok();
1548 }
1549}
1550
1551fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
1552 let mut result = LanguageQueries::default();
1553 if let Some(entries) = std::fs::read_dir(root_path).log_err() {
1554 for entry in entries {
1555 let Some(entry) = entry.log_err() else {
1556 continue;
1557 };
1558 let path = entry.path();
1559 if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
1560 if !remainder.ends_with(".scm") {
1561 continue;
1562 }
1563 for (name, query) in QUERY_FILENAME_PREFIXES {
1564 if remainder.starts_with(name) {
1565 if let Some(contents) = std::fs::read_to_string(&path).log_err() {
1566 match query(&mut result) {
1567 None => *query(&mut result) = Some(contents.into()),
1568 Some(r) => r.to_mut().push_str(contents.as_ref()),
1569 }
1570 }
1571 break;
1572 }
1573 }
1574 }
1575 }
1576 }
1577 result
1578}