From 235a76ebaa3093c13881e3fa103918f605ac407d Mon Sep 17 00:00:00 2001 From: Julia Ryan Date: Thu, 18 Dec 2025 14:33:17 -0500 Subject: [PATCH] Add zed::PopKey action --- 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(-) diff --git a/assets/keymaps/vim.json b/assets/keymaps/vim.json index 6e5d3423872a7dd83234b28e67c5082b36bd858f..bc674206ef4708752fa40f60aa81b5574642730d 100644 --- a/assets/keymaps/vim.json +++ b/assets/keymaps/vim.json @@ -1097,4 +1097,10 @@ "ctrl-e": "markdown::ScrollDown", }, }, + { + "context": "pending", + "bindings": { + "backspace": "zed::PopKey" + } + }, ] diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 1b92b9fe3ffabdbeec4bc7450adc1439e8e223eb..ec1b2114725b15720c92fe2c7b5e8e849f29f29c 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/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) { - let context_stack: Vec = dispatch_path + let mut context_stack: Vec = 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(), diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 840f2223fcc4a62b6e522f38b967a3fe4ad3209e..a8ff48b11d1429c58ec71430fadb76d480f5c5f4 100644 --- a/crates/gpui/src/window.rs +++ b/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 diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index d088df00839814e32a9c246a3486ac5ad5ca4b9e..b4b723e91d6f545c0ae7a5e8563472fc4dc3c3ea 100644 --- a/crates/zed/src/zed.rs +++ b/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()],