Detailed changes
@@ -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<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
keymap: Arc<Mutex<Keymap>>,
action_registry: Rc<ActionRegistry>,
+ 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(
@@ -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::<KeyDownEvent>() {
- 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
@@ -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<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
@@ -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.
@@ -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();