Redine command palette style

Nate Butler , Marshall Bowers , and Conrad Irwin created

Co-Authored-By: Marshall Bowers <1486634+maxdeviant@users.noreply.github.com>
Co-Authored-By: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

crates/command_palette2/src/command_palette.rs | 24 +++++---
crates/picker2/src/picker2.rs                  | 58 ++++++++++++-------
2 files changed, 52 insertions(+), 30 deletions(-)

Detailed changes

crates/command_palette2/src/command_palette.rs 🔗

@@ -2,13 +2,13 @@ use collections::{CommandPaletteFilter, HashMap};
 use fuzzy::{StringMatch, StringMatchCandidate};
 use gpui::{
     actions, div, Action, AppContext, Component, Div, EventEmitter, FocusHandle, Keystroke,
-    ParentElement, Render, StatelessInteractive, Styled, View, ViewContext, VisualContext,
-    WeakView, WindowContext,
+    ParentElement, Render, SharedString, StatelessInteractive, Styled, View, ViewContext,
+    VisualContext, WeakView, WindowContext,
 };
 use picker::{Picker, PickerDelegate};
 use std::cmp::{self, Reverse};
 use theme::ActiveTheme;
-use ui::{v_stack, Label, StyledExt};
+use ui::{v_stack, HighlightedLabel, StyledExt};
 use util::{
     channel::{parse_zed_link, ReleaseChannel, RELEASE_CHANNEL},
     ResultExt,
@@ -147,6 +147,10 @@ impl CommandPaletteDelegate {
 impl PickerDelegate for CommandPaletteDelegate {
     type ListItem = Div<Picker<Self>>;
 
+    fn placeholder_text(&self) -> Arc<str> {
+        "Execute a command...".into()
+    }
+
     fn match_count(&self) -> usize {
         self.matches.len()
     }
@@ -296,11 +300,10 @@ impl PickerDelegate for CommandPaletteDelegate {
         cx: &mut ViewContext<Picker<Self>>,
     ) -> Self::ListItem {
         let colors = cx.theme().colors();
-        let Some(command) = self
-            .matches
-            .get(ix)
-            .and_then(|m| self.commands.get(m.candidate_id))
-        else {
+        let Some(r#match) = self.matches.get(ix) else {
+            return div();
+        };
+        let Some(command) = self.commands.get(r#match.candidate_id) else {
             return div();
         };
 
@@ -312,7 +315,10 @@ impl PickerDelegate for CommandPaletteDelegate {
             .rounded_md()
             .when(selected, |this| this.bg(colors.ghost_element_selected))
             .hover(|this| this.bg(colors.ghost_element_hover))
-            .child(Label::new(command.name.clone()))
+            .child(HighlightedLabel::new(
+                command.name.clone(),
+                r#match.positions.clone(),
+            ))
     }
 
     // fn render_match(

crates/picker2/src/picker2.rs 🔗

@@ -5,7 +5,7 @@ use gpui::{
     WindowContext,
 };
 use std::cmp;
-use ui::{prelude::*, v_stack, Divider};
+use ui::{prelude::*, v_stack, Divider, Label, LabelColor};
 
 pub struct Picker<D: PickerDelegate> {
     pub delegate: D,
@@ -21,7 +21,7 @@ pub trait PickerDelegate: Sized + 'static {
     fn selected_index(&self) -> usize;
     fn set_selected_index(&mut self, ix: usize, cx: &mut ViewContext<Picker<Self>>);
 
-    // fn placeholder_text(&self) -> Arc<str>;
+    fn placeholder_text(&self) -> Arc<str>;
     fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()>;
 
     fn confirm(&mut self, secondary: bool, cx: &mut ViewContext<Picker<Self>>);
@@ -37,7 +37,11 @@ pub trait PickerDelegate: Sized + 'static {
 
 impl<D: PickerDelegate> Picker<D> {
     pub fn new(delegate: D, cx: &mut ViewContext<Self>) -> Self {
-        let editor = cx.build_view(|cx| Editor::single_line(cx));
+        let editor = cx.build_view(|cx| {
+            let mut editor = Editor::single_line(cx);
+            editor.set_placeholder_text(delegate.placeholder_text(), cx);
+            editor
+        });
         cx.subscribe(&editor, Self::on_input_editor_event).detach();
         Self {
             delegate,
@@ -159,23 +163,35 @@ impl<D: PickerDelegate> Render for Picker<D> {
                     .child(div().px_1().py_0p5().child(self.editor.clone())),
             )
             .child(Divider::horizontal())
-            .child(
-                v_stack()
-                    .p_1()
-                    .grow()
-                    .child(
-                        uniform_list("candidates", self.delegate.match_count(), {
-                            move |this: &mut Self, visible_range, cx| {
-                                let selected_ix = this.delegate.selected_index();
-                                visible_range
-                                    .map(|ix| this.delegate.render_match(ix, ix == selected_ix, cx))
-                                    .collect()
-                            }
-                        })
-                        .track_scroll(self.scroll_handle.clone()),
-                    )
-                    .max_h_72()
-                    .overflow_hidden(),
-            )
+            .when(self.delegate.match_count() > 0, |el| {
+                el.child(
+                    v_stack()
+                        .p_1()
+                        .grow()
+                        .child(
+                            uniform_list("candidates", self.delegate.match_count(), {
+                                move |this: &mut Self, visible_range, cx| {
+                                    let selected_ix = this.delegate.selected_index();
+                                    visible_range
+                                        .map(|ix| {
+                                            this.delegate.render_match(ix, ix == selected_ix, cx)
+                                        })
+                                        .collect()
+                                }
+                            })
+                            .track_scroll(self.scroll_handle.clone()),
+                        )
+                        .max_h_72()
+                        .overflow_hidden(),
+                )
+            })
+            .when(self.delegate.match_count() == 0, |el| {
+                el.child(
+                    v_stack()
+                        .p_1()
+                        .grow()
+                        .child(Label::new("No matches").color(LabelColor::Muted)),
+                )
+            })
     }
 }