keymap_editor: Make "toggle exact match mode" the default for binding search (#42883)

Danilo Leal created

I think having the "exact mode" turned on by default is usually what
users will expect when searching for a specific keybinding. When it's
turned off, it's very odd to search for a super common binding like
"command-enter" and get no results. That happens because without that
mode, we're trying to match for subsequent matches, which I'm betting
it's an edge case. Hopefully, this change will make the keymap editor
feel more like it works well.

I'm also adding the toggle icon button inside the keystroke input for
consistency with the project search input.

Making this change very inspired by [Sam Rose's
feedback](https://bsky.app/profile/samwho.dev/post/3m5juszqyd22w).

Release Notes:

- keymap editor: Made the "toggle exact match mode" the default
keystroke search mode so that whatever you search for matches exactly to
results.

Change summary

crates/keymap_editor/src/keymap_editor.rs                 | 71 +++-----
crates/keymap_editor/src/ui_components/keystroke_input.rs | 10 +
2 files changed, 39 insertions(+), 42 deletions(-)

Detailed changes

crates/keymap_editor/src/keymap_editor.rs 🔗

@@ -184,7 +184,7 @@ enum SearchMode {
 impl SearchMode {
     fn invert(&self) -> Self {
         match self {
-            SearchMode::Normal => SearchMode::KeyStroke { exact_match: false },
+            SearchMode::Normal => SearchMode::KeyStroke { exact_match: true },
             SearchMode::KeyStroke { .. } => SearchMode::Normal,
         }
     }
@@ -1600,9 +1600,33 @@ impl Item for KeymapEditor {
 
 impl Render for KeymapEditor {
     fn render(&mut self, _window: &mut Window, cx: &mut ui::Context<Self>) -> impl ui::IntoElement {
+        if let SearchMode::KeyStroke { exact_match } = self.search_mode {
+            let button = IconButton::new("keystrokes-exact-match", IconName::CaseSensitive)
+                .tooltip(move |_window, cx| {
+                    Tooltip::for_action(
+                        "Toggle Exact Match Mode",
+                        &ToggleExactKeystrokeMatching,
+                        cx,
+                    )
+                })
+                .shape(IconButtonShape::Square)
+                .toggle_state(exact_match)
+                .on_click(cx.listener(|_, _, window, cx| {
+                    window.dispatch_action(ToggleExactKeystrokeMatching.boxed_clone(), cx);
+                }));
+
+            self.keystroke_editor.update(cx, |editor, _| {
+                editor.actions_slot = Some(button.into_any_element());
+            });
+        } else {
+            self.keystroke_editor.update(cx, |editor, _| {
+                editor.actions_slot = None;
+            });
+        }
+
         let row_count = self.matches.len();
-        let theme = cx.theme();
         let focus_handle = &self.focus_handle;
+        let theme = cx.theme();
 
         v_flex()
             .id("keymap-editor")
@@ -1786,49 +1810,14 @@ impl Render for KeymapEditor {
                                     )
                             ),
                     )
-                    .when_some(
-                        match self.search_mode {
-                            SearchMode::Normal => None,
-                            SearchMode::KeyStroke { exact_match } => Some(exact_match),
-                        },
-                        |this, exact_match| {
+                    .when(
+                        matches!(self.search_mode, SearchMode::KeyStroke { .. }),
+                        |this| {
                             this.child(
                                 h_flex()
                                     .gap_2()
                                     .child(self.keystroke_editor.clone())
-                                    .child(
-                                        h_flex()
-                                            .min_w_64()
-                                            .child(
-                                                IconButton::new(
-                                                    "keystrokes-exact-match",
-                                                    IconName::CaseSensitive,
-                                                )
-                                                .tooltip({
-                                                    let keystroke_focus_handle =
-                                                        self.keystroke_editor.read(cx).focus_handle(cx);
-
-                                                    move |_window, cx| {
-                                                        Tooltip::for_action_in(
-                                                            "Toggle Exact Match Mode",
-                                                            &ToggleExactKeystrokeMatching,
-                                                            &keystroke_focus_handle,
-                                                            cx,
-                                                        )
-                                                    }
-                                                })
-                                                .shape(IconButtonShape::Square)
-                                                .toggle_state(exact_match)
-                                                .on_click(
-                                                    cx.listener(|_, _, window, cx| {
-                                                        window.dispatch_action(
-                                                            ToggleExactKeystrokeMatching.boxed_clone(),
-                                                            cx,
-                                                        );
-                                                    }),
-                                                ),
-                                            ),
-                                    )
+                                    .child(div().min_w_64()), // Spacer div to align with the search input
                             )
                         },
                     ),

crates/keymap_editor/src/ui_components/keystroke_input.rs 🔗

@@ -64,6 +64,7 @@ pub struct KeystrokeInput {
     clear_close_keystrokes_timer: Option<Task<()>>,
     #[cfg(test)]
     recording: bool,
+    pub actions_slot: Option<AnyElement>,
 }
 
 impl KeystrokeInput {
@@ -94,6 +95,7 @@ impl KeystrokeInput {
             clear_close_keystrokes_timer: None,
             #[cfg(test)]
             recording: false,
+            actions_slot: None,
         }
     }
 
@@ -445,6 +447,11 @@ impl KeystrokeInput {
         // not get de-synced
         self.inner_focus_handle.is_focused(window)
     }
+
+    pub fn actions_slot(mut self, action: impl IntoElement) -> Self {
+        self.actions_slot = Some(action.into_any_element());
+        self
+    }
 }
 
 impl EventEmitter<()> for KeystrokeInput {}
@@ -586,7 +593,7 @@ impl Render for KeystrokeInput {
                     .min_w_0()
                     .justify_center()
                     .flex_wrap()
-                    .gap(ui::DynamicSpacing::Base04.rems(cx))
+                    .gap_1()
                     .children(self.render_keystrokes(is_recording)),
             )
             .child(
@@ -636,6 +643,7 @@ impl Render for KeystrokeInput {
                             )
                         }
                     })
+                    .when_some(self.actions_slot.take(), |this, action| this.child(action))
                     .when(is_recording, |this| {
                         this.child(
                             IconButton::new("clear-btn", IconName::Backspace)