Add zed::PopKey action

Julia Ryan created

Change summary

assets/keymaps/vim.json         |  6 ++++++
crates/gpui/src/key_dispatch.rs | 15 ++++++++++++---
crates/gpui/src/window.rs       | 21 +++++++++++++++++++++
crates/zed/src/zed.rs           |  5 +++++
4 files changed, 44 insertions(+), 3 deletions(-)

Detailed changes

assets/keymaps/vim.json 🔗

@@ -1097,4 +1097,10 @@
       "ctrl-e": "markdown::ScrollDown",
     },
   },
+  {
+    "context": "pending",
+    "bindings": {
+      "backspace": "zed::PopKey"
+    }
+  },
 ]

crates/gpui/src/key_dispatch.rs 🔗

@@ -449,12 +449,19 @@ impl DispatchTree {
         &self,
         input: &[Keystroke],
         dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
+        has_pending_keystrokes: bool,
     ) -> (SmallVec<[KeyBinding; 1]>, bool, Vec<KeyContext>) {
-        let context_stack: Vec<KeyContext> = dispatch_path
+        let mut context_stack: Vec<KeyContext> = dispatch_path
             .iter()
             .filter_map(|node_id| self.node(*node_id).context.clone())
             .collect();
 
+        if has_pending_keystrokes {
+            let mut pending_context = KeyContext::default();
+            pending_context.add("pending");
+            context_stack.push(pending_context);
+        }
+
         let (bindings, partial) = self
             .keymap
             .borrow()
@@ -486,8 +493,10 @@ impl DispatchTree {
         keystroke: Keystroke,
         dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
     ) -> DispatchResult {
+        let has_pending_keystrokes = !input.is_empty();
         input.push(keystroke.clone());
-        let (bindings, pending, context_stack) = self.bindings_for_input(&input, dispatch_path);
+        let (bindings, pending, context_stack) =
+            self.bindings_for_input(&input, dispatch_path, has_pending_keystrokes);
 
         if pending {
             return DispatchResult {
@@ -542,7 +551,7 @@ impl DispatchTree {
     ) -> (SmallVec<[Keystroke; 1]>, SmallVec<[Replay; 1]>) {
         let mut to_replay: SmallVec<[Replay; 1]> = Default::default();
         for last in (0..input.len()).rev() {
-            let (bindings, _, _) = self.bindings_for_input(&input[0..=last], dispatch_path);
+            let (bindings, _, _) = self.bindings_for_input(&input[0..=last], dispatch_path, false);
             if !bindings.is_empty() {
                 to_replay.push(Replay {
                     keystroke: input.drain(0..=last).next_back().unwrap(),

crates/gpui/src/window.rs 🔗

@@ -4098,6 +4098,27 @@ impl Window {
         self.pending_input.take();
     }
 
+    /// Removes the last pending keystroke from the pending input sequence.
+    /// Returns true if a keystroke was removed, false if there were no pending keystrokes.
+    pub fn pop_pending_keystroke(&mut self, cx: &mut App) -> bool {
+        let Some(pending) = self.pending_input.as_mut() else {
+            return false;
+        };
+
+        if pending.keystrokes.is_empty() {
+            return false;
+        }
+
+        pending.keystrokes.pop();
+
+        if pending.keystrokes.is_empty() {
+            self.pending_input.take();
+        }
+
+        self.pending_input_changed(cx);
+        true
+    }
+
     /// Returns the currently pending input keystrokes that might result in a multi-stroke key binding.
     pub fn pending_input_keystrokes(&self) -> Option<&[Keystroke]> {
         self.pending_input

crates/zed/src/zed.rs 🔗

@@ -130,6 +130,8 @@ actions!(
         TestPanic,
         /// Triggers a hard crash for debugging.
         TestCrash,
+        /// Removes the latest pending keystroke.
+        PopKey,
     ]
 );
 
@@ -816,6 +818,9 @@ fn register_actions(
         .register_action(|_, _: &ToggleFullScreen, window, _| {
             window.toggle_fullscreen();
         })
+        .register_action(|_, _: &PopKey, window, cx| {
+            window.pop_pending_keystroke(cx);
+        })
         .register_action(|_, action: &OpenZedUrl, _, cx| {
             OpenListener::global(cx).open(RawOpenRequest {
                 urls: vec![action.url.clone()],