Allow extensions to be filtered on installed and not installed (#8375)

Joseph T. Lyons created

Partially resolves: https://github.com/zed-industries/zed/issues/7785

Right now, we can engage `Only show installed`, but I've been wanting to
be able to filter down to just uninstalled extensions too, so I can
browse things I don't have. I changed this to have 2 checkboxes,
`Installed` and `Not installed` and both are on by default. You deselect
them to filter down.

<img width="1608" alt="SCR-20240225-etyg"
src="https://github.com/zed-industries/zed/assets/19867440/e2267651-ff86-437b-ba59-89f3d338ea02">

Release Notes:

- Allow extensions list to be filtered down to both installed and not
installed.

Change summary

crates/extensions_ui/src/extensions_ui.rs | 60 ++++++++++++++++--------
1 file changed, 39 insertions(+), 21 deletions(-)

Detailed changes

crates/extensions_ui/src/extensions_ui.rs 🔗

@@ -34,7 +34,8 @@ pub struct ExtensionsPage {
     list: UniformListScrollHandle,
     telemetry: Arc<Telemetry>,
     is_fetching_extensions: bool,
-    is_only_showing_installed_extensions: bool,
+    is_showing_installed_extensions: bool,
+    is_showing_not_installed_extensions: bool,
     extension_entries: Vec<Extension>,
     query_editor: View<Editor>,
     query_contains_error: bool,
@@ -55,7 +56,8 @@ impl ExtensionsPage {
                 list: UniformListScrollHandle::new(),
                 telemetry: workspace.client().telemetry().clone(),
                 is_fetching_extensions: false,
-                is_only_showing_installed_extensions: false,
+                is_showing_installed_extensions: true,
+                is_showing_not_installed_extensions: true,
                 extension_entries: Vec::new(),
                 query_contains_error: false,
                 extension_fetch_task: None,
@@ -73,12 +75,16 @@ impl ExtensionsPage {
         self.extension_entries
             .iter()
             .filter(|extension| {
-                if self.is_only_showing_installed_extensions {
-                    let status = extension_store.extension_status(&extension.id);
-
-                    matches!(status, ExtensionStatus::Installed(_))
-                } else {
-                    true
+                let status = extension_store.extension_status(&extension.id);
+
+                match [
+                    self.is_showing_installed_extensions,
+                    self.is_showing_not_installed_extensions,
+                ] {
+                    [true, true] => true,
+                    [true, false] => matches!(status, ExtensionStatus::Installed(_)),
+                    [false, true] => matches!(status, ExtensionStatus::NotInstalled),
+                    [false, false] => false,
                 }
             })
             .cloned()
@@ -415,19 +421,15 @@ impl ExtensionsPage {
     }
 
     fn render_empty_state(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
-        let has_search = self.search_query(cx).is_some();
+        let is_filtering = self.search_query(cx).is_some()
+            || self.is_showing_installed_extensions
+            || self.is_showing_not_installed_extensions;
 
         let message = if self.is_fetching_extensions {
             "Loading extensions..."
-        } else if self.is_only_showing_installed_extensions {
-            if has_search {
-                "No installed extensions that match your search."
-            } else {
-                "No installed extensions."
-            }
         } else {
-            if has_search {
-                "No extensions that match your search."
+            if is_filtering {
+                "No extensions that match your search criteria."
             } else {
                 "No extensions."
             }
@@ -455,15 +457,31 @@ impl Render for ExtensionsPage {
                     .gap_2()
                     .child(h_flex().child(self.render_search(cx)))
                     .child(CheckboxWithLabel::new(
-                        "installed",
-                        Label::new("Only show installed"),
-                        if self.is_only_showing_installed_extensions {
+                        "Installed",
+                        Label::new("Installed"),
+                        if self.is_showing_installed_extensions {
+                            Selection::Selected
+                        } else {
+                            Selection::Unselected
+                        },
+                        cx.listener(|this, selection, _cx| {
+                            this.is_showing_installed_extensions = match selection {
+                                Selection::Selected => true,
+                                Selection::Unselected => false,
+                                Selection::Indeterminate => return,
+                            }
+                        }),
+                    ))
+                    .child(CheckboxWithLabel::new(
+                        "not installed",
+                        Label::new("Not installed"),
+                        if self.is_showing_not_installed_extensions {
                             Selection::Selected
                         } else {
                             Selection::Unselected
                         },
                         cx.listener(|this, selection, _cx| {
-                            this.is_only_showing_installed_extensions = match selection {
+                            this.is_showing_not_installed_extensions = match selection {
                                 Selection::Selected => true,
                                 Selection::Unselected => false,
                                 Selection::Indeterminate => return,