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 migration 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 })?;
1204
1205 this.update(cx, |this, cx| this.reload(None, cx))?.await;
1206 this.update(cx, |this, cx| {
1207 cx.emit(Event::ExtensionInstalled(extension_id.clone()));
1208 if let Some(events) = ExtensionEvents::try_global(cx)
1209 && let Some(manifest) = this.extension_manifest_for_id(&extension_id)
1210 {
1211 events.update(cx, |this, cx| {
1212 this.emit(extension::Event::ExtensionInstalled(manifest.clone()), cx)
1213 });
1214 }
1215 })?;
1216
1217 Ok(())
1218 })
1219 }
1220
1221 pub fn rebuild_dev_extension(&mut self, extension_id: Arc<str>, cx: &mut Context<Self>) {
1222 let path = self.installed_dir.join(extension_id.as_ref());
1223 let builder = self.builder.clone();
1224 let fs = self.fs.clone();
1225
1226 match self.outstanding_operations.entry(extension_id.clone()) {
1227 btree_map::Entry::Occupied(_) => return,
1228 btree_map::Entry::Vacant(e) => e.insert(ExtensionOperation::Upgrade),
1229 };
1230
1231 cx.notify();
1232 let compile = cx.background_spawn(async move {
1233 let mut manifest = ExtensionManifest::load(fs.clone(), &path).await?;
1234 builder
1235 .compile_extension(
1236 &path,
1237 &mut manifest,
1238 CompileExtensionOptions { release: true },
1239 fs,
1240 )
1241 .await
1242 });
1243
1244 cx.spawn(async move |this, cx| {
1245 let result = compile.await;
1246
1247 this.update(cx, |this, cx| {
1248 this.outstanding_operations.remove(&extension_id);
1249 cx.notify();
1250 })?;
1251
1252 if result.is_ok() {
1253 this.update(cx, |this, cx| this.reload(Some(extension_id), cx))?
1254 .await;
1255 }
1256
1257 result
1258 })
1259 .detach_and_log_err(cx)
1260 }
1261
1262 /// Updates the set of installed extensions.
1263 ///
1264 /// First, this unloads any themes, languages, or grammars that are
1265 /// no longer in the manifest, or whose files have changed on disk.
1266 /// Then it loads any themes, languages, or grammars that are newly
1267 /// added to the manifest, or whose files have changed on disk.
1268 fn extensions_updated(
1269 &mut self,
1270 mut new_index: ExtensionIndex,
1271 cx: &mut Context<Self>,
1272 ) -> Task<()> {
1273 let old_index = &self.extension_index;
1274
1275 new_index
1276 .extensions
1277 .retain(|extension_id, _| !SUPPRESSED_EXTENSIONS.contains(&extension_id.as_ref()));
1278
1279 // Determine which extensions need to be loaded and unloaded, based
1280 // on the changes to the manifest and the extensions that we know have been
1281 // modified.
1282 let mut extensions_to_unload = Vec::default();
1283 let mut extensions_to_load = Vec::default();
1284 {
1285 let mut old_keys = old_index.extensions.iter().peekable();
1286 let mut new_keys = new_index.extensions.iter().peekable();
1287 loop {
1288 match (old_keys.peek(), new_keys.peek()) {
1289 (None, None) => break,
1290 (None, Some(_)) => {
1291 extensions_to_load.push(new_keys.next().unwrap().0.clone());
1292 }
1293 (Some(_), None) => {
1294 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
1295 }
1296 (Some((old_key, _)), Some((new_key, _))) => match old_key.cmp(new_key) {
1297 Ordering::Equal => {
1298 let (old_key, old_value) = old_keys.next().unwrap();
1299 let (new_key, new_value) = new_keys.next().unwrap();
1300 if old_value != new_value || self.modified_extensions.contains(old_key)
1301 {
1302 extensions_to_unload.push(old_key.clone());
1303 extensions_to_load.push(new_key.clone());
1304 }
1305 }
1306 Ordering::Less => {
1307 extensions_to_unload.push(old_keys.next().unwrap().0.clone());
1308 }
1309 Ordering::Greater => {
1310 extensions_to_load.push(new_keys.next().unwrap().0.clone());
1311 }
1312 },
1313 }
1314 }
1315 self.modified_extensions.clear();
1316 }
1317
1318 if extensions_to_load.is_empty() && extensions_to_unload.is_empty() {
1319 self.reload_complete_senders.clear();
1320 return Task::ready(());
1321 }
1322
1323 let extension_ids = extensions_to_load
1324 .iter()
1325 .filter_map(|id| {
1326 Some((
1327 id.clone(),
1328 new_index.extensions.get(id)?.manifest.version.clone(),
1329 ))
1330 })
1331 .collect::<Vec<_>>();
1332
1333 telemetry::event!("Extensions Loaded", id_and_versions = extension_ids);
1334
1335 let themes_to_remove = old_index
1336 .themes
1337 .iter()
1338 .filter_map(|(name, entry)| {
1339 if extensions_to_unload.contains(&entry.extension) {
1340 Some(name.clone().into())
1341 } else {
1342 None
1343 }
1344 })
1345 .collect::<Vec<_>>();
1346 let icon_themes_to_remove = old_index
1347 .icon_themes
1348 .iter()
1349 .filter_map(|(name, entry)| {
1350 if extensions_to_unload.contains(&entry.extension) {
1351 Some(name.clone().into())
1352 } else {
1353 None
1354 }
1355 })
1356 .collect::<Vec<_>>();
1357 let languages_to_remove = old_index
1358 .languages
1359 .iter()
1360 .filter_map(|(name, entry)| {
1361 if extensions_to_unload.contains(&entry.extension) {
1362 Some(name.clone())
1363 } else {
1364 None
1365 }
1366 })
1367 .collect::<Vec<_>>();
1368 let mut grammars_to_remove = Vec::new();
1369 let mut server_removal_tasks = Vec::with_capacity(extensions_to_unload.len());
1370 for extension_id in &extensions_to_unload {
1371 let Some(extension) = old_index.extensions.get(extension_id) else {
1372 continue;
1373 };
1374 grammars_to_remove.extend(extension.manifest.grammars.keys().cloned());
1375 for (language_server_name, config) in &extension.manifest.language_servers {
1376 for language in config.languages() {
1377 server_removal_tasks.push(self.proxy.remove_language_server(
1378 &language,
1379 language_server_name,
1380 cx,
1381 ));
1382 }
1383 }
1384
1385 for server_id in extension.manifest.context_servers.keys() {
1386 self.proxy.unregister_context_server(server_id.clone(), cx);
1387 }
1388 for adapter in extension.manifest.debug_adapters.keys() {
1389 self.proxy.unregister_debug_adapter(adapter.clone());
1390 }
1391 for locator in extension.manifest.debug_locators.keys() {
1392 self.proxy.unregister_debug_locator(locator.clone());
1393 }
1394 for command_name in extension.manifest.slash_commands.keys() {
1395 self.proxy.unregister_slash_command(command_name.clone());
1396 }
1397 for provider_id in extension.manifest.language_model_providers.keys() {
1398 let full_provider_id: Arc<str> = format!("{}:{}", extension_id, provider_id).into();
1399 self.proxy
1400 .unregister_language_model_provider(full_provider_id, cx);
1401 }
1402 }
1403
1404 self.wasm_extensions
1405 .retain(|(extension, _)| !extensions_to_unload.contains(&extension.id));
1406 self.proxy.remove_user_themes(themes_to_remove);
1407 self.proxy.remove_icon_themes(icon_themes_to_remove);
1408 self.proxy
1409 .remove_languages(&languages_to_remove, &grammars_to_remove);
1410
1411 let mut grammars_to_add = Vec::new();
1412 let mut themes_to_add = Vec::new();
1413 let mut icon_themes_to_add = Vec::new();
1414 let mut snippets_to_add = Vec::new();
1415 for extension_id in &extensions_to_load {
1416 let Some(extension) = new_index.extensions.get(extension_id) else {
1417 continue;
1418 };
1419
1420 grammars_to_add.extend(extension.manifest.grammars.keys().map(|grammar_name| {
1421 let mut grammar_path = self.installed_dir.clone();
1422 grammar_path.extend([extension_id.as_ref(), "grammars"]);
1423 grammar_path.push(grammar_name.as_ref());
1424 grammar_path.set_extension("wasm");
1425 (grammar_name.clone(), grammar_path)
1426 }));
1427 themes_to_add.extend(extension.manifest.themes.iter().map(|theme_path| {
1428 let mut path = self.installed_dir.clone();
1429 path.extend([Path::new(extension_id.as_ref()), theme_path.as_path()]);
1430 path
1431 }));
1432 icon_themes_to_add.extend(extension.manifest.icon_themes.iter().map(
1433 |icon_theme_path| {
1434 let mut path = self.installed_dir.clone();
1435 path.extend([Path::new(extension_id.as_ref()), icon_theme_path.as_path()]);
1436
1437 let mut icons_root_path = self.installed_dir.clone();
1438 icons_root_path.extend([Path::new(extension_id.as_ref())]);
1439
1440 (path, icons_root_path)
1441 },
1442 ));
1443 snippets_to_add.extend(extension.manifest.snippets.iter().map(|snippets_path| {
1444 let mut path = self.installed_dir.clone();
1445 path.extend([Path::new(extension_id.as_ref()), snippets_path.as_path()]);
1446 path
1447 }));
1448 }
1449
1450 self.proxy.register_grammars(grammars_to_add);
1451 let languages_to_add = new_index
1452 .languages
1453 .iter()
1454 .filter(|(_, entry)| extensions_to_load.contains(&entry.extension))
1455 .collect::<Vec<_>>();
1456 for (language_name, language) in languages_to_add {
1457 let mut language_path = self.installed_dir.clone();
1458 language_path.extend([
1459 Path::new(language.extension.as_ref()),
1460 language.path.as_path(),
1461 ]);
1462 self.proxy.register_language(
1463 language_name.clone(),
1464 language.grammar.clone(),
1465 language.matcher.clone(),
1466 language.hidden,
1467 Arc::new(move || {
1468 let config = std::fs::read_to_string(language_path.join("config.toml"))?;
1469 let config: LanguageConfig = ::toml::from_str(&config)?;
1470 let queries = load_plugin_queries(&language_path);
1471 let context_provider =
1472 std::fs::read_to_string(language_path.join("tasks.json"))
1473 .ok()
1474 .and_then(|contents| {
1475 let definitions =
1476 serde_json_lenient::from_str(&contents).log_err()?;
1477 Some(Arc::new(ContextProviderWithTasks::new(definitions)) as Arc<_>)
1478 });
1479
1480 Ok(LoadedLanguage {
1481 config,
1482 queries,
1483 context_provider,
1484 toolchain_provider: None,
1485 manifest_name: None,
1486 })
1487 }),
1488 );
1489 }
1490
1491 let fs = self.fs.clone();
1492 let wasm_host = self.wasm_host.clone();
1493 let root_dir = self.installed_dir.clone();
1494 let proxy = self.proxy.clone();
1495 let extension_entries = extensions_to_load
1496 .iter()
1497 .filter_map(|name| new_index.extensions.get(name).cloned())
1498 .collect::<Vec<_>>();
1499 self.extension_index = new_index;
1500 cx.notify();
1501 cx.emit(Event::ExtensionsUpdated);
1502
1503 cx.spawn(async move |this, cx| {
1504 cx.background_spawn({
1505 let fs = fs.clone();
1506 async move {
1507 let _ = join_all(server_removal_tasks).await;
1508 for theme_path in themes_to_add {
1509 proxy
1510 .load_user_theme(theme_path, fs.clone())
1511 .await
1512 .log_err();
1513 }
1514
1515 for (icon_theme_path, icons_root_path) in icon_themes_to_add {
1516 proxy
1517 .load_icon_theme(icon_theme_path, icons_root_path, fs.clone())
1518 .await
1519 .log_err();
1520 }
1521
1522 for snippets_path in &snippets_to_add {
1523 match fs
1524 .load(snippets_path)
1525 .await
1526 .with_context(|| format!("Loading snippets from {snippets_path:?}"))
1527 {
1528 Ok(snippets_contents) => {
1529 proxy
1530 .register_snippet(snippets_path, &snippets_contents)
1531 .log_err();
1532 }
1533 Err(e) => log::error!("Cannot load snippets: {e:#}"),
1534 }
1535 }
1536 }
1537 })
1538 .await;
1539
1540 let mut wasm_extensions: Vec<(
1541 Arc<ExtensionManifest>,
1542 WasmExtension,
1543 Vec<LlmProviderWithModels>,
1544 )> = Vec::new();
1545 for extension in extension_entries {
1546 if extension.manifest.lib.kind.is_none() {
1547 continue;
1548 };
1549
1550 let extension_path = root_dir.join(extension.manifest.id.as_ref());
1551 let wasm_extension = WasmExtension::load(
1552 &extension_path,
1553 &extension.manifest,
1554 wasm_host.clone(),
1555 cx,
1556 )
1557 .await
1558 .with_context(|| format!("Loading extension from {extension_path:?}"));
1559
1560 match wasm_extension {
1561 Ok(wasm_extension) => {
1562 // Query for LLM providers if the manifest declares any
1563 let mut llm_providers_with_models = Vec::new();
1564 if !extension.manifest.language_model_providers.is_empty() {
1565 let providers_result = wasm_extension
1566 .call(|ext, store| {
1567 async move { ext.call_llm_providers(store).await }.boxed()
1568 })
1569 .await;
1570
1571 if let Ok(Ok(providers)) = providers_result {
1572 for provider_info in providers {
1573 let models_result = wasm_extension
1574 .call({
1575 let provider_id = provider_info.id.clone();
1576 |ext, store| {
1577 async move {
1578 ext.call_llm_provider_models(store, &provider_id)
1579 .await
1580 }
1581 .boxed()
1582 }
1583 })
1584 .await;
1585
1586 let models: Vec<LlmModelInfo> = match models_result {
1587 Ok(Ok(Ok(models))) => models,
1588 Ok(Ok(Err(e))) => {
1589 log::error!(
1590 "Failed to get models for LLM provider {} in extension {}: {}",
1591 provider_info.id,
1592 extension.manifest.id,
1593 e
1594 );
1595 Vec::new()
1596 }
1597 Ok(Err(e)) => {
1598 log::error!(
1599 "Wasm error calling llm_provider_models for {} in extension {}: {:?}",
1600 provider_info.id,
1601 extension.manifest.id,
1602 e
1603 );
1604 Vec::new()
1605 }
1606 Err(e) => {
1607 log::error!(
1608 "Extension call failed for llm_provider_models {} in extension {}: {:?}",
1609 provider_info.id,
1610 extension.manifest.id,
1611 e
1612 );
1613 Vec::new()
1614 }
1615 };
1616
1617 // Query initial authentication state
1618 let is_authenticated = wasm_extension
1619 .call({
1620 let provider_id = provider_info.id.clone();
1621 |ext, store| {
1622 async move {
1623 ext.call_llm_provider_is_authenticated(
1624 store,
1625 &provider_id,
1626 )
1627 .await
1628 }
1629 .boxed()
1630 }
1631 })
1632 .await
1633 .unwrap_or(Ok(false))
1634 .unwrap_or(false);
1635
1636 // Resolve icon path if provided
1637 let icon_path = provider_info.icon.as_ref().map(|icon| {
1638 let icon_file_path = extension_path.join(icon);
1639 // Canonicalize to resolve symlinks (dev extensions are symlinked)
1640 let absolute_icon_path = icon_file_path
1641 .canonicalize()
1642 .unwrap_or(icon_file_path)
1643 .to_string_lossy()
1644 .to_string();
1645 SharedString::from(absolute_icon_path)
1646 });
1647
1648 let provider_id_arc: Arc<str> =
1649 provider_info.id.as_str().into();
1650 let auth_config = extension
1651 .manifest
1652 .language_model_providers
1653 .get(&provider_id_arc)
1654 .and_then(|entry| entry.auth.clone());
1655
1656 llm_providers_with_models.push(LlmProviderWithModels {
1657 provider_info,
1658 models,
1659 is_authenticated,
1660 icon_path,
1661 auth_config,
1662 });
1663 }
1664 } else {
1665 log::error!(
1666 "Failed to get LLM providers from extension {}: {:?}",
1667 extension.manifest.id,
1668 providers_result
1669 );
1670 }
1671 }
1672
1673 wasm_extensions.push((
1674 extension.manifest.clone(),
1675 wasm_extension,
1676 llm_providers_with_models,
1677 ))
1678 }
1679 Err(e) => {
1680 log::error!(
1681 "Failed to load extension: {}, {:#}",
1682 extension.manifest.id,
1683 e
1684 );
1685 this.update(cx, |_, cx| {
1686 cx.emit(Event::ExtensionFailedToLoad(extension.manifest.id.clone()))
1687 })
1688 .ok();
1689 }
1690 }
1691 }
1692
1693 this.update(cx, |this, cx| {
1694 this.reload_complete_senders.clear();
1695
1696 for (manifest, wasm_extension, llm_providers_with_models) in &wasm_extensions {
1697 let extension = Arc::new(wasm_extension.clone());
1698
1699 for (language_server_id, language_server_config) in &manifest.language_servers {
1700 for language in language_server_config.languages() {
1701 this.proxy.register_language_server(
1702 extension.clone(),
1703 language_server_id.clone(),
1704 language.clone(),
1705 );
1706 }
1707 }
1708
1709 for (slash_command_name, slash_command) in &manifest.slash_commands {
1710 this.proxy.register_slash_command(
1711 extension.clone(),
1712 extension::SlashCommand {
1713 name: slash_command_name.to_string(),
1714 description: slash_command.description.to_string(),
1715 // We don't currently expose this as a configurable option, as it currently drives
1716 // the `menu_text` on the `SlashCommand` trait, which is not used for slash commands
1717 // defined in extensions, as they are not able to be added to the menu.
1718 tooltip_text: String::new(),
1719 requires_argument: slash_command.requires_argument,
1720 },
1721 );
1722 }
1723
1724 for id in manifest.context_servers.keys() {
1725 this.proxy
1726 .register_context_server(extension.clone(), id.clone(), cx);
1727 }
1728
1729 for (debug_adapter, meta) in &manifest.debug_adapters {
1730 let mut path = root_dir.clone();
1731 path.push(Path::new(manifest.id.as_ref()));
1732 if let Some(schema_path) = &meta.schema_path {
1733 path.push(schema_path);
1734 } else {
1735 path.push("debug_adapter_schemas");
1736 path.push(Path::new(debug_adapter.as_ref()).with_extension("json"));
1737 }
1738
1739 this.proxy.register_debug_adapter(
1740 extension.clone(),
1741 debug_adapter.clone(),
1742 &path,
1743 );
1744 }
1745
1746 for debug_adapter in manifest.debug_locators.keys() {
1747 this.proxy
1748 .register_debug_locator(extension.clone(), debug_adapter.clone());
1749 }
1750
1751 // Register LLM providers
1752 for llm_provider in llm_providers_with_models {
1753 let provider_id: Arc<str> =
1754 format!("{}:{}", manifest.id, llm_provider.provider_info.id).into();
1755 let wasm_ext = extension.as_ref().clone();
1756 let pinfo = llm_provider.provider_info.clone();
1757 let mods = llm_provider.models.clone();
1758 let auth = llm_provider.is_authenticated;
1759 let icon = llm_provider.icon_path.clone();
1760 let auth_config = llm_provider.auth_config.clone();
1761
1762 this.proxy.register_language_model_provider(
1763 provider_id.clone(),
1764 Box::new(move |cx: &mut App| {
1765 let provider = Arc::new(ExtensionLanguageModelProvider::new(
1766 wasm_ext, pinfo, mods, auth, icon, auth_config, cx,
1767 ));
1768 language_model::LanguageModelRegistry::global(cx).update(
1769 cx,
1770 |registry, cx| {
1771 registry.register_provider(provider, cx);
1772 },
1773 );
1774 }),
1775 cx,
1776 );
1777 }
1778 }
1779
1780 let wasm_extensions_without_llm: Vec<_> = wasm_extensions
1781 .into_iter()
1782 .map(|(manifest, ext, _)| (manifest, ext))
1783 .collect();
1784 this.wasm_extensions.extend(wasm_extensions_without_llm);
1785 this.proxy.set_extensions_loaded();
1786 this.proxy.reload_current_theme(cx);
1787 this.proxy.reload_current_icon_theme(cx);
1788
1789 if let Some(events) = ExtensionEvents::try_global(cx) {
1790 events.update(cx, |this, cx| {
1791 this.emit(extension::Event::ExtensionsInstalledChanged, cx)
1792 });
1793 }
1794 })
1795 .ok();
1796 })
1797 }
1798
1799 fn rebuild_extension_index(&self, cx: &mut Context<Self>) -> Task<ExtensionIndex> {
1800 let fs = self.fs.clone();
1801 let work_dir = self.wasm_host.work_dir.clone();
1802 let extensions_dir = self.installed_dir.clone();
1803 let index_path = self.index_path.clone();
1804 let proxy = self.proxy.clone();
1805 cx.background_spawn(async move {
1806 let mut index = ExtensionIndex::default();
1807
1808 fs.create_dir(&work_dir).await.log_err();
1809 fs.create_dir(&extensions_dir).await.log_err();
1810
1811 let extension_paths = fs.read_dir(&extensions_dir).await;
1812 if let Ok(mut extension_paths) = extension_paths {
1813 while let Some(extension_dir) = extension_paths.next().await {
1814 let Ok(extension_dir) = extension_dir else {
1815 continue;
1816 };
1817
1818 if extension_dir
1819 .file_name()
1820 .is_some_and(|file_name| file_name == ".DS_Store")
1821 {
1822 continue;
1823 }
1824
1825 Self::add_extension_to_index(
1826 fs.clone(),
1827 extension_dir,
1828 &mut index,
1829 proxy.clone(),
1830 )
1831 .await
1832 .log_err();
1833 }
1834 }
1835
1836 if let Ok(index_json) = serde_json::to_string_pretty(&index) {
1837 fs.save(&index_path, &index_json.as_str().into(), Default::default())
1838 .await
1839 .context("failed to save extension index")
1840 .log_err();
1841 }
1842
1843 index
1844 })
1845 }
1846
1847 async fn add_extension_to_index(
1848 fs: Arc<dyn Fs>,
1849 extension_dir: PathBuf,
1850 index: &mut ExtensionIndex,
1851 proxy: Arc<ExtensionHostProxy>,
1852 ) -> Result<()> {
1853 let mut extension_manifest = ExtensionManifest::load(fs.clone(), &extension_dir).await?;
1854 let extension_id = extension_manifest.id.clone();
1855
1856 if SUPPRESSED_EXTENSIONS.contains(&extension_id.as_ref()) {
1857 return Ok(());
1858 }
1859
1860 // TODO: distinguish dev extensions more explicitly, by the absence
1861 // of a checksum file that we'll create when downloading normal extensions.
1862 let is_dev = fs
1863 .metadata(&extension_dir)
1864 .await?
1865 .context("directory does not exist")?
1866 .is_symlink;
1867
1868 if let Ok(mut language_paths) = fs.read_dir(&extension_dir.join("languages")).await {
1869 while let Some(language_path) = language_paths.next().await {
1870 let language_path = language_path?;
1871 let Ok(relative_path) = language_path.strip_prefix(&extension_dir) else {
1872 continue;
1873 };
1874 let Ok(Some(fs_metadata)) = fs.metadata(&language_path).await else {
1875 continue;
1876 };
1877 if !fs_metadata.is_dir {
1878 continue;
1879 }
1880 let config = fs.load(&language_path.join("config.toml")).await?;
1881 let config = ::toml::from_str::<LanguageConfig>(&config)?;
1882
1883 let relative_path = relative_path.to_path_buf();
1884 if !extension_manifest.languages.contains(&relative_path) {
1885 extension_manifest.languages.push(relative_path.clone());
1886 }
1887
1888 index.languages.insert(
1889 config.name.clone(),
1890 ExtensionIndexLanguageEntry {
1891 extension: extension_id.clone(),
1892 path: relative_path,
1893 matcher: config.matcher,
1894 hidden: config.hidden,
1895 grammar: config.grammar,
1896 },
1897 );
1898 }
1899 }
1900
1901 if let Ok(mut theme_paths) = fs.read_dir(&extension_dir.join("themes")).await {
1902 while let Some(theme_path) = theme_paths.next().await {
1903 let theme_path = theme_path?;
1904 let Ok(relative_path) = theme_path.strip_prefix(&extension_dir) else {
1905 continue;
1906 };
1907
1908 let Some(theme_families) = proxy
1909 .list_theme_names(theme_path.clone(), fs.clone())
1910 .await
1911 .log_err()
1912 else {
1913 continue;
1914 };
1915
1916 let relative_path = relative_path.to_path_buf();
1917 if !extension_manifest.themes.contains(&relative_path) {
1918 extension_manifest.themes.push(relative_path.clone());
1919 }
1920
1921 for theme_name in theme_families {
1922 index.themes.insert(
1923 theme_name.into(),
1924 ExtensionIndexThemeEntry {
1925 extension: extension_id.clone(),
1926 path: relative_path.clone(),
1927 },
1928 );
1929 }
1930 }
1931 }
1932
1933 if let Ok(mut icon_theme_paths) = fs.read_dir(&extension_dir.join("icon_themes")).await {
1934 while let Some(icon_theme_path) = icon_theme_paths.next().await {
1935 let icon_theme_path = icon_theme_path?;
1936 let Ok(relative_path) = icon_theme_path.strip_prefix(&extension_dir) else {
1937 continue;
1938 };
1939
1940 let Some(icon_theme_families) = proxy
1941 .list_icon_theme_names(icon_theme_path.clone(), fs.clone())
1942 .await
1943 .log_err()
1944 else {
1945 continue;
1946 };
1947
1948 let relative_path = relative_path.to_path_buf();
1949 if !extension_manifest.icon_themes.contains(&relative_path) {
1950 extension_manifest.icon_themes.push(relative_path.clone());
1951 }
1952
1953 for icon_theme_name in icon_theme_families {
1954 index.icon_themes.insert(
1955 icon_theme_name.into(),
1956 ExtensionIndexIconThemeEntry {
1957 extension: extension_id.clone(),
1958 path: relative_path.clone(),
1959 },
1960 );
1961 }
1962 }
1963 }
1964
1965 let extension_wasm_path = extension_dir.join("extension.wasm");
1966 if fs.is_file(&extension_wasm_path).await {
1967 extension_manifest
1968 .lib
1969 .kind
1970 .get_or_insert(ExtensionLibraryKind::Rust);
1971 }
1972
1973 index.extensions.insert(
1974 extension_id.clone(),
1975 ExtensionIndexEntry {
1976 dev: is_dev,
1977 manifest: Arc::new(extension_manifest),
1978 },
1979 );
1980
1981 Ok(())
1982 }
1983
1984 fn prepare_remote_extension(
1985 &mut self,
1986 extension_id: Arc<str>,
1987 is_dev: bool,
1988 tmp_dir: PathBuf,
1989 cx: &mut Context<Self>,
1990 ) -> Task<Result<()>> {
1991 let src_dir = self.extensions_dir().join(extension_id.as_ref());
1992 let Some(loaded_extension) = self.extension_index.extensions.get(&extension_id).cloned()
1993 else {
1994 return Task::ready(Err(anyhow!("extension no longer installed")));
1995 };
1996 let fs = self.fs.clone();
1997 cx.background_spawn(async move {
1998 const EXTENSION_TOML: &str = "extension.toml";
1999 const EXTENSION_WASM: &str = "extension.wasm";
2000 const CONFIG_TOML: &str = "config.toml";
2001
2002 if is_dev {
2003 let manifest_toml = toml::to_string(&loaded_extension.manifest)?;
2004 fs.save(
2005 &tmp_dir.join(EXTENSION_TOML),
2006 &Rope::from(manifest_toml),
2007 language::LineEnding::Unix,
2008 )
2009 .await?;
2010 } else {
2011 fs.copy_file(
2012 &src_dir.join(EXTENSION_TOML),
2013 &tmp_dir.join(EXTENSION_TOML),
2014 fs::CopyOptions::default(),
2015 )
2016 .await?
2017 }
2018
2019 if fs.is_file(&src_dir.join(EXTENSION_WASM)).await {
2020 fs.copy_file(
2021 &src_dir.join(EXTENSION_WASM),
2022 &tmp_dir.join(EXTENSION_WASM),
2023 fs::CopyOptions::default(),
2024 )
2025 .await?
2026 }
2027
2028 for language_path in loaded_extension.manifest.languages.iter() {
2029 if fs
2030 .is_file(&src_dir.join(language_path).join(CONFIG_TOML))
2031 .await
2032 {
2033 fs.create_dir(&tmp_dir.join(language_path)).await?;
2034 fs.copy_file(
2035 &src_dir.join(language_path).join(CONFIG_TOML),
2036 &tmp_dir.join(language_path).join(CONFIG_TOML),
2037 fs::CopyOptions::default(),
2038 )
2039 .await?
2040 }
2041 }
2042
2043 for (adapter_name, meta) in loaded_extension.manifest.debug_adapters.iter() {
2044 let schema_path = &extension::build_debug_adapter_schema_path(adapter_name, meta);
2045
2046 if fs.is_file(&src_dir.join(schema_path)).await {
2047 if let Some(parent) = schema_path.parent() {
2048 fs.create_dir(&tmp_dir.join(parent)).await?
2049 }
2050 fs.copy_file(
2051 &src_dir.join(schema_path),
2052 &tmp_dir.join(schema_path),
2053 fs::CopyOptions::default(),
2054 )
2055 .await?
2056 }
2057 }
2058
2059 Ok(())
2060 })
2061 }
2062
2063 async fn sync_extensions_to_remotes(
2064 this: &WeakEntity<Self>,
2065 client: WeakEntity<RemoteClient>,
2066 cx: &mut AsyncApp,
2067 ) -> Result<()> {
2068 let extensions = this.update(cx, |this, _cx| {
2069 this.extension_index
2070 .extensions
2071 .iter()
2072 .filter_map(|(id, entry)| {
2073 if !entry.manifest.allow_remote_load() {
2074 return None;
2075 }
2076 Some(proto::Extension {
2077 id: id.to_string(),
2078 version: entry.manifest.version.to_string(),
2079 dev: entry.dev,
2080 })
2081 })
2082 .collect()
2083 })?;
2084
2085 let response = client
2086 .update(cx, |client, _cx| {
2087 client
2088 .proto_client()
2089 .request(proto::SyncExtensions { extensions })
2090 })?
2091 .await?;
2092 let path_style = client.read_with(cx, |client, _| client.path_style())?;
2093
2094 for missing_extension in response.missing_extensions.into_iter() {
2095 let tmp_dir = tempfile::tempdir()?;
2096 this.update(cx, |this, cx| {
2097 this.prepare_remote_extension(
2098 missing_extension.id.clone().into(),
2099 missing_extension.dev,
2100 tmp_dir.path().to_owned(),
2101 cx,
2102 )
2103 })?
2104 .await?;
2105 let dest_dir = RemotePathBuf::new(
2106 path_style
2107 .join(&response.tmp_dir, &missing_extension.id)
2108 .with_context(|| {
2109 format!(
2110 "failed to construct destination path: {:?}, {:?}",
2111 response.tmp_dir, missing_extension.id,
2112 )
2113 })?,
2114 path_style,
2115 );
2116
2117 client
2118 .update(cx, |client, cx| {
2119 client.upload_directory(tmp_dir.path().to_owned(), dest_dir.clone(), cx)
2120 })?
2121 .await?;
2122
2123 let result = client
2124 .update(cx, |client, _cx| {
2125 client.proto_client().request(proto::InstallExtension {
2126 tmp_dir: dest_dir.to_proto(),
2127 extension: Some(missing_extension.clone()),
2128 })
2129 })?
2130 .await;
2131
2132 if let Err(e) = result {
2133 log::error!(
2134 "Failed to install extension {}: {}",
2135 missing_extension.id,
2136 e
2137 );
2138 }
2139 }
2140
2141 anyhow::Ok(())
2142 }
2143
2144 pub async fn update_remote_clients(this: &WeakEntity<Self>, cx: &mut AsyncApp) -> Result<()> {
2145 let clients = this.update(cx, |this, _cx| {
2146 this.remote_clients.retain(|v| v.upgrade().is_some());
2147 this.remote_clients.clone()
2148 })?;
2149
2150 for client in clients {
2151 Self::sync_extensions_to_remotes(this, client, cx)
2152 .await
2153 .log_err();
2154 }
2155
2156 anyhow::Ok(())
2157 }
2158
2159 pub fn register_remote_client(
2160 &mut self,
2161 client: Entity<RemoteClient>,
2162 _cx: &mut Context<Self>,
2163 ) {
2164 self.remote_clients.push(client.downgrade());
2165 self.ssh_registered_tx.unbounded_send(()).ok();
2166 }
2167}
2168
2169fn load_plugin_queries(root_path: &Path) -> LanguageQueries {
2170 let mut result = LanguageQueries::default();
2171 if let Some(entries) = std::fs::read_dir(root_path).log_err() {
2172 for entry in entries {
2173 let Some(entry) = entry.log_err() else {
2174 continue;
2175 };
2176 let path = entry.path();
2177 if let Some(remainder) = path.strip_prefix(root_path).ok().and_then(|p| p.to_str()) {
2178 if !remainder.ends_with(".scm") {
2179 continue;
2180 }
2181 for (name, query) in QUERY_FILENAME_PREFIXES {
2182 if remainder.starts_with(name) {
2183 if let Some(contents) = std::fs::read_to_string(&path).log_err() {
2184 match query(&mut result) {
2185 None => *query(&mut result) = Some(contents.into()),
2186 Some(r) => r.to_mut().push_str(contents.as_ref()),
2187 }
2188 }
2189 break;
2190 }
2191 }
2192 }
2193 }
2194 }
2195 result
2196}