extensions_ui: Add upsell banners for integrated extensions (#43872)

Rani Malach and Marshall Bowers created

Add informational banners for extensions that have been integrated into
Zed core:
- Basedpyright (Python language server)
- Ruff (Python linter)
- Ty (Python language server)

These banners appear when users search for these extensions, informing
them that the functionality is now built-in and linking to relevant
documentation.

The banners trigger when:
- Users search by extension ID (e.g., 'id:ruff')
- Users search using relevant keywords (e.g., 'basedpyright', 'pyright',
'ruff', 'ty')

Supersedes #43844

Closes #43837

Release Notes:

- Added banners to the extensions page when searching for Basedpyright,
Ruff, or Ty, indicating that these features are now built-in.

---------

Co-authored-by: Marshall Bowers <git@maxdeviant.com>

Change summary

crates/extensions_ui/src/extensions_ui.rs | 36 +++++++++++++++++++++++++
1 file changed, 36 insertions(+)

Detailed changes

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -229,8 +229,10 @@ enum Feature {
     AgentClaude,
     AgentCodex,
     AgentGemini,
+    ExtensionBasedpyright,
     ExtensionRuff,
     ExtensionTailwind,
+    ExtensionTy,
     Git,
     LanguageBash,
     LanguageC,
@@ -251,8 +253,13 @@ fn keywords_by_feature() -> &'static BTreeMap<Feature, Vec<&'static str>> {
             (Feature::AgentClaude, vec!["claude", "claude code"]),
             (Feature::AgentCodex, vec!["codex", "codex cli"]),
             (Feature::AgentGemini, vec!["gemini", "gemini cli"]),
+            (
+                Feature::ExtensionBasedpyright,
+                vec!["basedpyright", "pyright"],
+            ),
             (Feature::ExtensionRuff, vec!["ruff"]),
             (Feature::ExtensionTailwind, vec!["tail", "tailwind"]),
+            (Feature::ExtensionTy, vec!["ty"]),
             (Feature::Git, vec!["git"]),
             (Feature::LanguageBash, vec!["sh", "bash"]),
             (Feature::LanguageC, vec!["c", "clang"]),
@@ -1364,6 +1371,23 @@ impl ExtensionsPage {
             return;
         };
 
+        if let Some(id) = search.strip_prefix("id:") {
+            self.upsells.clear();
+
+            let upsell = match id.to_lowercase().as_str() {
+                "ruff" => Some(Feature::ExtensionRuff),
+                "basedpyright" => Some(Feature::ExtensionBasedpyright),
+                "ty" => Some(Feature::ExtensionTy),
+                _ => None,
+            };
+
+            if let Some(upsell) = upsell {
+                self.upsells.insert(upsell);
+            }
+
+            return;
+        }
+
         let search = search.to_lowercase();
         let search_terms = search
             .split_whitespace()
@@ -1482,6 +1506,12 @@ impl ExtensionsPage {
                     false,
                     cx,
                 ),
+                Feature::ExtensionBasedpyright => self.render_feature_upsell_banner(
+                    "Basedpyright (Python language server) support is built-in to Zed!".into(),
+                    "https://zed.dev/docs/languages/python#basedpyright".into(),
+                    false,
+                    cx,
+                ),
                 Feature::ExtensionRuff => self.render_feature_upsell_banner(
                     "Ruff (linter for Python) support is built-in to Zed!".into(),
                     "https://zed.dev/docs/languages/python#code-formatting--linting".into(),
@@ -1494,6 +1524,12 @@ impl ExtensionsPage {
                     false,
                     cx,
                 ),
+                Feature::ExtensionTy => self.render_feature_upsell_banner(
+                    "Ty (Python language server) support is built-in to Zed!".into(),
+                    "https://zed.dev/docs/languages/python".into(),
+                    false,
+                    cx,
+                ),
                 Feature::Git => self.render_feature_upsell_banner(
                     "Zed comes with basic Git support—more features are coming in the future."
                         .into(),