Find keystrokes defined on a child but handled by a parent

Antonio Scandurra created

This fixes a bug that was preventing keystrokes from being shown on tooltips
for the "Buffer Search" and "Inline Assist" buttons in the toolbar.

This commit makes the behavior of `keystrokes_for_action` more consistent with
the behavior of `available_actions`. It seems reasonable that, if a child view
defines a keystroke for an action and that action is handled on a parent, we
should show the child's keystroke.

Change summary

crates/gpui/src/app.rs | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)

Detailed changes

crates/gpui/src/app.rs 🔗

@@ -3513,14 +3513,12 @@ impl<'a, 'b, 'c, V> LayoutContext<'a, 'b, 'c, V> {
             handler_depth = Some(contexts.len())
         }
 
-        let action_contexts = if let Some(depth) = handler_depth {
-            &contexts[depth..]
-        } else {
-            &contexts
-        };
-
-        self.keystroke_matcher
-            .keystrokes_for_action(action, action_contexts)
+        let handler_depth = handler_depth.unwrap_or(0);
+        (0..=handler_depth).find_map(|depth| {
+            let contexts = &contexts[depth..];
+            self.keystroke_matcher
+                .keystrokes_for_action(action, contexts)
+        })
     }
 
     fn notify_if_view_ancestors_change(&mut self, view_id: usize) {
@@ -6499,7 +6497,7 @@ mod tests {
 
     #[crate::test(self)]
     fn test_keystrokes_for_action(cx: &mut TestAppContext) {
-        actions!(test, [Action1, Action2, GlobalAction]);
+        actions!(test, [Action1, Action2, Action3, GlobalAction]);
 
         struct View1 {
             child: ViewHandle<View2>,
@@ -6542,12 +6540,14 @@ mod tests {
 
         cx.update(|cx| {
             cx.add_action(|_: &mut View1, _: &Action1, _cx| {});
+            cx.add_action(|_: &mut View1, _: &Action3, _cx| {});
             cx.add_action(|_: &mut View2, _: &Action2, _cx| {});
             cx.add_global_action(|_: &GlobalAction, _| {});
             cx.add_bindings(vec![
                 Binding::new("a", Action1, Some("View1")),
                 Binding::new("b", Action2, Some("View1 > View2")),
-                Binding::new("c", GlobalAction, Some("View3")), // View 3 does not exist
+                Binding::new("c", Action3, Some("View2")),
+                Binding::new("d", GlobalAction, Some("View3")), // View 3 does not exist
             ]);
         });
 
@@ -6577,6 +6577,14 @@ mod tests {
                         .as_slice(),
                     &[Keystroke::parse("b").unwrap()]
                 );
+                assert_eq!(layout_cx.keystrokes_for_action(view_1.id(), &Action3), None);
+                assert_eq!(
+                    layout_cx
+                        .keystrokes_for_action(view_2.id(), &Action3)
+                        .unwrap()
+                        .as_slice(),
+                    &[Keystroke::parse("c").unwrap()]
+                );
 
                 // The 'a' keystroke propagates up the view tree from view_2
                 // to view_1. The action, Action1, is handled by view_1.
@@ -6604,7 +6612,8 @@ mod tests {
             &available_actions(window.into(), view_1.id(), cx),
             &[
                 ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
-                ("test::GlobalAction", vec![])
+                ("test::Action3", vec![]),
+                ("test::GlobalAction", vec![]),
             ],
         );
 
@@ -6614,6 +6623,7 @@ mod tests {
             &[
                 ("test::Action1", vec![Keystroke::parse("a").unwrap()]),
                 ("test::Action2", vec![Keystroke::parse("b").unwrap()]),
+                ("test::Action3", vec![Keystroke::parse("c").unwrap()]),
                 ("test::GlobalAction", vec![]),
             ],
         );