Detailed changes
@@ -34,6 +34,7 @@ use std::{
atomic::{AtomicBool, Ordering::SeqCst},
Arc,
},
+ time::Duration,
};
use unindent::Unindent as _;
@@ -5945,3 +5946,26 @@ async fn test_right_click_menu_behind_collab_panel(cx: &mut TestAppContext) {
});
assert!(cx.debug_bounds("MENU_ITEM-Close").is_some());
}
+
+#[gpui::test]
+async fn test_cmd_k_left(cx: &mut TestAppContext) {
+ let client = TestServer::start1(cx).await;
+ let (workspace, cx) = client.build_test_workspace(cx).await;
+
+ cx.simulate_keystrokes("cmd-n");
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 1);
+ });
+ cx.simulate_keystrokes("cmd-k left");
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
+ });
+ cx.simulate_keystrokes("cmd-k");
+ // sleep for longer than the timeout in keyboard shortcut handling
+ // to verify that it doesn't fire in this case.
+ cx.executor().advance_clock(Duration::from_secs(2));
+ cx.simulate_keystrokes("left");
+ workspace.update(cx, |workspace, cx| {
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 3);
+ });
+}
@@ -127,6 +127,11 @@ impl TestServer {
(client_a, client_b, channel_id)
}
+ pub async fn start1<'a>(cx: &'a mut TestAppContext) -> TestClient {
+ let mut server = Self::start(cx.executor().clone()).await;
+ server.create_client(cx, "user_a").await
+ }
+
pub async fn reset(&self) {
self.app_state.db.reset();
let epoch = self
@@ -272,12 +272,17 @@ impl DispatchTree {
.collect()
}
+ // dispatch_key pushses the next keystroke into any key binding matchers.
+ // any matching bindings are returned in the order that they should be dispatched:
+ // * First by length of binding (so if you have a binding for "b" and "ab", the "ab" binding fires first)
+ // * Secondly by depth in the tree (so if Editor has a binding for "b" and workspace a
+ // binding for "b", the Editor action fires first).
pub fn dispatch_key(
&mut self,
keystroke: &Keystroke,
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
) -> KeymatchResult {
- let mut bindings = SmallVec::new();
+ let mut bindings = SmallVec::<[KeyBinding; 1]>::new();
let mut pending = false;
let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
@@ -295,9 +300,19 @@ impl DispatchTree {
.entry(context_stack.clone())
.or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone()));
- let mut result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
+ let result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
pending = result.pending || pending;
- bindings.append(&mut result.bindings);
+ for new_binding in result.bindings {
+ match bindings
+ .iter()
+ .position(|el| el.keystrokes.len() < new_binding.keystrokes.len())
+ {
+ Some(idx) => {
+ bindings.insert(idx, new_binding);
+ }
+ None => bindings.push(new_binding),
+ }
+ }
context_stack.pop();
}
@@ -1186,18 +1186,21 @@ impl<'a> WindowContext<'a> {
currently_pending.bindings.push(binding);
}
- currently_pending.timer = Some(self.spawn(|mut cx| async move {
- cx.background_executor.timer(Duration::from_secs(1)).await;
- cx.update(move |cx| {
- cx.clear_pending_keystrokes();
- let Some(currently_pending) = cx.window.pending_input.take() else {
- return;
- };
- cx.replay_pending_input(currently_pending)
- })
- .log_err();
- }));
- self.window.pending_input = Some(currently_pending);
+ // for vim compatibility, we also shoul check "is input handler enabled"
+ if !currently_pending.text.is_empty() || !currently_pending.bindings.is_empty() {
+ currently_pending.timer = Some(self.spawn(|mut cx| async move {
+ cx.background_executor.timer(Duration::from_secs(1)).await;
+ cx.update(move |cx| {
+ cx.clear_pending_keystrokes();
+ let Some(currently_pending) = cx.window.pending_input.take() else {
+ return;
+ };
+ cx.replay_pending_input(currently_pending)
+ })
+ .log_err();
+ }));
+ self.window.pending_input = Some(currently_pending);
+ }
self.propagate_event = false;
return;