Detailed changes
@@ -272,6 +272,7 @@ pub struct App {
// TypeId is the type of the event that the listener callback expects
pub(crate) event_listeners: SubscriberSet<EntityId, (TypeId, Listener)>,
pub(crate) keystroke_observers: SubscriberSet<(), KeystrokeObserver>,
+ pub(crate) keystroke_interceptors: SubscriberSet<(), KeystrokeObserver>,
pub(crate) keyboard_layout_observers: SubscriberSet<(), Handler>,
pub(crate) release_listeners: SubscriberSet<EntityId, ReleaseListener>,
pub(crate) global_observers: SubscriberSet<TypeId, Handler>,
@@ -344,6 +345,7 @@ impl App {
event_listeners: SubscriberSet::new(),
release_listeners: SubscriberSet::new(),
keystroke_observers: SubscriberSet::new(),
+ keystroke_interceptors: SubscriberSet::new(),
keyboard_layout_observers: SubscriberSet::new(),
global_observers: SubscriberSet::new(),
quit_observers: SubscriberSet::new(),
@@ -1322,6 +1324,32 @@ impl App {
)
}
+ /// Register a callback to be invoked when a keystroke is received by the application
+ /// in any window. Note that this fires _before_ all other action and event mechanisms have resolved
+ /// unlike [`App::observe_keystrokes`] which fires after. This means that `cx.stop_propagation` calls
+ /// within interceptors will prevent action dispatch
+ pub fn intercept_keystrokes(
+ &mut self,
+ mut f: impl FnMut(&KeystrokeEvent, &mut Window, &mut App) + 'static,
+ ) -> Subscription {
+ fn inner(
+ keystroke_interceptors: &SubscriberSet<(), KeystrokeObserver>,
+ handler: KeystrokeObserver,
+ ) -> Subscription {
+ let (subscription, activate) = keystroke_interceptors.insert((), handler);
+ activate();
+ subscription
+ }
+
+ inner(
+ &mut self.keystroke_interceptors,
+ Box::new(move |event, window, cx| {
+ f(event, window, cx);
+ true
+ }),
+ )
+ }
+
/// Register key bindings.
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
self.keymap.borrow_mut().add_bindings(bindings);
@@ -1369,6 +1369,31 @@ impl Window {
});
}
+ pub(crate) fn dispatch_keystroke_interceptors(
+ &mut self,
+ event: &dyn Any,
+ context_stack: Vec<KeyContext>,
+ cx: &mut App,
+ ) {
+ let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() else {
+ return;
+ };
+
+ cx.keystroke_interceptors
+ .clone()
+ .retain(&(), move |callback| {
+ (callback)(
+ &KeystrokeEvent {
+ keystroke: key_down_event.keystroke.clone(),
+ action: None,
+ context_stack: context_stack.clone(),
+ },
+ self,
+ cx,
+ )
+ });
+ }
+
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
/// that are currently on the stack to be returned to the app.
pub fn defer(&self, cx: &mut App, f: impl FnOnce(&mut Window, &mut App) + 'static) {
@@ -3522,6 +3547,13 @@ impl Window {
return;
};
+ cx.propagate_event = true;
+ self.dispatch_keystroke_interceptors(event, self.context_stack(), cx);
+ if !cx.propagate_event {
+ self.finish_dispatch_key_event(event, dispatch_path, self.context_stack(), cx);
+ return;
+ }
+
let mut currently_pending = self.pending_input.take().unwrap_or_default();
if currently_pending.focus.is_some() && currently_pending.focus != self.focus {
currently_pending = PendingInput::default();
@@ -3570,7 +3602,6 @@ impl Window {
return;
}
- cx.propagate_event = true;
for binding in match_result.bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref(), cx);
if !cx.propagate_event {
@@ -916,7 +916,7 @@ impl KeybindingEditorModal {
window: &mut Window,
cx: &mut App,
) -> Self {
- let keybind_editor = cx.new(KeystrokeInput::new);
+ let keybind_editor = cx.new(|cx| KeystrokeInput::new(window, cx));
let context_editor = cx.new(|cx| {
let mut editor = Editor::single_line(window, cx);
@@ -1315,14 +1315,22 @@ async fn save_keybinding_update(
struct KeystrokeInput {
keystrokes: Vec<Keystroke>,
focus_handle: FocusHandle,
+ intercept_subscription: Option<Subscription>,
+ _focus_subscriptions: [Subscription; 2],
}
impl KeystrokeInput {
- fn new(cx: &mut Context<Self>) -> Self {
+ fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let focus_handle = cx.focus_handle();
+ let _focus_subscriptions = [
+ cx.on_focus_in(&focus_handle, window, Self::on_focus_in),
+ cx.on_focus_out(&focus_handle, window, Self::on_focus_out),
+ ];
Self {
keystrokes: Vec::new(),
focus_handle,
+ intercept_subscription: None,
+ _focus_subscriptions,
}
}
@@ -1351,21 +1359,13 @@ impl KeystrokeInput {
cx.notify();
}
- fn on_key_down(
- &mut self,
- event: &gpui::KeyDownEvent,
- _window: &mut Window,
- cx: &mut Context<Self>,
- ) {
- if event.is_held {
- return;
- }
+ fn handle_keystroke(&mut self, keystroke: &Keystroke, cx: &mut Context<Self>) {
if let Some(last) = self.keystrokes.last_mut()
&& last.key.is_empty()
{
- *last = event.keystroke.clone();
- } else {
- self.keystrokes.push(event.keystroke.clone());
+ *last = keystroke.clone();
+ } else if Some(keystroke) != self.keystrokes.last() {
+ self.keystrokes.push(keystroke.clone());
}
cx.stop_propagation();
cx.notify();
@@ -1391,6 +1391,24 @@ impl KeystrokeInput {
cx.notify();
}
+ fn on_focus_in(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
+ if self.intercept_subscription.is_none() {
+ let listener = cx.listener(|this, event: &gpui::KeystrokeEvent, _window, cx| {
+ this.handle_keystroke(&event.keystroke, cx);
+ });
+ self.intercept_subscription = Some(cx.intercept_keystrokes(listener))
+ }
+ }
+
+ fn on_focus_out(
+ &mut self,
+ _event: gpui::FocusOutEvent,
+ _window: &mut Window,
+ _cx: &mut Context<Self>,
+ ) {
+ self.intercept_subscription.take();
+ }
+
fn keystrokes(&self) -> &[Keystroke] {
if self
.keystrokes
@@ -1418,7 +1436,6 @@ impl Render for KeystrokeInput {
.id("keybinding_input")
.track_focus(&self.focus_handle)
.on_modifiers_changed(cx.listener(Self::on_modifiers_changed))
- .on_key_down(cx.listener(Self::on_key_down))
.on_key_up(cx.listener(Self::on_key_up))
.focus(|mut style| {
style.border_color = Some(colors.border_focused);