Disable extension entries when the corresponding dev extension is installed (#10580)

Marshall Bowers created

This PR updates the extension list to disable remote entries when the
corresponding dev extension is installed.

Here is what this looks like:

<img width="1189" alt="Screenshot 2024-04-15 at 4 09 45 PM"
src="https://github.com/zed-industries/zed/assets/1486634/48bb61d4-bc85-4ca6-b233-716831dfa7d8">


Release Notes:

- Disabled extension entries when there is a development copy of that
extension installed.

Change summary

crates/extensions_ui/src/components/extension_card.rs | 26 ++++++++++++
crates/extensions_ui/src/extensions_ui.rs             | 23 +++++++++++
2 files changed, 47 insertions(+), 2 deletions(-)

Detailed changes

crates/extensions_ui/src/components/extension_card.rs 🔗

@@ -4,15 +4,22 @@ use ui::prelude::*;
 
 #[derive(IntoElement)]
 pub struct ExtensionCard {
+    overridden_by_dev_extension: bool,
     children: SmallVec<[AnyElement; 2]>,
 }
 
 impl ExtensionCard {
     pub fn new() -> Self {
         Self {
+            overridden_by_dev_extension: false,
             children: SmallVec::new(),
         }
     }
+
+    pub fn overridden_by_dev_extension(mut self, overridden: bool) -> Self {
+        self.overridden_by_dev_extension = overridden;
+        self
+    }
 }
 
 impl ParentElement for ExtensionCard {
@@ -34,7 +41,24 @@ impl RenderOnce for ExtensionCard {
                 .border_1()
                 .border_color(cx.theme().colors().border)
                 .rounded_md()
-                .children(self.children),
+                .children(self.children)
+                .when(self.overridden_by_dev_extension, |card| {
+                    card.child(
+                        h_flex()
+                            .absolute()
+                            .top_0()
+                            .left_0()
+                            .occlude()
+                            .size_full()
+                            .items_center()
+                            .justify_center()
+                            .bg(theme::color_alpha(
+                                cx.theme().colors().elevated_surface_background,
+                                0.8,
+                            ))
+                            .child(Label::new("Overridden by dev extension.")),
+                    )
+                }),
         )
     }
 }

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -190,6 +190,15 @@ impl ExtensionsPage {
         }
     }
 
+    /// Returns whether a dev extension currently exists for the extension with the given ID.
+    fn dev_extension_exists(extension_id: &str, cx: &mut ViewContext<Self>) -> bool {
+        let extension_store = ExtensionStore::global(cx).read(cx);
+
+        extension_store
+            .dev_extensions()
+            .any(|dev_extension| dev_extension.id.as_ref() == extension_id)
+    }
+
     fn extension_status(extension_id: &str, cx: &mut ViewContext<Self>) -> ExtensionStatus {
         let extension_store = ExtensionStore::global(cx).read(cx);
 
@@ -417,10 +426,11 @@ impl ExtensionsPage {
     ) -> ExtensionCard {
         let this = cx.view().clone();
         let status = Self::extension_status(&extension.id, cx);
+        let has_dev_extension = Self::dev_extension_exists(&extension.id, cx);
 
         let extension_id = extension.id.clone();
         let (install_or_uninstall_button, upgrade_button) =
-            self.buttons_for_entry(extension, &status, cx);
+            self.buttons_for_entry(extension, &status, has_dev_extension, cx);
         let version = extension.manifest.version.clone();
         let repository_url = extension.manifest.repository.clone();
 
@@ -430,6 +440,7 @@ impl ExtensionsPage {
         };
 
         ExtensionCard::new()
+            .overridden_by_dev_extension(has_dev_extension)
             .child(
                 h_flex()
                     .justify_between()
@@ -588,10 +599,20 @@ impl ExtensionsPage {
         &self,
         extension: &ExtensionMetadata,
         status: &ExtensionStatus,
+        has_dev_extension: bool,
         cx: &mut ViewContext<Self>,
     ) -> (Button, Option<Button>) {
         let is_compatible = extension::is_version_compatible(&extension);
 
+        if has_dev_extension {
+            // If we have a dev extension for the given extension, just treat it as uninstalled.
+            // The button here is a placeholder, as it won't be interactable anyways.
+            return (
+                Button::new(SharedString::from(extension.id.clone()), "Install"),
+                None,
+            );
+        }
+
         match status.clone() {
             ExtensionStatus::NotInstalled => (
                 Button::new(SharedString::from(extension.id.clone()), "Install").on_click(