From c5d7c8e12204a8689699d8c788181c047c5c67cd Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Mon, 22 Jan 2024 08:38:20 -0700 Subject: [PATCH] Fix cmd-k left --- crates/collab/src/tests/integration_tests.rs | 24 +++++++++++++++++ crates/collab/src/tests/test_server.rs | 5 ++++ crates/gpui/src/key_dispatch.rs | 21 ++++++++++++--- crates/gpui/src/window.rs | 27 +++++++++++--------- 4 files changed, 62 insertions(+), 15 deletions(-) diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index a8e52f4094c83ac79a29c9b30eae666566de96e2..90fdc64e260cadd38e1148c66670059f70a68be4 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -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::>().len() == 1); + }); + cx.simulate_keystrokes("cmd-k left"); + workspace.update(cx, |workspace, cx| { + assert!(workspace.items(cx).collect::>().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::>().len() == 3); + }); +} diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index ea08d83b6cbe71c4516c1c8bab4d46edce6cf60d..8efd9535b09bfb5e6243852a648c6cb1d5ad0450 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -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 diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index 08ada73d10d6a9290bcc81ffb65c538736d16120..9129cdf31c41ba2effbedecd7dade7e5acbfe301 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -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(); } diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 6aa7e73f2d0b969749366805cb219952cb95a961..2cf27fc1cd01d0dea07aef1999d78b14c5bc6357 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -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;