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