extensions_ui: Show extension features on cards (#24310)

Marshall Bowers created

This PR updates the extensions list to display the features that an
extension provides.

<img width="1309" alt="Screenshot 2025-02-05 at 4 12 07 PM"
src="https://github.com/user-attachments/assets/ff0c61cd-b7fe-49c3-9fc8-a0ab6b0511a6"
/>

Note that this will only show up for extensions that have this data
(which will be extensions published/updated on or after now).

Here's the view with some mocked data:

<img width="1309" alt="Screenshot 2025-02-05 at 4 01 56 PM"
src="https://github.com/user-attachments/assets/d6d6a818-d6ac-4162-9309-95472b17833a"
/>

Release Notes:

- N/A

Change summary

crates/extensions_ui/src/extensions_ui.rs | 50 +++++++++++++++++++++++-
1 file changed, 47 insertions(+), 3 deletions(-)

Detailed changes

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -6,7 +6,7 @@ use std::sync::OnceLock;
 use std::time::Duration;
 use std::{ops::Range, sync::Arc};
 
-use client::ExtensionMetadata;
+use client::{ExtensionMetadata, ExtensionProvides};
 use collections::{BTreeMap, BTreeSet};
 use editor::{Editor, EditorElement, EditorStyle};
 use extension_host::{ExtensionManifest, ExtensionOperation, ExtensionStore};
@@ -575,7 +575,6 @@ impl ExtensionsPage {
                     .child(
                         h_flex()
                             .gap_2()
-                            .items_end()
                             .child(
                                 Headline::new(extension.manifest.name.clone())
                                     .size(HeadlineSize::Medium),
@@ -588,7 +587,52 @@ impl ExtensionsPage {
                                         Headline::new(format!("(v{installed_version} installed)",))
                                             .size(HeadlineSize::XSmall)
                                     }),
-                            ),
+                            )
+                            .map(|parent| {
+                                if extension.manifest.provides.is_empty() {
+                                    return parent;
+                                }
+
+                                parent.child(
+                                    h_flex().gap_2().children(
+                                        extension
+                                            .manifest
+                                            .provides
+                                            .iter()
+                                            .map(|provides| {
+                                                let label = match provides {
+                                                    ExtensionProvides::Themes => "Themes",
+                                                    ExtensionProvides::IconThemes => "Icon Themes",
+                                                    ExtensionProvides::Languages => "Languages",
+                                                    ExtensionProvides::Grammars => "Grammars",
+                                                    ExtensionProvides::LanguageServers => {
+                                                        "Language Servers"
+                                                    }
+                                                    ExtensionProvides::ContextServers => {
+                                                        "Context Servers"
+                                                    }
+                                                    ExtensionProvides::SlashCommands => {
+                                                        "Slash Commands"
+                                                    }
+                                                    ExtensionProvides::IndexedDocsProviders => {
+                                                        "Indexed Docs Providers"
+                                                    }
+                                                    ExtensionProvides::Snippets => "Snippets",
+                                                };
+                                                div()
+                                                    .bg(cx.theme().colors().element_background)
+                                                    .px_0p5()
+                                                    .border_1()
+                                                    .border_color(cx.theme().colors().border)
+                                                    .rounded_md()
+                                                    .child(
+                                                        Label::new(label).size(LabelSize::XSmall),
+                                                    )
+                                            })
+                                            .collect::<Vec<_>>(),
+                                    ),
+                                )
+                            }),
                     )
                     .child(
                         h_flex()