diff --git a/crates/gpui/src/key_dispatch.rs b/crates/gpui/src/key_dispatch.rs index dd5a7ab84e..59c75abd5e 100644 --- a/crates/gpui/src/key_dispatch.rs +++ b/crates/gpui/src/key_dispatch.rs @@ -63,6 +63,16 @@ use std::{ sync::Arc, }; +/// 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); @@ -75,6 +85,7 @@ pub(crate) struct DispatchTree { keystroke_matchers: FxHashMap, KeystrokeMatcher>, keymap: Arc>, action_registry: Rc, + pub(crate) keymatch_mode: KeymatchMode, } #[derive(Default)] @@ -106,6 +117,7 @@ impl DispatchTree { keystroke_matchers: FxHashMap::default(), keymap, action_registry, + keymatch_mode: KeymatchMode::Sequenced, } } @@ -116,6 +128,7 @@ 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( diff --git a/crates/gpui/src/window.rs b/crates/gpui/src/window.rs index 5796d139f9..e026c71185 100644 --- a/crates/gpui/src/window.rs +++ b/crates/gpui/src/window.rs @@ -2,11 +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, - 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, + 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, }; use anyhow::{anyhow, Context as _, Result}; use collections::FxHashSet; @@ -1214,12 +1215,21 @@ impl<'a> WindowContext<'a> { .dispatch_path(node_id); if let Some(key_down_event) = event.downcast_ref::() { - let KeymatchResult { bindings, pending } = self + let KeymatchResult { + bindings, + mut 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 diff --git a/crates/gpui/src/window/element_cx.rs b/crates/gpui/src/window/element_cx.rs index 132cc72a5c..14ed818d25 100644 --- a/crates/gpui/src/window/element_cx.rs +++ b/crates/gpui/src/window/element_cx.rs @@ -17,11 +17,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, 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, 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, }; type AnyMouseListener = Box; @@ -1090,6 +1090,15 @@ 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 c67fbfc4d0..fc9e4bb21e 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -762,6 +762,7 @@ 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();