diff --git a/assets/keymaps/default.json b/assets/keymaps/default.json index 8679296733559a03c51a94d1f133c6bd922eadf9..06358dab225af5c06555c5a2b2d49838c8fb08ad 100644 --- a/assets/keymaps/default.json +++ b/assets/keymaps/default.json @@ -415,7 +415,15 @@ "cmd-?": "assistant::ToggleFocus", "cmd-alt-s": "workspace::SaveAll", "cmd-k m": "language_selector::Toggle", - "escape": "workspace::Unfollow" + "escape": "workspace::Unfollow", + "cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"], + "cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"], + "cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"], + "cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"], + "cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"], + "cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"], + "cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"], + "cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"] } }, // Bindings from Sublime Text @@ -441,18 +449,6 @@ "ctrl-alt-shift-f": "editor::SelectToNextSubwordEnd" } }, - { - "bindings": { - "cmd-k cmd-left": ["workspace::ActivatePaneInDirection", "Left"], - "cmd-k cmd-right": ["workspace::ActivatePaneInDirection", "Right"], - "cmd-k cmd-up": ["workspace::ActivatePaneInDirection", "Up"], - "cmd-k cmd-down": ["workspace::ActivatePaneInDirection", "Down"], - "cmd-k shift-left": ["workspace::SwapPaneInDirection", "Left"], - "cmd-k shift-right": ["workspace::SwapPaneInDirection", "Right"], - "cmd-k shift-up": ["workspace::SwapPaneInDirection", "Up"], - "cmd-k shift-down": ["workspace::SwapPaneInDirection", "Down"] - } - }, // Bindings from Atom { "context": "Pane", diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 625544e4a1572b67ccb09cb52a256631cb5ec88b..eb0beb6f6653d7f1a5846e8584fc522ab44aad53 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -5967,6 +5967,6 @@ async fn test_cmd_k_left(cx: &mut TestAppContext) { cx.executor().advance_clock(Duration::from_secs(2)); cx.simulate_keystrokes("left"); workspace.update(cx, |workspace, cx| { - assert!(workspace.items(cx).collect::>().len() == 3); + assert!(workspace.items(cx).collect::>().len() == 2); }); } diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index c6a2e1788453cc1a5c654f0e7c2f4fef70cfabb4..8f9eb16a62584d31465256b5e397f29a90079e8d 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -62,16 +62,6 @@ use std::{ rc::Rc, }; -/// KeymatchMode controls how keybindings are resolved in the case of conflicting pending keystrokes. -/// When `Sequenced`, gpui will wait for 1s for sequences to complete. -/// When `Immediate`, gpui will immediately resolve the keybinding. -#[derive(Default, PartialEq)] -pub enum KeymatchMode { - #[default] - Sequenced, - Immediate, -} - #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)] pub(crate) struct DispatchNodeId(usize); @@ -84,7 +74,6 @@ pub(crate) struct DispatchTree { keystroke_matchers: FxHashMap, KeystrokeMatcher>, keymap: Rc>, action_registry: Rc, - pub(crate) keymatch_mode: KeymatchMode, } #[derive(Default)] @@ -116,7 +105,6 @@ impl DispatchTree { keystroke_matchers: FxHashMap::default(), keymap, action_registry, - keymatch_mode: KeymatchMode::Sequenced, } } @@ -127,7 +115,6 @@ impl DispatchTree { self.focusable_node_ids.clear(); self.view_node_ids.clear(); self.keystroke_matchers.clear(); - self.keymatch_mode = KeymatchMode::Sequenced; } pub fn push_node( @@ -335,7 +322,7 @@ impl DispatchTree { .collect() } - // dispatch_key pushses the next keystroke into any key binding matchers. + // dispatch_key pushes 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 @@ -364,6 +351,11 @@ impl DispatchTree { .or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone())); let result = keystroke_matcher.match_keystroke(keystroke, &context_stack); + if result.pending && !pending && !bindings.is_empty() { + context_stack.pop(); + continue; + } + pending = result.pending || pending; for new_binding in result.bindings { match bindings diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 10929477f0b600c01de9cb7d82a6e49c997e0690..13051be58221ca2a25e8c2ac013a049471f4065d 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2,12 +2,12 @@ use crate::{ px, size, transparent_black, Action, AnyDrag, AnyView, AppContext, Arena, AsyncWindowContext, AvailableSpace, Bounds, Context, Corners, CursorStyle, DispatchActionListener, DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten, - Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchMode, - KeymatchResult, Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, - MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, - PlatformWindow, Point, PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, - Subscription, TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds, - WindowOptions, WindowTextSystem, + Global, GlobalElementId, Hsla, KeyBinding, KeyContext, KeyDownEvent, KeyMatch, KeymatchResult, + Keystroke, KeystrokeEvent, Model, ModelContext, Modifiers, MouseButton, MouseMoveEvent, + MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformWindow, Point, + PromptLevel, Render, ScaledPixels, SharedString, Size, SubscriberSet, Subscription, + TaffyLayoutEngine, Task, View, VisualContext, WeakView, WindowBounds, WindowOptions, + WindowTextSystem, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -289,10 +289,6 @@ struct PendingInput { } impl PendingInput { - fn is_noop(&self) -> bool { - self.bindings.is_empty() && (self.keystrokes.iter().all(|k| k.ime_key.is_none())) - } - fn input(&self) -> String { self.keystrokes .iter() @@ -1255,21 +1251,12 @@ impl<'a> WindowContext<'a> { .dispatch_path(node_id); if let Some(key_down_event) = event.downcast_ref::() { - let KeymatchResult { - bindings, - mut pending, - } = self + let KeymatchResult { bindings, pending } = self .window .rendered_frame .dispatch_tree .dispatch_key(&key_down_event.keystroke, &dispatch_path); - if self.window.rendered_frame.dispatch_tree.keymatch_mode == KeymatchMode::Immediate - && !bindings.is_empty() - { - pending = false; - } - if pending { let mut currently_pending = self.window.pending_input.take().unwrap_or_default(); if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus @@ -1284,22 +1271,17 @@ impl<'a> WindowContext<'a> { currently_pending.bindings.push(binding); } - // for vim compatibility, we also should check "is input handler enabled" - if !currently_pending.is_noop() { - 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(); - })); - } else { - currently_pending.timer = None; - } + 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; @@ -1327,8 +1309,21 @@ impl<'a> WindowContext<'a> { } } + self.dispatch_key_down_up_event(event, &dispatch_path); + if !self.propagate_event { + return; + } + + self.dispatch_keystroke_observers(event, None); + } + + fn dispatch_key_down_up_event( + &mut self, + event: &dyn Any, + dispatch_path: &SmallVec<[DispatchNodeId; 32]>, + ) { // Capture phase - for node_id in &dispatch_path { + for node_id in dispatch_path { let node = self.window.rendered_frame.dispatch_tree.node(*node_id); for key_listener in node.key_listeners.clone() { @@ -1354,8 +1349,6 @@ impl<'a> WindowContext<'a> { } } } - - self.dispatch_keystroke_observers(event, None); } /// Determine whether a potential multi-stroke key binding is in progress on this window. @@ -1392,6 +1385,24 @@ impl<'a> WindowContext<'a> { } } + let dispatch_path = self + .window + .rendered_frame + .dispatch_tree + .dispatch_path(node_id); + + for keystroke in currently_pending.keystrokes { + let event = KeyDownEvent { + keystroke, + is_held: false, + }; + + self.dispatch_key_down_up_event(&event, &dispatch_path); + if !self.propagate_event { + return; + } + } + if !input.is_empty() { if let Some(mut input_handler) = self.window.platform_window.take_input_handler() { input_handler.flush_pending_input(&input, self); diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index c7814fc10100763bb8162bc247e775172a198f89..8ba3fc5c4fc94c27629d18efee3c45e023b4a8a1 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -31,11 +31,11 @@ use crate::{ prelude::*, size, AnyTooltip, AppContext, AvailableSpace, Bounds, BoxShadow, ContentMask, Corners, CursorStyle, DevicePixels, DispatchPhase, DispatchTree, ElementId, ElementStateBox, EntityId, FocusHandle, FocusId, FontId, GlobalElementId, GlyphId, Hsla, ImageData, - InputHandler, IsZero, KeyContext, KeyEvent, KeymatchMode, LayoutId, MonochromeSprite, - MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, - RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, - StackingContext, StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, - Window, WindowContext, SUBPIXEL_VARIANTS, + InputHandler, IsZero, KeyContext, KeyEvent, LayoutId, MonochromeSprite, MouseEvent, PaintQuad, + Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad, RenderGlyphParams, + RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size, StackingContext, + StackingOrder, Style, Surface, TextStyleRefinement, Underline, UnderlineStyle, Window, + WindowContext, SUBPIXEL_VARIANTS, }; type AnyMouseListener = Box; @@ -1143,15 +1143,6 @@ impl<'a> ElementContext<'a> { } } - /// keymatch mode immediate instructs GPUI to prefer shorter action bindings. - /// In the case that you have a keybinding of `"cmd-k": "terminal::Clear"` and - /// `"cmd-k left": "workspace::MoveLeft"`, GPUI will by default wait for 1s after - /// you type cmd-k to see if you're going to type left. - /// This is problematic in the terminal - pub fn keymatch_mode_immediate(&mut self) { - self.window.next_frame.dispatch_tree.keymatch_mode = KeymatchMode::Immediate; - } - /// Register a mouse event listener on the window for the next frame. The type of event /// is determined by the first parameter of the given listener. When the next frame is rendered /// the listener will be cleared. diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 73d9bb27f539bce4ec25ea4ff2e04753bd11e96a..66ef42bc46ebb09244ff4ae45c7cb26bbd6b3ead 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -776,7 +776,6 @@ impl Element for TerminalElement { self.interactivity .paint(bounds, bounds.size, state, cx, |_, _, cx| { cx.handle_input(&self.focus, terminal_input_handler); - cx.keymatch_mode_immediate(); cx.on_key_event({ let this = self.terminal.clone();