Detailed changes
@@ -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",
@@ -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::<Vec<_>>().len() == 3);
+ assert!(workspace.items(cx).collect::<Vec<_>>().len() == 2);
});
}
@@ -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<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
keymap: Rc<RefCell<Keymap>>,
action_registry: Rc<ActionRegistry>,
- 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
@@ -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::<KeyDownEvent>() {
- 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);
@@ -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<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;
@@ -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.
@@ -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();