1mod anthropic_migration;
2mod capability_granter;
3mod copilot_migration;
4pub mod extension_settings;
5mod google_ai_migration;
6pub mod headless_host;
7mod open_router_migration;
8mod openai_migration;
9pub mod wasm_host;
10
11#[cfg(test)]
12mod extension_store_test;
13
14use anyhow::{Context as _, Result, anyhow, bail};
15use async_compression::futures::bufread::GzipDecoder;
16use async_tar::Archive;
17use client::ExtensionProvides;
18use client::{Client, ExtensionMetadata, GetExtensionsResponse, proto, telemetry::Telemetry};
19use collections::{BTreeMap, BTreeSet, HashSet, btree_map};
20
21pub use extension::ExtensionManifest;
22use extension::extension_builder::{CompileExtensionOptions, ExtensionBuilder};
23use extension::{
24 ExtensionContextServerProxy, ExtensionDebugAdapterProviderProxy, ExtensionEvents,
25 ExtensionGrammarProxy, ExtensionHostProxy, ExtensionLanguageModelProviderProxy,
26 ExtensionLanguageProxy, ExtensionLanguageServerProxy, ExtensionSlashCommandProxy,
27 ExtensionSnippetProxy, ExtensionThemeProxy,
28};
29use fs::{Fs, RemoveOptions};
30use futures::future::join_all;
31use futures::{
32 AsyncReadExt as _, Future, FutureExt as _, StreamExt as _,
33 channel::{
34 mpsc::{UnboundedSender, unbounded},
35 oneshot,
36 },
37 io::BufReader,
38 select_biased,
39};
40use gpui::{
41 App, AppContext as _, AsyncApp, Context, Entity, EventEmitter, Global, SharedString, Task,
42 WeakEntity, actions,
43};
44use http_client::{AsyncBody, HttpClient, HttpClientWithUrl};
45use language::{
46 LanguageConfig, LanguageMatcher, LanguageName, LanguageQueries, LoadedLanguage,
47 QUERY_FILENAME_PREFIXES, Rope,
48};
49use node_runtime::NodeRuntime;
50use project::ContextProviderWithTasks;
51use release_channel::ReleaseChannel;
52use remote::RemoteClient;
53use semver::Version;
54use serde::{Deserialize, Serialize};
55use settings::Settings;
56use std::ops::RangeInclusive;
57use std::str::FromStr;
58use std::{
59 cmp::Ordering,
60 path::{self, Path, PathBuf},
61 sync::Arc,
62 time::Duration,
63};
64use url::Url;
65use util::{ResultExt, paths::RemotePathBuf};
66use wasm_host::llm_provider::ExtensionLanguageModelProvider;
67use wasm_host::{
68 WasmExtension, WasmHost,
69 wit::{LlmModelInfo, LlmProviderInfo, is_supported_wasm_api_version, wasm_api_version_range},
70};
71
72struct LlmProviderWithModels {
73 provider_info: LlmProviderInfo,
74 models: Vec<LlmModelInfo>,
75 is_authenticated: bool,
76 icon_path: Option<SharedString>,
77 auth_config: Option<extension::LanguageModelAuthConfig>,
78}
79
80pub use extension::{
81 ExtensionLibraryKind, GrammarManifestEntry, OldExtensionManifest, SchemaVersion,
82};
83pub use extension_settings::ExtensionSettings;
84
85pub const RELOAD_DEBOUNCE_DURATION: Duration = Duration::from_millis(200);
86const FS_WATCH_LATENCY: Duration = Duration::from_millis(100);
87
88/// Extension IDs that are being migrated from hardcoded LLM providers.
89/// For backwards compatibility, if the user has the corresponding env var set,
90/// we automatically enable env var reading for these extensions on first install.
91pub const LEGACY_LLM_EXTENSION_IDS: &[&str] = &[
92 "anthropic",
93 "copilot-chat",
94 "google-ai",
95 "openrouter",
96 "openai",
97];
98
99/// Migrates legacy LLM provider extensions by auto-enabling env var reading
100/// if the env var is currently present in the environment.
101///
102/// This is idempotent: if the env var is already in `allowed_env_vars`,
103/// we skip. This means if a user explicitly removes it, it will be re-added on
104/// next launch if the env var is still set - but that's predictable behavior.
105fn migrate_legacy_llm_provider_env_var(manifest: &ExtensionManifest, cx: &mut App) {
106 // Only apply migration to known legacy LLM extensions
107 if !LEGACY_LLM_EXTENSION_IDS.contains(&manifest.id.as_ref()) {
108 return;
109 }
110
111 // Check each provider in the manifest
112 for (provider_id, provider_entry) in &manifest.language_model_providers {
113 let Some(auth_config) = &provider_entry.auth else {
114 continue;
115 };
116 let Some(env_vars) = &auth_config.env_vars else {
117 continue;
118 };
119
120 let full_provider_id = format!("{}:{}", manifest.id, provider_id);
121
122 // For each env var, check if it's set and enable it if so
123 for env_var_name in env_vars {
124 let env_var_is_set = std::env::var(env_var_name)
125 .map(|v| !v.is_empty())
126 .unwrap_or(false);
127
128 if !env_var_is_set {
129 continue;
130 }
131
132 let settings_key: Arc<str> = format!("{}:{}", full_provider_id, env_var_name).into();
133
134 // Check if already enabled in settings
135 let already_enabled = ExtensionSettings::get_global(cx)
136 .allowed_env_var_providers
137 .contains(settings_key.as_ref());
138
139 if already_enabled {
140 continue;
141 }
142
143 // Enable env var reading since the env var is set
144 settings::update_settings_file(<dyn fs::Fs>::global(cx), cx, {
145 let settings_key = settings_key.clone();
146 move |settings, _| {
147 let allowed = settings
148 .extension
149 .allowed_env_var_providers
150 .get_or_insert_with(Vec::new);
151
152 if !allowed
153 .iter()
154 .any(|id| id.as_ref() == settings_key.as_ref())
155 {
156 allowed.push(settings_key);
157 }
158 }
159 });
160 }
161 }
162}
163
164/// The current extension [`SchemaVersion`] supported by Zed.
165const CURRENT_SCHEMA_VERSION: SchemaVersion = SchemaVersion(1);
166
167/// Extensions that should no longer be loaded or downloaded.
168///
169/// These snippets should no longer be downloaded or loaded, because their
170/// functionality has been integrated into the core editor.
171const SUPPRESSED_EXTENSIONS: &[&str] = &["snippets", "ruff", "ty", "basedpyright"];
172
173/// Returns the [`SchemaVersion`] range that is compatible with this version of Zed.
174pub fn schema_version_range() -> RangeInclusive<SchemaVersion> {
175 SchemaVersion::ZERO..=CURRENT_SCHEMA_VERSION
176}
177
178/// Returns whether the given extension version is compatible with this version of Zed.
179pub fn is_version_compatible(
180 release_channel: ReleaseChannel,
181 extension_version: &ExtensionMetadata,
182) -> bool {
183 let schema_version = extension_version.manifest.schema_version.unwrap_or(0);
184 if CURRENT_SCHEMA_VERSION.0 < schema_version {
185 return false;
186 }
187
188 if let Some(wasm_api_version) = extension_version
189 .manifest
190 .wasm_api_version
191 .as_ref()
192 .and_then(|wasm_api_version| Version::from_str(wasm_api_version).ok())
193 && !is_supported_wasm_api_version(release_channel, wasm_api_version)
194 {
195 return false;
196 }
197
198 true
199}
200
201pub struct ExtensionStore {
202 pub proxy: Arc<ExtensionHostProxy>,
203 pub builder: Arc<ExtensionBuilder>,
204 pub extension_index: ExtensionIndex,
205 pub fs: Arc<dyn Fs>,
206 pub http_client: Arc<HttpClientWithUrl>,
207 pub telemetry: Option<Arc<Telemetry>>,
208 pub reload_tx: UnboundedSender<Option<Arc<str>>>,
209 pub reload_complete_senders: Vec<oneshot::Sender<()>>,
210 pub installed_dir: PathBuf,
211 pub outstanding_operations: BTreeMap<Arc<str>, ExtensionOperation>,
212 pub index_path: PathBuf,
213 pub modified_extensions: HashSet<Arc<str>>,
214 pub wasm_host: Arc<WasmHost>,
215 pub wasm_extensions: Vec<(Arc<ExtensionManifest>, WasmExtension)>,
216 pub tasks: Vec<Task<()>>,
217 pub remote_clients: Vec<WeakEntity<RemoteClient>>,
218 pub ssh_registered_tx: UnboundedSender<()>,
219}
220
221#[derive(Clone, Copy)]
222pub enum ExtensionOperation {
223 Upgrade,
224 Install,
225 /// Auto-install from settings - triggers legacy LLM provider migrations
226 AutoInstall,
227 Remove,
228}
229
230#[derive(Clone)]
231pub enum Event {
232 ExtensionsUpdated,
233 StartedReloading,
234 ExtensionInstalled(Arc<str>),
235 ExtensionUninstalled(Arc<str>),
236 ExtensionFailedToLoad(Arc<str>),
237}
238
239impl EventEmitter<Event> for ExtensionStore {}
240
241struct GlobalExtensionStore(Entity<ExtensionStore>);
242
243impl Global for GlobalExtensionStore {}
244
245#[derive(Debug, Deserialize, Serialize, Default, PartialEq, Eq)]
246pub struct ExtensionIndex {
247 pub extensions: BTreeMap<Arc<str>, ExtensionIndexEntry>,
248 pub themes: BTreeMap<Arc<str>, ExtensionIndexThemeEntry>,
249 #[serde(default)]
250 pub icon_themes: BTreeMap<Arc<str>, ExtensionIndexIconThemeEntry>,
251 pub languages: BTreeMap<LanguageName, ExtensionIndexLanguageEntry>,
252}
253
254#[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]
255pub struct ExtensionIndexEntry {
256 pub manifest: Arc<ExtensionManifest>,
257 pub dev: bool,
258}
259
260#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
261pub struct ExtensionIndexThemeEntry {
262 pub extension: Arc<str>,
263 pub path: PathBuf,
264}
265
266#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
267pub struct ExtensionIndexIconThemeEntry {
268 pub extension: Arc<str>,
269 pub path: PathBuf,
270}
271
272#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Deserialize, Serialize)]
273pub struct ExtensionIndexLanguageEntry {
274 pub extension: Arc<str>,
275 pub path: PathBuf,
276 pub matcher: LanguageMatcher,
277 pub hidden: bool,
278 pub grammar: Option<Arc<str>>,
279}
280
281actions!(
282 zed,
283 [
284 /// Reloads all installed extensions.
285 ReloadExtensions
286 ]
287);
288
289pub fn init(
290 extension_host_proxy: Arc<ExtensionHostProxy>,
291 fs: Arc<dyn Fs>,
292 client: Arc<Client>,
293 node_runtime: NodeRuntime,
294 cx: &mut App,
295) {
296 let store = cx.new(move |cx| {
297 ExtensionStore::new(
298 paths::extensions_dir().clone(),
299 None,
300 extension_host_proxy,
301 fs,
302 client.http_client(),
303 client.http_client(),
304 Some(client.telemetry().clone()),
305 node_runtime,
306 cx,
307 )
308 });
309
310 cx.on_action(|_: &ReloadExtensions, cx| {
311 let store = cx.global::<GlobalExtensionStore>().0.clone();
312 store.update(cx, |store, cx| drop(store.reload(None, cx)));
313 });
314
315 cx.set_global(GlobalExtensionStore(store));
316}
317
318impl ExtensionStore {
319 pub fn try_global(cx: &App) -> Option<Entity<Self>> {
320 cx.try_global::<GlobalExtensionStore>()
321 .map(|store| store.0.clone())
322 }
323
324 pub fn global(cx: &App) -> Entity<Self> {
325 cx.global::<GlobalExtensionStore>().0.clone()
326 }
327
328 pub fn new(
329 extensions_dir: PathBuf,
330 build_dir: Option<PathBuf>,
331 extension_host_proxy: Arc<ExtensionHostProxy>,
332 fs: Arc<dyn Fs>,
333 http_client: Arc<HttpClientWithUrl>,
334 builder_client: Arc<dyn HttpClient>,
335 telemetry: Option<Arc<Telemetry>>,
336 node_runtime: NodeRuntime,
337 cx: &mut Context<Self>,
338 ) -> Self {
339 let work_dir = extensions_dir.join("work");
340 let build_dir = build_dir.unwrap_or_else(|| extensions_dir.join("build"));
341 let installed_dir = extensions_dir.join("installed");
342 let index_path = extensions_dir.join("index.json");
343
344 let (reload_tx, mut reload_rx) = unbounded();
345 let (connection_registered_tx, mut connection_registered_rx) = unbounded();
346 let mut this = Self {
347 proxy: extension_host_proxy.clone(),
348 extension_index: Default::default(),
349 installed_dir,
350 index_path,
351 builder: Arc::new(ExtensionBuilder::new(builder_client, build_dir)),
352 outstanding_operations: Default::default(),
353 modified_extensions: Default::default(),
354 reload_complete_senders: Vec::new(),
355 wasm_host: WasmHost::new(
356 fs.clone(),
357 http_client.clone(),
358 node_runtime,
359 extension_host_proxy,
360 work_dir,
361 cx,
362 ),
363 wasm_extensions: Vec::new(),
364 fs,
365 http_client,
366 telemetry,
367 reload_tx,
368 tasks: Vec::new(),
369
370 remote_clients: Default::default(),
371 ssh_registered_tx: connection_registered_tx,
372 };
373
374 // The extensions store maintains an index file, which contains a complete
375 // list of the installed extensions and the resources that they provide.
376 // This index is loaded synchronously on startup.
377 let (index_content, index_metadata, extensions_metadata) =
378 cx.background_executor().block(async {
379 futures::join!(
380 this.fs.load(&this.index_path),
381 this.fs.metadata(&this.index_path),
382 this.fs.metadata(&this.installed_dir),
383 )
384 });
385
386 // Normally, there is no need to rebuild the index. But if the index file
387 // is invalid or is out-of-date according to the filesystem mtimes, then
388 // it must be asynchronously rebuilt.
389 let mut extension_index = ExtensionIndex::default();
390 let mut extension_index_needs_rebuild = true;
391 if let Ok(index_content) = index_content
392 && let Some(index) = serde_json::from_str(&index_content).log_err()
393 {
394 extension_index = index;
395 if let (Ok(Some(index_metadata)), Ok(Some(extensions_metadata))) =
396 (index_metadata, extensions_metadata)
397 && index_metadata
398 .mtime
399 .bad_is_greater_than(extensions_metadata.mtime)
400 {
401 extension_index_needs_rebuild = false;
402 }
403 }
404
405 // Immediately load all of the extensions in the initial manifest. If the
406 // index needs to be rebuild, then enqueue
407 let load_initial_extensions = this.extensions_updated(extension_index, cx);
408 let mut reload_future = None;
409 if extension_index_needs_rebuild {
410 reload_future = Some(this.reload(None, cx));
411 }
412
413 cx.spawn(async move |this, cx| {
414 if let Some(future) = reload_future {
415 future.await;
416 }
417 this.update(cx, |this, cx| this.auto_install_extensions(cx))
418 .ok();
419 this.update(cx, |this, cx| this.check_for_updates(cx)).ok();
420 })
421 .detach();
422
423 // Perform all extension loading in a single task to ensure that we
424 // never attempt to simultaneously load/unload extensions from multiple
425 // parallel tasks.
426 this.tasks.push(cx.spawn(async move |this, cx| {
427 async move {
428 load_initial_extensions.await;
429
430 let mut index_changed = false;
431 let mut debounce_timer = cx.background_spawn(futures::future::pending()).fuse();
432 loop {
433 select_biased! {
434 _ = debounce_timer => {
435 if index_changed {
436 let index = this
437 .update(cx, |this, cx| this.rebuild_extension_index(cx))?
438 .await;
439 this.update(cx, |this, cx| this.extensions_updated(index, cx))?
440 .await;
441 index_changed = false;
442 }
443
444 Self::update_remote_clients(&this, cx).await?;
445 }
446 _ = connection_registered_rx.next() => {
447 debounce_timer = cx
448 .background_executor()
449 .timer(RELOAD_DEBOUNCE_DURATION)
450 .fuse();
451 }
452 extension_id = reload_rx.next() => {
453 let Some(extension_id) = extension_id else { break; };
454 this.update(cx, |this, _| {
455 this.modified_extensions.extend(extension_id);
456 })?;
457 index_changed = true;
458 debounce_timer = cx
459 .background_executor()
460 .timer(RELOAD_DEBOUNCE_DURATION)
461 .fuse();
462 }
463 }
464 }
465
466 anyhow::Ok(())
467 }
468 .map(drop)
469 .await;
470 }));
471
472 // Watch the installed extensions directory for changes. Whenever changes are
473 // detected, rebuild the extension index, and load/unload any extensions that
474 // have been added, removed, or modified.
475 this.tasks.push(cx.background_spawn({
476 let fs = this.fs.clone();
477 let reload_tx = this.reload_tx.clone();
478 let installed_dir = this.installed_dir.clone();
479 async move {
480 let (mut paths, _) = fs.watch(&installed_dir, FS_WATCH_LATENCY).await;
481 while let Some(events) = paths.next().await {
482 for event in events {
483 let Ok(event_path) = event.path.strip_prefix(&installed_dir) else {
484 continue;
485 };
486
487 if let Some(path::Component::Normal(extension_dir_name)) =
488 event_path.components().next()
489 && let Some(extension_id) = extension_dir_name.to_str()
490 {
491 reload_tx.unbounded_send(Some(extension_id.into())).ok();
492 }
493 }
494 }
495 }
496 }));
497
498 this
499 }
500
501 pub fn reload(
502 &mut self,
503 modified_extension: Option<Arc<str>>,
504 cx: &mut Context<Self>,
505 ) -> impl Future<Output = ()> + use<> {
506 let (tx, rx) = oneshot::channel();
507 self.reload_complete_senders.push(tx);
508 self.reload_tx
509 .unbounded_send(modified_extension)
510 .expect("reload task exited");
511 cx.emit(Event::StartedReloading);
512
513 async move {
514 rx.await.ok();
515 }
516 }
517
518 fn extensions_dir(&self) -> PathBuf {
519 self.installed_dir.clone()
520 }
521
522 pub fn outstanding_operations(&self) -> &BTreeMap<Arc<str>, ExtensionOperation> {
523 &self.outstanding_operations
524 }
525
526 pub fn installed_extensions(&self) -> &BTreeMap<Arc<str>, ExtensionIndexEntry> {
527 &self.extension_index.extensions
528 }
529
530 pub fn dev_extensions(&self) -> impl Iterator<Item = &Arc<ExtensionManifest>> {
531 self.extension_index
532 .extensions
533 .values()
534 .filter_map(|extension| extension.dev.then_some(&extension.manifest))
535 }
536
537 pub fn extension_manifest_for_id(&self, extension_id: &str) -> Option<&Arc<ExtensionManifest>> {
538 self.extension_index
539 .extensions
540 .get(extension_id)
541 .map(|extension| &extension.manifest)
542 }
543
544 /// Returns the names of themes provided by extensions.
545 pub fn extension_themes<'a>(
546 &'a self,
547 extension_id: &'a str,
548 ) -> impl Iterator<Item = &'a Arc<str>> {
549 self.extension_index
550 .themes
551 .iter()
552 .filter_map(|(name, theme)| theme.extension.as_ref().eq(extension_id).then_some(name))
553 }
554
555 /// Returns the path to the theme file within an extension, if there is an
556 /// extension that provides the theme.
557 pub fn path_to_extension_theme(&self, theme_name: &str) -> Option<PathBuf> {
558 let entry = self.extension_index.themes.get(theme_name)?;
559
560 Some(
561 self.extensions_dir()
562 .join(entry.extension.as_ref())
563 .join(&entry.path),
564 )
565 }
566
567 /// Returns the names of icon themes provided by extensions.
568 pub fn extension_icon_themes<'a>(
569 &'a self,
570 extension_id: &'a str,
571 ) -> impl Iterator<Item = &'a Arc<str>> {
572 self.extension_index
573 .icon_themes
574 .iter()
575 .filter_map(|(name, icon_theme)| {
576 icon_theme
577 .extension
578 .as_ref()
579 .eq(extension_id)
580 .then_some(name)
581 })
582 }
583
584 /// Returns the path to the icon theme file within an extension, if there is
585 /// an extension that provides the icon theme.
586 pub fn path_to_extension_icon_theme(
587 &self,
588 icon_theme_name: &str,
589 ) -> Option<(PathBuf, PathBuf)> {
590 let entry = self.extension_index.icon_themes.get(icon_theme_name)?;
591
592 let icon_theme_path = self
593 .extensions_dir()
594 .join(entry.extension.as_ref())
595 .join(&entry.path);
596 let icons_root_path = self.extensions_dir().join(entry.extension.as_ref());
597
598 Some((icon_theme_path, icons_root_path))
599 }
600
601 pub fn fetch_extensions(
602 &self,
603 search: Option<&str>,
604 provides_filter: Option<&BTreeSet<ExtensionProvides>>,
605 cx: &mut Context<Self>,
606 ) -> Task<Result<Vec<ExtensionMetadata>>> {
607 let version = CURRENT_SCHEMA_VERSION.to_string();
608 let mut query = vec![("max_schema_version", version.as_str())];
609 if let Some(search) = search {
610 query.push(("filter", search));
611 }
612
613 let provides_filter = provides_filter.map(|provides_filter| {
614 provides_filter
615 .iter()
616 .map(|provides| provides.to_string())
617 .collect::<Vec<_>>()
618 .join(",")
619 });
620 if let Some(provides_filter) = provides_filter.as_deref() {
621 query.push(("provides", provides_filter));
622 }
623
624 self.fetch_extensions_from_api("/extensions", &query, cx)
625 }
626
627 pub fn fetch_extensions_with_update_available(
628 &mut self,
629 cx: &mut Context<Self>,
630 ) -> Task<Result<Vec<ExtensionMetadata>>> {
631 let schema_versions = schema_version_range();
632 let wasm_api_versions = wasm_api_version_range(ReleaseChannel::global(cx));
633 let extension_settings = ExtensionSettings::get_global(cx);
634 let extension_ids = self
635 .extension_index
636 .extensions
637 .iter()
638 .filter(|(id, entry)| !entry.dev && extension_settings.should_auto_update(id))
639 .map(|(id, _)| id.as_ref())
640 .collect::<Vec<_>>()
641 .join(",");
642 let task = self.fetch_extensions_from_api(
643 "/extensions/updates",
644 &[
645 ("min_schema_version", &schema_versions.start().to_string()),
646 ("max_schema_version", &schema_versions.end().to_string()),
647 (
648 "min_wasm_api_version",
649 &wasm_api_versions.start().to_string(),
650 ),
651 ("max_wasm_api_version", &wasm_api_versions.end().to_string()),
652 ("ids", &extension_ids),
653 ],
654 cx,
655 );
656 cx.spawn(async move |this, cx| {
657 let extensions = task.await?;
658 this.update(cx, |this, _cx| {
659 extensions
660 .into_iter()
661 .filter(|extension| {
662 this.extension_index
663 .extensions
664 .get(&extension.id)
665 .is_none_or(|installed_extension| {
666 installed_extension.manifest.version != extension.manifest.version
667 })
668 })
669 .collect()
670 })
671 })
672 }
673
674 pub fn fetch_extension_versions(
675 &self,
676 extension_id: &str,
677 cx: &mut Context<Self>,
678 ) -> Task<Result<Vec<ExtensionMetadata>>> {
679 self.fetch_extensions_from_api(&format!("/extensions/{extension_id}"), &[], cx)
680 }
681
682 /// Installs any extensions that should be included with Zed by default.
683 ///
684 /// This can be used to make certain functionality provided by extensions
685 /// available out-of-the-box.
686 pub fn auto_install_extensions(&mut self, cx: &mut Context<Self>) {
687 if cfg!(test) {
688 return;
689 }
690
691 let extension_settings = ExtensionSettings::get_global(cx);
692
693 let extensions_to_install = extension_settings
694 .auto_install_extensions
695 .keys()
696 .filter(|extension_id| extension_settings.should_auto_install(extension_id))
697 .filter(|extension_id| {
698 let is_already_installed = self
699 .extension_index
700 .extensions
701 .contains_key(extension_id.as_ref());
702 !is_already_installed && !SUPPRESSED_EXTENSIONS.contains(&extension_id.as_ref())
703 })
704 .cloned()
705 .collect::<Vec<_>>();
706
707 cx.spawn(async move |this, cx| {
708 for extension_id in extensions_to_install {
709 // When enabled, this checks if an extension exists locally in the repo's extensions/
710 // directory and installs it as a dev extension instead of fetching from the registry.
711 // This is useful for testing auto-installed extensions before they've been published.
712 // Set to `true` only during local development/testing of new auto-install extensions.
713 #[cfg(debug_assertions)]
714 const DEBUG_ALLOW_UNPUBLISHED_AUTO_EXTENSIONS: bool = false;
715
716 #[cfg(debug_assertions)]
717 if DEBUG_ALLOW_UNPUBLISHED_AUTO_EXTENSIONS {
718 let local_extension_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
719 .parent()
720 .unwrap()
721 .parent()
722 .unwrap()
723 .join("extensions")
724 .join(extension_id.as_ref());
725
726 if local_extension_path.exists() {
727 // Force-remove existing extension directory if it exists and isn't a symlink
728 // This handles the case where the extension was previously installed from the registry
729 if let Some(installed_dir) = this
730 .update(cx, |this, _cx| this.installed_dir.clone())
731 .ok()
732 {
733 let existing_path = installed_dir.join(extension_id.as_ref());
734 if existing_path.exists() {
735 let metadata = std::fs::symlink_metadata(&existing_path);
736 let is_symlink = metadata.map(|m| m.is_symlink()).unwrap_or(false);
737 if !is_symlink {
738 if let Err(e) = std::fs::remove_dir_all(&existing_path) {
739 log::error!(
740 "Failed to remove existing extension directory {:?}: {}",
741 existing_path,
742 e
743 );
744 }
745 }
746 }
747 }
748
749 if let Some(task) = this
750 .update(cx, |this, cx| {
751 this.install_dev_extension(local_extension_path, cx)
752 })
753 .ok()
754 {
755 task.await.log_err();
756 }
757 continue;
758 }
759 }
760
761 this.update(cx, |this, cx| {
762 this.auto_install_latest_extension(extension_id.clone(), cx);
763 })
764 .ok();
765 }
766 })
767 .detach();
768 }
769
770 pub fn check_for_updates(&mut self, cx: &mut Context<Self>) {
771 let task = self.fetch_extensions_with_update_available(cx);
772 cx.spawn(async move |this, cx| Self::upgrade_extensions(this, task.await?, cx).await)
773 .detach();
774 }
775
776 async fn upgrade_extensions(
777 this: WeakEntity<Self>,
778 extensions: Vec<ExtensionMetadata>,
779 cx: &mut AsyncApp,
780 ) -> Result<()> {
781 for extension in extensions {
782 let task = this.update(cx, |this, cx| {
783 if let Some(installed_extension) =
784 this.extension_index.extensions.get(&extension.id)
785 {
786 let installed_version =
787 Version::from_str(&installed_extension.manifest.version).ok()?;
788 let latest_version = Version::from_str(&extension.manifest.version).ok()?;
789
790 if installed_version >= latest_version {
791 return None;
792 }
793 }
794
795 Some(this.upgrade_extension(extension.id, extension.manifest.version, cx))
796 })?;
797
798 if let Some(task) = task {
799 task.await.log_err();
800 }
801 }
802 anyhow::Ok(())
803 }
804
805 fn fetch_extensions_from_api(
806 &self,
807 path: &str,
808 query: &[(&str, &str)],
809 cx: &mut Context<ExtensionStore>,
810 ) -> Task<Result<Vec<ExtensionMetadata>>> {
811 let url = self.http_client.build_zed_api_url(path, query);
812 let http_client = self.http_client.clone();
813 cx.spawn(async move |_, _| {
814 let mut response = http_client
815 .get(url?.as_ref(), AsyncBody::empty(), true)
816 .await?;
817
818 let mut body = Vec::new();
819 response
820 .body_mut()
821 .read_to_end(&mut body)
822 .await
823 .context("error reading extensions")?;
824
825 if response.status().is_client_error() {
826 let text = String::from_utf8_lossy(body.as_slice());
827 bail!(
828 "status error {}, response: {text:?}",
829 response.status().as_u16()
830 );
831 }
832
833 let mut response: GetExtensionsResponse = serde_json::from_slice(&body)?;
834
835 response
836 .data
837 .retain(|extension| !SUPPRESSED_EXTENSIONS.contains(&extension.id.as_ref()));
838
839 Ok(response.data)
840 })
841 }
842
843 pub fn install_extension(
844 &mut self,
845 extension_id: Arc<str>,
846 version: Arc<str>,
847 cx: &mut Context<Self>,
848 ) {
849 self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Install, cx)
850 .detach_and_log_err(cx);
851 }
852
853 fn install_or_upgrade_extension_at_endpoint(
854 &mut self,
855 extension_id: Arc<str>,
856 url: Url,
857 operation: ExtensionOperation,
858 cx: &mut Context<Self>,
859 ) -> Task<Result<()>> {
860 let extension_dir = self.installed_dir.join(extension_id.as_ref());
861 let http_client = self.http_client.clone();
862 let fs = self.fs.clone();
863
864 match self.outstanding_operations.entry(extension_id.clone()) {
865 btree_map::Entry::Occupied(_) => return Task::ready(Ok(())),
866 btree_map::Entry::Vacant(e) => e.insert(operation),
867 };
868 cx.notify();
869
870 cx.spawn(async move |this, cx| {
871 let _finish = cx.on_drop(&this, {
872 let extension_id = extension_id.clone();
873 move |this, cx| {
874 this.outstanding_operations.remove(extension_id.as_ref());
875 cx.notify();
876 }
877 });
878
879 let mut response = http_client
880 .get(url.as_ref(), Default::default(), true)
881 .await
882 .context("downloading extension")?;
883
884 fs.remove_dir(
885 &extension_dir,
886 RemoveOptions {
887 recursive: true,
888 ignore_if_not_exists: true,
889 },
890 )
891 .await?;
892
893 let content_length = response
894 .headers()
895 .get(http_client::http::header::CONTENT_LENGTH)
896 .and_then(|value| value.to_str().ok()?.parse::<usize>().ok());
897
898 let mut body = BufReader::new(response.body_mut());
899 let mut tar_gz_bytes = Vec::new();
900 body.read_to_end(&mut tar_gz_bytes).await?;
901
902 if let Some(content_length) = content_length {
903 let actual_len = tar_gz_bytes.len();
904 if content_length != actual_len {
905 bail!(concat!(
906 "downloaded extension size {actual_len} ",
907 "does not match content length {content_length}"
908 ));
909 }
910 }
911 let decompressed_bytes = GzipDecoder::new(BufReader::new(tar_gz_bytes.as_slice()));
912 let archive = Archive::new(decompressed_bytes);
913 archive.unpack(extension_dir).await?;
914 this.update(cx, |this, cx| this.reload(Some(extension_id.clone()), cx))?
915 .await;
916
917 if matches!(
918 operation,
919 ExtensionOperation::Install | ExtensionOperation::AutoInstall
920 ) {
921 this.update(cx, |this, cx| {
922 cx.emit(Event::ExtensionInstalled(extension_id.clone()));
923 if let Some(events) = ExtensionEvents::try_global(cx)
924 && let Some(manifest) = this.extension_manifest_for_id(&extension_id)
925 {
926 events.update(cx, |this, cx| {
927 this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx)
928 });
929 }
930
931 // Run legacy LLM provider migrations only for auto-installed extensions
932 if matches!(operation, ExtensionOperation::AutoInstall) {
933 if let Some(manifest) = this.extension_manifest_for_id(&extension_id) {
934 migrate_legacy_llm_provider_env_var(&manifest, cx);
935 }
936 copilot_migration::migrate_copilot_credentials_if_needed(&extension_id, cx);
937 anthropic_migration::migrate_anthropic_credentials_if_needed(
938 &extension_id,
939 cx,
940 );
941 google_ai_migration::migrate_google_ai_credentials_if_needed(
942 &extension_id,
943 cx,
944 );
945 openai_migration::migrate_openai_credentials_if_needed(&extension_id, cx);
946 open_router_migration::migrate_open_router_credentials_if_needed(
947 &extension_id,
948 cx,
949 );
950 }
951 })
952 .ok();
953 }
954
955 anyhow::Ok(())
956 })
957 }
958
959 pub fn install_latest_extension(&mut self, extension_id: Arc<str>, cx: &mut Context<Self>) {
960 self.install_latest_extension_with_operation(extension_id, ExtensionOperation::Install, cx);
961 }
962
963 /// Auto-install an extension, triggering legacy LLM provider migrations.
964 fn auto_install_latest_extension(&mut self, extension_id: Arc<str>, cx: &mut Context<Self>) {
965 self.install_latest_extension_with_operation(
966 extension_id,
967 ExtensionOperation::AutoInstall,
968 cx,
969 );
970 }
971
972 fn install_latest_extension_with_operation(
973 &mut self,
974 extension_id: Arc<str>,
975 operation: ExtensionOperation,
976 cx: &mut Context<Self>,
977 ) {
978 let schema_versions = schema_version_range();
979 let wasm_api_versions = wasm_api_version_range(ReleaseChannel::global(cx));
980
981 let Some(url) = self
982 .http_client
983 .build_zed_api_url(
984 &format!("/extensions/{extension_id}/download"),
985 &[
986 ("min_schema_version", &schema_versions.start().to_string()),
987 ("max_schema_version", &schema_versions.end().to_string()),
988 (
989 "min_wasm_api_version",
990 &wasm_api_versions.start().to_string(),
991 ),
992 ("max_wasm_api_version", &wasm_api_versions.end().to_string()),
993 ],
994 )
995 .log_err()
996 else {
997 return;
998 };
999
1000 self.install_or_upgrade_extension_at_endpoint(extension_id, url, operation, cx)
1001 .detach_and_log_err(cx);
1002 }
1003
1004 pub fn upgrade_extension(
1005 &mut self,
1006 extension_id: Arc<str>,
1007 version: Arc<str>,
1008 cx: &mut Context<Self>,
1009 ) -> Task<Result<()>> {
1010 self.install_or_upgrade_extension(extension_id, version, ExtensionOperation::Upgrade, cx)
1011 }
1012
1013 fn install_or_upgrade_extension(
1014 &mut self,
1015 extension_id: Arc<str>,
1016 version: Arc<str>,
1017 operation: ExtensionOperation,
1018 cx: &mut Context<Self>,
1019 ) -> Task<Result<()>> {
1020 let Some(url) = self
1021 .http_client
1022 .build_zed_api_url(
1023 &format!("/extensions/{extension_id}/{version}/download"),
1024 &[],
1025 )
1026 .log_err()
1027 else {
1028 return Task::ready(Ok(()));
1029 };
1030
1031 self.install_or_upgrade_extension_at_endpoint(extension_id, url, operation, cx)
1032 }
1033
1034 pub fn uninstall_extension(
1035 &mut self,
1036 extension_id: Arc<str>,
1037 cx: &mut Context<Self>,
1038 ) -> Task<Result<()>> {
1039 let extension_dir = self.installed_dir.join(extension_id.as_ref());
1040 let work_dir = self.wasm_host.work_dir.join(extension_id.as_ref());
1041 let fs = self.fs.clone();
1042
1043 let extension_manifest = self.extension_manifest_for_id(&extension_id).cloned();
1044
1045 match self.outstanding_operations.entry(extension_id.clone()) {
1046 btree_map::Entry::Occupied(_) => return Task::ready(Ok(())),
1047 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Remove),
1048 };
1049
1050 cx.spawn(async move |extension_store, cx| {
1051 let _finish = cx.on_drop(&extension_store, {
1052 let extension_id = extension_id.clone();
1053 move |this, cx| {
1054 this.outstanding_operations.remove(extension_id.as_ref());
1055 cx.notify();
1056 }
1057 });
1058
1059 fs.remove_dir(
1060 &extension_dir,
1061 RemoveOptions {
1062 recursive: true,
1063 ignore_if_not_exists: true,
1064 },
1065 )
1066 .await
1067 .with_context(|| format!("Removing extension dir {extension_dir:?}"))?;
1068
1069 extension_store
1070 .update(cx, |extension_store, cx| extension_store.reload(None, cx))?
1071 .await;
1072
1073 // There's a race between wasm extension fully stopping and the directory removal.
1074 // On Windows, it's impossible to remove a directory that has a process running in it.
1075 for i in 0..3 {
1076 cx.background_executor()
1077 .timer(Duration::from_millis(i * 100))
1078 .await;
1079 let removal_result = fs
1080 .remove_dir(
1081 &work_dir,
1082 RemoveOptions {
1083 recursive: true,
1084 ignore_if_not_exists: true,
1085 },
1086 )
1087 .await;
1088 match removal_result {
1089 Ok(()) => break,
1090 Err(e) => {
1091 if i == 2 {
1092 log::error!("Failed to remove extension work dir {work_dir:?} : {e}");
1093 }
1094 }
1095 }
1096 }
1097
1098 extension_store.update(cx, |_, cx| {
1099 cx.emit(Event::ExtensionUninstalled(extension_id.clone()));
1100 if let Some(events) = ExtensionEvents::try_global(cx)
1101 && let Some(manifest) = extension_manifest
1102 {
1103 events.update(cx, |this, cx| {
1104 this.emit(extension::Event::ExtensionUninstalled(manifest.clone()), cx)
1105 });
1106 }
1107 })?;
1108
1109 anyhow::Ok(())
1110 })
1111 }
1112
1113 pub fn install_dev_extension(
1114 &mut self,
1115 extension_source_path: PathBuf,
1116 cx: &mut Context<Self>,
1117 ) -> Task<Result<()>> {
1118 let extensions_dir = self.extensions_dir();
1119 let fs = self.fs.clone();
1120 let builder = self.builder.clone();
1121
1122 cx.spawn(async move |this, cx| {
1123 let mut extension_manifest =
1124 ExtensionManifest::load(fs.clone(), &extension_source_path).await?;
1125 let extension_id = extension_manifest.id.clone();
1126
1127 if let Some(uninstall_task) = this
1128 .update(cx, |this, cx| {
1129 this.extension_index
1130 .extensions
1131 .get(extension_id.as_ref())
1132 .is_some_and(|index_entry| !index_entry.dev)
1133 .then(|| this.uninstall_extension(extension_id.clone(), cx))
1134 })
1135 .ok()
1136 .flatten()
1137 {
1138 uninstall_task.await.log_err();
1139 }
1140
1141 if !this.update(cx, |this, cx| {
1142 match this.outstanding_operations.entry(extension_id.clone()) {
1143 btree_map::Entry::Occupied(_) => return false,
1144 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Install),
1145 };
1146 cx.notify();
1147 true
1148 })? {
1149 return Ok(());
1150 }
1151
1152 let _finish = cx.on_drop(&this, {
1153 let extension_id = extension_id.clone();
1154 move |this, cx| {
1155 this.outstanding_operations.remove(extension_id.as_ref());
1156 cx.notify();
1157 }
1158 });
1159
1160 cx.background_spawn({
1161 let extension_source_path = extension_source_path.clone();
1162 let fs = fs.clone();
1163 async move {
1164 builder
1165 .compile_extension(
1166 &extension_source_path,
1167 &mut extension_manifest,
1168 CompileExtensionOptions { release: false },
1169 fs,
1170 )
1171 .await
1172 }
1173 })
1174 .await
1175 .inspect_err(|error| {
1176 util::log_err(error);
1177 })?;
1178
1179 let output_path = &extensions_dir.join(extension_id.as_ref());
1180 if let Some(metadata) = fs.metadata(output_path).await? {
1181 if metadata.is_symlink {
1182 fs.remove_file(
1183 output_path,
1184 RemoveOptions {
1185 recursive: false,
1186 ignore_if_not_exists: true,
1187 },
1188 )
1189 .await?;
1190 } else {
1191 bail!("extension {extension_id} is still installed");
1192 }
1193 }
1194
1195 fs.create_symlink(output_path, extension_source_path.clone())
1196 .await?;
1197
1198 // Re-load manifest and run migrations before reload so settings are updated before providers are registered
1199 let manifest_for_migration =
1200 ExtensionManifest::load(fs.clone(), &extension_source_path).await?;
1201 this.update(cx, |_this, cx| {
1202 migrate_legacy_llm_provider_env_var(&manifest_for_migration, cx);
1203 // Also run credential migrations for dev extensions
1204 copilot_migration::migrate_copilot_credentials_if_needed(
1205 manifest_for_migration.id.as_ref(),
1206 cx,
1207 );
1208 anthropic_migration::migrate_anthropic_credentials_if_needed(
1209 manifest_for_migration.id.as_ref(),
1210 cx,
1211 );
1212 google_ai_migration::migrate_google_ai_credentials_if_needed(
1213 manifest_for_migration.id.as_ref(),
1214 cx,
1215 );
1216 openai_migration::migrate_openai_credentials_if_needed(
1217 manifest_for_migration.id.as_ref(),
1218 cx,
1219 );
1220 open_router_migration::migrate_open_router_credentials_if_needed(
1221 manifest_for_migration.id.as_ref(),
1222 cx,
1223 );
1224 })?;
1225
1226 this.update(cx, |this, cx| this.reload(None, cx))?.await;
1227 this.update(cx, |this, cx| {
1228 cx.emit(Event::ExtensionInstalled(extension_id.clone()));
1229 if let Some(events) = ExtensionEvents::try_global(cx)
1230 && let Some(manifest) = this.extension_manifest_for_id(&extension_id)
1231 {
1232 events.update(cx, |this, cx| {
1233 this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx)
1234 });
1235 }
1236 })?;
1237
1238 Ok(())
1239 })
1240 }
1241
1242 pub fn rebuild_dev_extension(&mut self, extension_id: Arc<str>, cx: &mut Context<Self>) {
1243 let path = self.installed_dir.join(extension_id.as_ref());
1244 let builder = self.builder.clone();
1245 let fs = self.fs.clone();
1246
1247 match self.outstanding_operations.entry(extension_id.clone()) {
1248 btree_map::Entry::Occupied(_) => return,
1249 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Upgrade),
1250 };
1251
1252 cx.notify();
1253 let compile = cx.background_spawn(async move {
1254 let mut manifest = ExtensionManifest::load(fs.clone(), &path).await?;
1255 builder
1256 .compile_extension(
1257 &path,
1258 &mut manifest,
1259 CompileExtensionOptions { release: true },
1260 fs,
1261 )
1262 .await
1263 });
1264
1265 cx.spawn(async move |this, cx| {
1266 let result = compile.await;
1267
1268 this.update(cx, |this, cx| {
1269 this.outstanding_operations.remove(&extension_id);
1270 cx.notify();
1271 })?;
1272
1273 if result.is_ok() {
1274 this.update(cx, |this, cx| this.reload(Some(extension_id), cx))?
1275 .await;
1276 }
1277
1278 result
1279 })
1280 .detach_and_log_err(cx)
1281 }
1282
1283 /// Updates the set of installed extensions.
1284 ///
1285 /// First, this unloads any themes, languages, or grammars that are
1286 /// no longer in the manifest, or whose files have changed on disk.
1287 /// Then it loads any themes, languages, or grammars that are newly
1288 /// added to the manifest, or whose files have changed on disk.
1289 fn extensions_updated(
1290 &mut self,
1291 mut new_index: ExtensionIndex,
1292 cx: &mut Context<Self>,
1293 ) -> Task<()> {
1294 let old_index = &self.extension_index;
1295
1296 new_index
1297 .extensions
1298 .retain(|extension_id, _| !SUPPRESSED_EXTENSIONS.contains(&extension_id.as_ref()));
1299
1300 // Determine which extensions need to be loaded and unloaded, based
1301 // on the changes to the manifest and the extensions that we know have been
1302 // modified.
1303 let mut extensions_to_unload = Vec::default();
1304 let mut extensions_to_load = Vec::default();
1305 {
1306 let mut old_keys = old_index.extensions.iter().peekable();
1307 let mut new_keys = new_index.extensions.iter().peekable();
1308 loop {
1309 match (old_keys.peek(), new_keys.peek()) {
1310 (None, None) => break,
1311 (None, Some(_)) => {
1312 extensions_to_load.push(new_keys.next().unwrap().0.clone());
1313 }
1314 (Some(_), None) => {
1315 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
1316 }
1317 (Some((old_key, _)), Some((new_key, _))) => match old_key.cmp(new_key) {
1318 Ordering::Equal => {
1319 let (old_key, old_value) = old_keys.next().unwrap();
1320 let (new_key, new_value) = new_keys.next().unwrap();
1321 if old_value != new_value || self.modified_extensions.contains(old_key)
1322 {
1323 extensions_to_unload.push(old_key.clone());
1324 extensions_to_load.push(new_key.clone());
1325 }
1326 }
1327 Ordering::Less => {
1328 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
1329 }
1330 Ordering::Greater => {
1331 extensions_to_load.push(new_keys.next().unwrap().0.clone());
1332 }
1333 },
1334 }
1335 }
1336 self.modified_extensions.clear();
1337 }
1338
1339 if extensions_to_load.is_empty() && extensions_to_unload.is_empty() {
1340 self.reload_complete_senders.clear();
1341 return Task::ready(());
1342 }
1343
1344 let extension_ids = extensions_to_load
1345 .iter()
1346 .filter_map(|id| {
1347 Some((
1348 id.clone(),
1349 new_index.extensions.get(id)?.manifest.version.clone(),
1350 ))
1351 })
1352 .collect::<Vec<_>>();
1353
1354 telemetry::event!("Extensions Loaded", id_and_versions = extension_ids);
1355
1356 let themes_to_remove = old_index
1357 .themes
1358 .iter()
1359 .filter_map(|(name, entry)| {
1360 if extensions_to_unload.contains(&entry.extension) {
1361 Some(name.clone().into())
1362 } else {
1363 None
1364 }
1365 })
1366 .collect::<Vec<_>>();
1367 let icon_themes_to_remove = old_index
1368 .icon_themes
1369 .iter()
1370 .filter_map(|(name, entry)| {
1371 if extensions_to_unload.contains(&entry.extension) {
1372 Some(name.clone().into())
1373 } else {
1374 None
1375 }
1376 })
1377 .collect::<Vec<_>>();
1378 let languages_to_remove = old_index
1379 .languages
1380 .iter()
1381 .filter_map(|(name, entry)| {
1382 if extensions_to_unload.contains(&entry.extension) {
1383 Some(name.clone())
1384 } else {
1385 None
1386 }
1387 })
1388 .collect::<Vec<_>>();
1389 let mut grammars_to_remove = Vec::new();
1390 let mut server_removal_tasks = Vec::with_capacity(extensions_to_unload.len());
1391 for extension_id in &extensions_to_unload {
1392 let Some(extension) = old_index.extensions.get(extension_id) else {
1393 continue;
1394 };
1395 grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
1396 for (language_server_name, config) in &extension.manifest.language_servers {
1397 for language in config.languages() {
1398 server_removal_tasks.push(self.proxy.remove_language_server(
1399 &language,
1400 language_server_name,
1401 cx,
1402 ));
1403 }
1404 }
1405
1406 for server_id in extension.manifest.context_servers.keys() {
1407 self.proxy.unregister_context_server(server_id.clone(), cx);
1408 }
1409 for adapter in extension.manifest.debug_adapters.keys() {
1410 self.proxy.unregister_debug_adapter(adapter.clone());
1411 }
1412 for locator in extension.manifest.debug_locators.keys() {
1413 self.proxy.unregister_debug_locator(locator.clone());
1414 }
1415 for command_name in extension.manifest.slash_commands.keys() {
1416 self.proxy.unregister_slash_command(command_name.clone());
1417 }
1418 for provider_id in extension.manifest.language_model_providers.keys() {
1419 let full_provider_id: Arc<str> = format!("{}:{}", extension_id, provider_id).into();
1420 self.proxy
1421 .unregister_language_model_provider(full_provider_id, cx);
1422 }
1423 }
1424
1425 self.wasm_extensions
1426 .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
1427 self.proxy.remove_user_themes(themes_to_remove);
1428 self.proxy.remove_icon_themes(icon_themes_to_remove);
1429 self.proxy
1430 .remove_languages(&languages_to_remove, &grammars_to_remove);
1431
1432 let mut grammars_to_add = Vec::new();
1433 let mut themes_to_add = Vec::new();
1434 let mut icon_themes_to_add = Vec::new();
1435 let mut snippets_to_add = Vec::new();
1436 for extension_id in &extensions_to_load {
1437 let Some(extension) = new_index.extensions.get(extension_id) else {
1438 continue;
1439 };
1440
1441 grammars_to_add.extend(extension.manifest.grammars.keys().map(|grammar_name| {
1442 let mut grammar_path = self.installed_dir.clone();
1443 grammar_path.extend([extension_id.as_ref(), "grammars"]);
1444 grammar_path.push(grammar_name.as_ref());
1445 grammar_path.set_extension("wasm");
1446 (grammar_name.clone(), grammar_path)
1447 }));
1448 themes_to_add.extend(extension.manifest.themes.iter().map(|theme_path| {
1449 let mut path = self.installed_dir.clone();
1450 path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
1451 path
1452 }));
1453 icon_themes_to_add.extend(extension.manifest.icon_themes.iter().map(
1454 |icon_theme_path| {
1455 let mut path = self.installed_dir.clone();
1456 path.extend([Path::new(extension_id.as_ref()), icon_theme_path.as_path()]);
1457
1458 let mut icons_root_path = self.installed_dir.clone();
1459 icons_root_path.extend([Path::new(extension_id.as_ref())]);
1460
1461 (path, icons_root_path)
1462 },
1463 ));
1464 snippets_to_add.extend(extension.manifest.snippets.iter().map(|snippets_path| {
1465 let mut path = self.installed_dir.clone();
1466 path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
1467 path
1468 }));
1469 }
1470
1471 self.proxy.register_grammars(grammars_to_add);
1472 let languages_to_add = new_index
1473 .languages
1474 .iter()
1475 .filter(|(_, entry)| extensions_to_load.contains(&entry.extension))
1476 .collect::<Vec<_>>();
1477 for (language_name, language) in languages_to_add {
1478 let mut language_path = self.installed_dir.clone();
1479 language_path.extend([
1480 Path::new(language.extension.as_ref()),
1481 language.path.as_path(),
1482 ]);
1483 self.proxy.register_language(
1484 language_name.clone(),
1485 language.grammar.clone(),
1486 language.matcher.clone(),
1487 language.hidden,
1488 Arc::new(move || {
1489 let config = std::fs::read_to_string(language_path.join("config.toml"))?;
1490 let config: LanguageConfig = ::toml::from_str(&config)?;
1491 let queries = load_plugin_queries(&language_path);
1492 let context_provider =
1493 std::fs::read_to_string(language_path.join("tasks.json"))
1494 .ok()
1495 .and_then(|contents| {
1496 let definitions =
1497 serde_json_lenient::from_str(&contents).log_err()?;
1498 Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
1499 });
1500
1501 Ok(LoadedLanguage {
1502 config,
1503 queries,
1504 context_provider,
1505 toolchain_provider: None,
1506 manifest_name: None,
1507 })
1508 }),
1509 );
1510 }
1511
1512 let fs = self.fs.clone();
1513 let wasm_host = self.wasm_host.clone();
1514 let root_dir = self.installed_dir.clone();
1515 let proxy = self.proxy.clone();
1516 let extension_entries = extensions_to_load
1517 .iter()
1518 .filter_map(|name| new_index.extensions.get(name).cloned())
1519 .collect::<Vec<_>>();
1520 self.extension_index = new_index;
1521 cx.notify();
1522 cx.emit(Event::ExtensionsUpdated);
1523
1524 cx.spawn(async move |this, cx| {
1525 cx.background_spawn({
1526 let fs = fs.clone();
1527 async move {
1528 let _ = join_all(server_removal_tasks).await;
1529 for theme_path in themes_to_add {
1530 proxy
1531 .load_user_theme(theme_path, fs.clone())
1532 .await
1533 .log_err();
1534 }
1535
1536 for (icon_theme_path, icons_root_path) in icon_themes_to_add {
1537 proxy
1538 .load_icon_theme(icon_theme_path, icons_root_path, fs.clone())
1539 .await
1540 .log_err();
1541 }
1542
1543 for snippets_path in &snippets_to_add {
1544 match fs
1545 .load(snippets_path)
1546 .await
1547 .with_context(|| format!("Loading snippets from {snippets_path:?}"))
1548 {
1549 Ok(snippets_contents) => {
1550 proxy
1551 .register_snippet(snippets_path, &snippets_contents)
1552 .log_err();
1553 }
1554 Err(e) => log::error!("Cannot load snippets: {e:#}"),
1555 }
1556 }
1557 }
1558 })
1559 .await;
1560
1561 let mut wasm_extensions: Vec<(
1562 Arc<ExtensionManifest>,
1563 WasmExtension,
1564 Vec<LlmProviderWithModels>,
1565 )> = Vec::new();
1566 for extension in extension_entries {
1567 if extension.manifest.lib.kind.is_none() {
1568 continue;
1569 };
1570
1571 let extension_path = root_dir.join(extension.manifest.id.as_ref());
1572 let wasm_extension = WasmExtension::load(
1573 &extension_path,
1574 &extension.manifest,
1575 wasm_host.clone(),
1576 cx,
1577 )
1578 .await
1579 .with_context(|| format!("Loading extension from {extension_path:?}"));
1580
1581 match wasm_extension {
1582 Ok(wasm_extension) => {
1583 // Query for LLM providers if the manifest declares any
1584 let mut llm_providers_with_models = Vec::new();
1585 if !extension.manifest.language_model_providers.is_empty() {
1586 let providers_result = wasm_extension
1587 .call(|ext, store| {
1588 async move { ext.call_llm_providers(store).await }.boxed()
1589 })
1590 .await;
1591
1592 if let Ok(Ok(providers)) = providers_result {
1593 for provider_info in providers {
1594 let models_result = wasm_extension
1595 .call({
1596 let provider_id = provider_info.id.clone();
1597 |ext, store| {
1598 async move {
1599 ext.call_llm_provider_models(store, &provider_id)
1600 .await
1601 }
1602 .boxed()
1603 }
1604 })
1605 .await;
1606
1607 let models: Vec<LlmModelInfo> = match models_result {
1608 Ok(Ok(Ok(models))) => models,
1609 Ok(Ok(Err(e))) => {
1610 log::error!(
1611 "Failed to get models for LLM provider {} in extension {}: {}",
1612 provider_info.id,
1613 extension.manifest.id,
1614 e
1615 );
1616 Vec::new()
1617 }
1618 Ok(Err(e)) => {
1619 log::error!(
1620 "Wasm error calling llm_provider_models for {} in extension {}: {:?}",
1621 provider_info.id,
1622 extension.manifest.id,
1623 e
1624 );
1625 Vec::new()
1626 }
1627 Err(e) => {
1628 log::error!(
1629 "Extension call failed for llm_provider_models {} in extension {}: {:?}",
1630 provider_info.id,
1631 extension.manifest.id,
1632 e
1633 );
1634 Vec::new()
1635 }
1636 };
1637
1638 // Query initial authentication state
1639 let is_authenticated = wasm_extension
1640 .call({
1641 let provider_id = provider_info.id.clone();
1642 |ext, store| {
1643 async move {
1644 ext.call_llm_provider_is_authenticated(
1645 store,
1646 &provider_id,
1647 )
1648 .await
1649 }
1650 .boxed()
1651 }
1652 })
1653 .await
1654 .unwrap_or(Ok(false))
1655 .unwrap_or(false);
1656
1657 // Resolve icon path if provided
1658 let icon_path = provider_info.icon.as_ref().map(|icon| {
1659 let icon_file_path = extension_path.join(icon);
1660 // Canonicalize to resolve symlinks (dev extensions are symlinked)
1661 let absolute_icon_path = icon_file_path
1662 .canonicalize()
1663 .unwrap_or(icon_file_path)
1664 .to_string_lossy()
1665 .to_string();
1666 SharedString::from(absolute_icon_path)
1667 });
1668
1669 let provider_id_arc: Arc<str> =
1670 provider_info.id.as_str().into();
1671 let auth_config = extension
1672 .manifest
1673 .language_model_providers
1674 .get(&provider_id_arc)
1675 .and_then(|entry| entry.auth.clone());
1676
1677 llm_providers_with_models.push(LlmProviderWithModels {
1678 provider_info,
1679 models,
1680 is_authenticated,
1681 icon_path,
1682 auth_config,
1683 });
1684 }
1685 } else {
1686 log::error!(
1687 "Failed to get LLM providers from extension {}: {:?}",
1688 extension.manifest.id,
1689 providers_result
1690 );
1691 }
1692 }
1693
1694 wasm_extensions.push((
1695 extension.manifest.clone(),
1696 wasm_extension,
1697 llm_providers_with_models,
1698 ))
1699 }
1700 Err(e) => {
1701 log::error!(
1702 "Failed to load extension: {}, {:#}",
1703 extension.manifest.id,
1704 e
1705 );
1706 this.update(cx, |_, cx| {
1707 cx.emit(Event::ExtensionFailedToLoad(extension.manifest.id.clone()))
1708 })
1709 .ok();
1710 }
1711 }
1712 }
1713
1714 this.update(cx, |this, cx| {
1715 this.reload_complete_senders.clear();
1716
1717 for (manifest, wasm_extension, llm_providers_with_models) in &wasm_extensions {
1718 let extension = Arc::new(wasm_extension.clone());
1719
1720 for (language_server_id, language_server_config) in &manifest.language_servers {
1721 for language in language_server_config.languages() {
1722 this.proxy.register_language_server(
1723 extension.clone(),
1724 language_server_id.clone(),
1725 language.clone(),
1726 );
1727 }
1728 }
1729
1730 for (slash_command_name, slash_command) in &manifest.slash_commands {
1731 this.proxy.register_slash_command(
1732 extension.clone(),
1733 extension::SlashCommand {
1734 name: slash_command_name.to_string(),
1735 description: slash_command.description.to_string(),
1736 // We don't currently expose this as a configurable option, as it currently drives
1737 // the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
1738 // defined in extensions, as they are not able to be added to the menu.
1739 tooltip_text: String::new(),
1740 requires_argument: slash_command.requires_argument,
1741 },
1742 );
1743 }
1744
1745 for id in manifest.context_servers.keys() {
1746 this.proxy
1747 .register_context_server(extension.clone(), id.clone(), cx);
1748 }
1749
1750 for (debug_adapter, meta) in &manifest.debug_adapters {
1751 let mut path = root_dir.clone();
1752 path.push(Path::new(manifest.id.as_ref()));
1753 if let Some(schema_path) = &meta.schema_path {
1754 path.push(schema_path);
1755 } else {
1756 path.push("debug_adapter_schemas");
1757 path.push(Path::new(debug_adapter.as_ref()).with_extension("json"));
1758 }
1759
1760 this.proxy.register_debug_adapter(
1761 extension.clone(),
1762 debug_adapter.clone(),
1763 &path,
1764 );
1765 }
1766
1767 for debug_adapter in manifest.debug_locators.keys() {
1768 this.proxy
1769 .register_debug_locator(extension.clone(), debug_adapter.clone());
1770 }
1771
1772 // Register LLM providers
1773 for llm_provider in llm_providers_with_models {
1774 let provider_id: Arc<str> =
1775 format!("{}:{}", manifest.id, llm_provider.provider_info.id).into();
1776 let wasm_ext = extension.as_ref().clone();
1777 let pinfo = llm_provider.provider_info.clone();
1778 let mods = llm_provider.models.clone();
1779 let auth = llm_provider.is_authenticated;
1780 let icon = llm_provider.icon_path.clone();
1781 let auth_config = llm_provider.auth_config.clone();
1782
1783 this.proxy.register_language_model_provider(
1784 provider_id.clone(),
1785 Box::new(move |cx: &mut App| {
1786 let provider = Arc::new(ExtensionLanguageModelProvider::new(
1787 wasm_ext, pinfo, mods, auth, icon, auth_config, cx,
1788 ));
1789 language_model::LanguageModelRegistry::global(cx).update(
1790 cx,
1791 |registry, cx| {
1792 registry.register_provider(provider, cx);
1793 },
1794 );
1795 }),
1796 cx,
1797 );
1798 }
1799 }
1800
1801 let wasm_extensions_without_llm: Vec<_> = wasm_extensions
1802 .into_iter()
1803 .map(|(manifest, ext, _)| (manifest, ext))
1804 .collect();
1805 this.wasm_extensions.extend(wasm_extensions_without_llm);
1806 this.proxy.set_extensions_loaded();
1807 this.proxy.reload_current_theme(cx);
1808 this.proxy.reload_current_icon_theme(cx);
1809
1810 if let Some(events) = ExtensionEvents::try_global(cx) {
1811 events.update(cx, |this, cx| {
1812 this.emit(extension::Event::ExtensionsInstalledChanged, cx)
1813 });
1814 }
1815 })
1816 .ok();
1817 })
1818 }
1819
1820 fn rebuild_extension_index(&self, cx: &mut Context<Self>) -> Task<ExtensionIndex> {
1821 let fs = self.fs.clone();
1822 let work_dir = self.wasm_host.work_dir.clone();
1823 let extensions_dir = self.installed_dir.clone();
1824 let index_path = self.index_path.clone();
1825 let proxy = self.proxy.clone();
1826 cx.background_spawn(async move {
1827 let mut index = ExtensionIndex::default();
1828
1829 fs.create_dir(&work_dir).await.log_err();
1830 fs.create_dir(&extensions_dir).await.log_err();
1831
1832 let extension_paths = fs.read_dir(&extensions_dir).await;
1833 if let Ok(mut extension_paths) = extension_paths {
1834 while let Some(extension_dir) = extension_paths.next().await {
1835 let Ok(extension_dir) = extension_dir else {
1836 continue;
1837 };
1838
1839 if extension_dir
1840 .file_name()
1841 .is_some_and(|file_name| file_name == ".DS_Store")
1842 {
1843 continue;
1844 }
1845
1846 Self::add_extension_to_index(
1847 fs.clone(),
1848 extension_dir,
1849 &mut index,
1850 proxy.clone(),
1851 )
1852 .await
1853 .log_err();
1854 }
1855 }
1856
1857 if let Ok(index_json) = serde_json::to_string_pretty(&index) {
1858 fs.save(&index_path, &index_json.as_str().into(), Default::default())
1859 .await
1860 .context("failed to save extension index")
1861 .log_err();
1862 }
1863
1864 index
1865 })
1866 }
1867
1868 async fn add_extension_to_index(
1869 fs: Arc<dyn Fs>,
1870 extension_dir: PathBuf,
1871 index: &mut ExtensionIndex,
1872 proxy: Arc<ExtensionHostProxy>,
1873 ) -> Result<()> {
1874 let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
1875 let extension_id = extension_manifest.id.clone();
1876
1877 if SUPPRESSED_EXTENSIONS.contains(&extension_id.as_ref()) {
1878 return Ok(());
1879 }
1880
1881 // TODO: distinguish dev extensions more explicitly, by the absence
1882 // of a checksum file that we'll create when downloading normal extensions.
1883 let is_dev = fs
1884 .metadata(&extension_dir)
1885 .await?
1886 .context("directory does not exist")?
1887 .is_symlink;
1888
1889 if let Ok(mut language_paths) = fs.read_dir(&extension_dir.join("languages")).await {
1890 while let Some(language_path) = language_paths.next().await {
1891 let language_path = language_path?;
1892 let Ok(relative_path) = language_path.strip_prefix(&extension_dir) else {
1893 continue;
1894 };
1895 let Ok(Some(fs_metadata)) = fs.metadata(&language_path).await else {
1896 continue;
1897 };
1898 if !fs_metadata.is_dir {
1899 continue;
1900 }
1901 let config = fs.load(&language_path.join("config.toml")).await?;
1902 let config = ::toml::from_str::<LanguageConfig>(&config)?;
1903
1904 let relative_path = relative_path.to_path_buf();
1905 if !extension_manifest.languages.contains(&relative_path) {
1906 extension_manifest.languages.push(relative_path.clone());
1907 }
1908
1909 index.languages.insert(
1910 config.name.clone(),
1911 ExtensionIndexLanguageEntry {
1912 extension: extension_id.clone(),
1913 path: relative_path,
1914 matcher: config.matcher,
1915 hidden: config.hidden,
1916 grammar: config.grammar,
1917 },
1918 );
1919 }
1920 }
1921
1922 if let Ok(mut theme_paths) = fs.read_dir(&extension_dir.join("themes")).await {
1923 while let Some(theme_path) = theme_paths.next().await {
1924 let theme_path = theme_path?;
1925 let Ok(relative_path) = theme_path.strip_prefix(&extension_dir) else {
1926 continue;
1927 };
1928
1929 let Some(theme_families) = proxy
1930 .list_theme_names(theme_path.clone(), fs.clone())
1931 .await
1932 .log_err()
1933 else {
1934 continue;
1935 };
1936
1937 let relative_path = relative_path.to_path_buf();
1938 if !extension_manifest.themes.contains(&relative_path) {
1939 extension_manifest.themes.push(relative_path.clone());
1940 }
1941
1942 for theme_name in theme_families {
1943 index.themes.insert(
1944 theme_name.into(),
1945 ExtensionIndexThemeEntry {
1946 extension: extension_id.clone(),
1947 path: relative_path.clone(),
1948 },
1949 );
1950 }
1951 }
1952 }
1953
1954 if let Ok(mut icon_theme_paths) = fs.read_dir(&extension_dir.join("icon_themes")).await {
1955 while let Some(icon_theme_path) = icon_theme_paths.next().await {
1956 let icon_theme_path = icon_theme_path?;
1957 let Ok(relative_path) = icon_theme_path.strip_prefix(&extension_dir) else {
1958 continue;
1959 };
1960
1961 let Some(icon_theme_families) = proxy
1962 .list_icon_theme_names(icon_theme_path.clone(), fs.clone())
1963 .await
1964 .log_err()
1965 else {
1966 continue;
1967 };
1968
1969 let relative_path = relative_path.to_path_buf();
1970 if !extension_manifest.icon_themes.contains(&relative_path) {
1971 extension_manifest.icon_themes.push(relative_path.clone());
1972 }
1973
1974 for icon_theme_name in icon_theme_families {
1975 index.icon_themes.insert(
1976 icon_theme_name.into(),
1977 ExtensionIndexIconThemeEntry {
1978 extension: extension_id.clone(),
1979 path: relative_path.clone(),
1980 },
1981 );
1982 }
1983 }
1984 }
1985
1986 let extension_wasm_path = extension_dir.join("extension.wasm");
1987 if fs.is_file(&extension_wasm_path).await {
1988 extension_manifest
1989 .lib
1990 .kind
1991 .get_or_insert(ExtensionLibraryKind::Rust);
1992 }
1993
1994 index.extensions.insert(
1995 extension_id.clone(),
1996 ExtensionIndexEntry {
1997 dev: is_dev,
1998 manifest: Arc::new(extension_manifest),
1999 },
2000 );
2001
2002 Ok(())
2003 }
2004
2005 fn prepare_remote_extension(
2006 &mut self,
2007 extension_id: Arc<str>,
2008 is_dev: bool,
2009 tmp_dir: PathBuf,
2010 cx: &mut Context<Self>,
2011 ) -> Task<Result<()>> {
2012 let src_dir = self.extensions_dir().join(extension_id.as_ref());
2013 let Some(loaded_extension) = self.extension_index.extensions.get(&extension_id).cloned()
2014 else {
2015 return Task::ready(Err(anyhow!("extension no longer installed")));
2016 };
2017 let fs = self.fs.clone();
2018 cx.background_spawn(async move {
2019 const EXTENSION_TOML: &str = "extension.toml";
2020 const EXTENSION_WASM: &str = "extension.wasm";
2021 const CONFIG_TOML: &str = "config.toml";
2022
2023 if is_dev {
2024 let manifest_toml = toml::to_string(&loaded_extension.manifest)?;
2025 fs.save(
2026 &tmp_dir.join(EXTENSION_TOML),
2027 &Rope::from(manifest_toml),
2028 language::LineEnding::Unix,
2029 )
2030 .await?;
2031 } else {
2032 fs.copy_file(
2033 &src_dir.join(EXTENSION_TOML),
2034 &tmp_dir.join(EXTENSION_TOML),
2035 fs::CopyOptions::default(),
2036 )
2037 .await?
2038 }
2039
2040 if fs.is_file(&src_dir.join(EXTENSION_WASM)).await {
2041 fs.copy_file(
2042 &src_dir.join(EXTENSION_WASM),
2043 &tmp_dir.join(EXTENSION_WASM),
2044 fs::CopyOptions::default(),
2045 )
2046 .await?
2047 }
2048
2049 for language_path in loaded_extension.manifest.languages.iter() {
2050 if fs
2051 .is_file(&src_dir.join(language_path).join(CONFIG_TOML))
2052 .await
2053 {
2054 fs.create_dir(&tmp_dir.join(language_path)).await?;
2055 fs.copy_file(
2056 &src_dir.join(language_path).join(CONFIG_TOML),
2057 &tmp_dir.join(language_path).join(CONFIG_TOML),
2058 fs::CopyOptions::default(),
2059 )
2060 .await?
2061 }
2062 }
2063
2064 for (adapter_name, meta) in loaded_extension.manifest.debug_adapters.iter() {
2065 let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta);
2066
2067 if fs.is_file(&src_dir.join(schema_path)).await {
2068 if let Some(parent) = schema_path.parent() {
2069 fs.create_dir(&tmp_dir.join(parent)).await?
2070 }
2071 fs.copy_file(
2072 &src_dir.join(schema_path),
2073 &tmp_dir.join(schema_path),
2074 fs::CopyOptions::default(),
2075 )
2076 .await?
2077 }
2078 }
2079
2080 Ok(())
2081 })
2082 }
2083
2084 async fn sync_extensions_to_remotes(
2085 this: &WeakEntity<Self>,
2086 client: WeakEntity<RemoteClient>,
2087 cx: &mut AsyncApp,
2088 ) -> Result<()> {
2089 let extensions = this.update(cx, |this, _cx| {
2090 this.extension_index
2091 .extensions
2092 .iter()
2093 .filter_map(|(id, entry)| {
2094 if !entry.manifest.allow_remote_load() {
2095 return None;
2096 }
2097 Some(proto::Extension {
2098 id: id.to_string(),
2099 version: entry.manifest.version.to_string(),
2100 dev: entry.dev,
2101 })
2102 })
2103 .collect()
2104 })?;
2105
2106 let response = client
2107 .update(cx, |client, _cx| {
2108 client
2109 .proto_client()
2110 .request(proto::SyncExtensions { extensions })
2111 })?
2112 .await?;
2113 let path_style = client.read_with(cx, |client, _| client.path_style())?;
2114
2115 for missing_extension in response.missing_extensions.into_iter() {
2116 let tmp_dir = tempfile::tempdir()?;
2117 this.update(cx, |this, cx| {
2118 this.prepare_remote_extension(
2119 missing_extension.id.clone().into(),
2120 missing_extension.dev,
2121 tmp_dir.path().to_owned(),
2122 cx,
2123 )
2124 })?
2125 .await?;
2126 let dest_dir = RemotePathBuf::new(
2127 path_style
2128 .join(&response.tmp_dir, &missing_extension.id)
2129 .with_context(|| {
2130 format!(
2131 "failed to construct destination path: {:?}, {:?}",
2132 response.tmp_dir, missing_extension.id,
2133 )
2134 })?,
2135 path_style,
2136 );
2137
2138 client
2139 .update(cx, |client, cx| {
2140 client.upload_directory(tmp_dir.path().to_owned(), dest_dir.clone(), cx)
2141 })?
2142 .await?;
2143
2144 let result = client
2145 .update(cx, |client, _cx| {
2146 client.proto_client().request(proto::InstallExtension {
2147 tmp_dir: dest_dir.to_proto(),
2148 extension: Some(missing_extension.clone()),
2149 })
2150 })?
2151 .await;
2152
2153 if let Err(e) = result {
2154 log::error!(
2155 "Failed to install extension {}: {}",
2156 missing_extension.id,
2157 e
2158 );
2159 }
2160 }
2161
2162 anyhow::Ok(())
2163 }
2164
2165 pub async fn update_remote_clients(this: &WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
2166 let clients = this.update(cx, |this, _cx| {
2167 this.remote_clients.retain(|v| v.upgrade().is_some());
2168 this.remote_clients.clone()
2169 })?;
2170
2171 for client in clients {
2172 Self::sync_extensions_to_remotes(this, client, cx)
2173 .await
2174 .log_err();
2175 }
2176
2177 anyhow::Ok(())
2178 }
2179
2180 pub fn register_remote_client(
2181 &mut self,
2182 client: Entity<RemoteClient>,
2183 _cx: &mut Context<Self>,
2184 ) {
2185 self.remote_clients.push(client.downgrade());
2186 self.ssh_registered_tx.unbounded_send(()).ok();
2187 }
2188}
2189
2190fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
2191 let mut result = LanguageQueries::default();
2192 if let Some(entries) = std::fs::read_dir(root_path).log_err() {
2193 for entry in entries {
2194 let Some(entry) = entry.log_err() else {
2195 continue;
2196 };
2197 let path = entry.path();
2198 if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
2199 if !remainder.ends_with(".scm") {
2200 continue;
2201 }
2202 for (name, query) in QUERY_FILENAME_PREFIXES {
2203 if remainder.starts_with(name) {
2204 if let Some(contents) = std::fs::read_to_string(&path).log_err() {
2205 match query(&mut result) {
2206 None => *query(&mut result) = Some(contents.into()),
2207 Some(r) => r.to_mut().push_str(contents.as_ref()),
2208 }
2209 }
2210 break;
2211 }
2212 }
2213 }
2214 }
2215 }
2216 result
2217}