Detailed changes
@@ -1130,14 +1130,7 @@ impl AppContext {
for window in self.windows() {
window
.update(self, |_, cx| {
- cx.window
- .rendered_frame
- .dispatch_tree
- .clear_pending_keystrokes();
- cx.window
- .next_frame
- .dispatch_tree
- .clear_pending_keystrokes();
+ cx.clear_pending_keystrokes();
})
.ok();
}
@@ -51,7 +51,7 @@
///
use crate::{
Action, ActionRegistry, DispatchPhase, EntityId, FocusId, KeyBinding, KeyContext, Keymap,
- KeymatchResult, Keystroke, KeystrokeMatcher, ModifiersChangedEvent, WindowContext,
+ Keystroke, ModifiersChangedEvent, WindowContext,
};
use collections::FxHashMap;
use smallvec::SmallVec;
@@ -73,7 +73,6 @@ pub(crate) struct DispatchTree {
nodes: Vec<DispatchNode>,
focusable_node_ids: FxHashMap<FocusId, DispatchNodeId>,
view_node_ids: FxHashMap<EntityId, DispatchNodeId>,
- keystroke_matchers: FxHashMap<SmallVec<[KeyContext; 4]>, KeystrokeMatcher>,
keymap: Rc<RefCell<Keymap>>,
action_registry: Rc<ActionRegistry>,
}
@@ -111,6 +110,19 @@ impl ReusedSubtree {
}
}
+#[derive(Default, Debug)]
+pub(crate) struct Replay {
+ pub(crate) keystroke: Keystroke,
+ pub(crate) bindings: SmallVec<[KeyBinding; 1]>,
+}
+
+#[derive(Default, Debug)]
+pub(crate) struct DispatchResult {
+ pub(crate) pending: SmallVec<[Keystroke; 1]>,
+ pub(crate) bindings: SmallVec<[KeyBinding; 1]>,
+ pub(crate) to_replay: SmallVec<[Replay; 1]>,
+}
+
type KeyListener = Rc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext)>;
type ModifiersChangedListener = Rc<dyn Fn(&ModifiersChangedEvent, &mut WindowContext)>;
@@ -129,7 +141,6 @@ impl DispatchTree {
nodes: Vec::new(),
focusable_node_ids: FxHashMap::default(),
view_node_ids: FxHashMap::default(),
- keystroke_matchers: FxHashMap::default(),
keymap,
action_registry,
}
@@ -142,7 +153,6 @@ impl DispatchTree {
self.nodes.clear();
self.focusable_node_ids.clear();
self.view_node_ids.clear();
- self.keystroke_matchers.clear();
}
pub fn len(&self) -> usize {
@@ -310,33 +320,6 @@ impl DispatchTree {
self.nodes.truncate(index);
}
- pub fn clear_pending_keystrokes(&mut self) {
- self.keystroke_matchers.clear();
- }
-
- /// Preserve keystroke matchers from previous frames to support multi-stroke
- /// bindings across multiple frames.
- pub fn preserve_pending_keystrokes(&mut self, old_tree: &mut Self, focus_id: Option<FocusId>) {
- if let Some(node_id) = focus_id.and_then(|focus_id| self.focusable_node_id(focus_id)) {
- let dispatch_path = self.dispatch_path(node_id);
-
- self.context_stack.clear();
- for node_id in dispatch_path {
- let node = self.node(node_id);
- if let Some(context) = node.context.clone() {
- self.context_stack.push(context);
- }
-
- if let Some((context_stack, matcher)) = old_tree
- .keystroke_matchers
- .remove_entry(self.context_stack.as_slice())
- {
- self.keystroke_matchers.insert(context_stack, matcher);
- }
- }
- }
- }
-
pub fn on_key_event(&mut self, listener: KeyListener) {
self.active_node().key_listeners.push(listener);
}
@@ -419,74 +402,110 @@ impl DispatchTree {
keymap
.bindings_for_action(action)
.filter(|binding| {
- for i in 0..context_stack.len() {
- let context = &context_stack[0..=i];
- if keymap.binding_enabled(binding, context) {
- return true;
- }
- }
- false
+ let (bindings, _) = keymap.bindings_for_input(&binding.keystrokes, &context_stack);
+ bindings
+ .iter()
+ .next()
+ .is_some_and(|b| b.action.partial_eq(action))
})
.cloned()
.collect()
}
- // 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
- // binding for "b", the Editor action fires first).
- pub fn dispatch_key(
- &mut self,
- keystroke: &Keystroke,
+ fn bindings_for_input(
+ &self,
+ input: &[Keystroke],
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
- ) -> KeymatchResult {
- let mut bindings = SmallVec::<[KeyBinding; 1]>::new();
- let mut pending = false;
+ ) -> (SmallVec<[KeyBinding; 1]>, bool) {
+ let context_stack: SmallVec<[KeyContext; 4]> = dispatch_path
+ .iter()
+ .filter_map(|node_id| self.node(*node_id).context.clone())
+ .collect();
- let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
- for node_id in dispatch_path {
- let node = self.node(*node_id);
+ self.keymap
+ .borrow()
+ .bindings_for_input(&input, &context_stack)
+ }
- if let Some(context) = node.context.clone() {
- context_stack.push(context);
- }
+ /// dispatch_key processes the keystroke
+ /// input should be set to the value of `pending` from the previous call to dispatch_key.
+ /// This returns three instructions to the input handler:
+ /// - bindings: any bindings to execute before processing this keystroke
+ /// - pending: the new set of pending keystrokes to store
+ /// - to_replay: any keystroke that had been pushed to pending, but are no-longer matched,
+ /// these should be replayed first.
+ pub fn dispatch_key(
+ &mut self,
+ mut input: SmallVec<[Keystroke; 1]>,
+ keystroke: Keystroke,
+ dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
+ ) -> DispatchResult {
+ input.push(keystroke.clone());
+ let (bindings, pending) = self.bindings_for_input(&input, dispatch_path);
+
+ if pending {
+ return DispatchResult {
+ pending: input,
+ ..Default::default()
+ };
+ } else if !bindings.is_empty() {
+ return DispatchResult {
+ bindings,
+ ..Default::default()
+ };
+ } else if input.len() == 1 {
+ return DispatchResult::default();
}
+ input.pop();
- while !context_stack.is_empty() {
- let keystroke_matcher = self
- .keystroke_matchers
- .entry(context_stack.clone())
- .or_insert_with(|| KeystrokeMatcher::new(self.keymap.clone()));
+ let (suffix, mut to_replay) = self.replay_prefix(input, dispatch_path);
- let result = keystroke_matcher.match_keystroke(keystroke, &context_stack);
- if result.pending && !pending && !bindings.is_empty() {
- context_stack.pop();
- continue;
- }
+ let mut result = self.dispatch_key(suffix, keystroke, dispatch_path);
+ to_replay.extend(result.to_replay);
+ result.to_replay = to_replay;
+ return result;
+ }
- pending = result.pending || pending;
- 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();
+ /// If the user types a matching prefix of a binding and then waits for a timeout
+ /// flush_dispatch() converts any previously pending input to replay events.
+ pub fn flush_dispatch(
+ &mut self,
+ input: SmallVec<[Keystroke; 1]>,
+ dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
+ ) -> SmallVec<[Replay; 1]> {
+ let (suffix, mut to_replay) = self.replay_prefix(input, dispatch_path);
+
+ if suffix.len() > 0 {
+ to_replay.extend(self.flush_dispatch(suffix, dispatch_path))
}
- KeymatchResult { bindings, pending }
+ to_replay
}
- pub fn has_pending_keystrokes(&self) -> bool {
- self.keystroke_matchers
- .iter()
- .any(|(_, matcher)| matcher.has_pending_keystrokes())
+ /// Converts the longest prefix of input to a replay event and returns the rest.
+ fn replay_prefix(
+ &mut self,
+ mut input: SmallVec<[Keystroke; 1]>,
+ dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
+ ) -> (SmallVec<[Keystroke; 1]>, SmallVec<[Replay; 1]>) {
+ let mut to_replay: SmallVec<[Replay; 1]> = Default::default();
+ for last in (0..input.len()).rev() {
+ let (bindings, _) = self.bindings_for_input(&input[0..=last], dispatch_path);
+ if !bindings.is_empty() {
+ to_replay.push(Replay {
+ keystroke: input.drain(0..=last).last().unwrap(),
+ bindings,
+ });
+ break;
+ }
+ }
+ if to_replay.is_empty() {
+ to_replay.push(Replay {
+ keystroke: input.remove(0),
+ ..Default::default()
+ });
+ }
+ (input, to_replay)
}
pub fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
@@ -1,13 +1,11 @@
mod binding;
mod context;
-mod matcher;
pub use binding::*;
pub use context::*;
-pub(crate) use matcher::*;
use crate::{Action, Keystroke, NoAction};
-use collections::{HashMap, HashSet};
+use collections::HashMap;
use smallvec::SmallVec;
use std::any::{Any, TypeId};
@@ -21,8 +19,6 @@ pub struct KeymapVersion(usize);
pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
- disabled_keystrokes:
- HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
version: KeymapVersion,
}
@@ -41,22 +37,13 @@ impl Keymap {
/// Add more bindings to the keymap.
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
- let no_action_id = (NoAction {}).type_id();
-
for binding in bindings {
let action_id = binding.action().as_any().type_id();
- if action_id == no_action_id {
- self.disabled_keystrokes
- .entry(binding.keystrokes)
- .or_default()
- .insert(binding.context_predicate);
- } else {
- self.binding_indices_by_action_id
- .entry(action_id)
- .or_default()
- .push(self.bindings.len());
- self.bindings.push(binding);
- }
+ self.binding_indices_by_action_id
+ .entry(action_id)
+ .or_default()
+ .push(self.bindings.len());
+ self.bindings.push(binding);
}
self.version.0 += 1;
@@ -66,7 +53,6 @@ impl Keymap {
pub fn clear(&mut self) {
self.bindings.clear();
self.binding_indices_by_action_id.clear();
- self.disabled_keystrokes.clear();
self.version.0 += 1;
}
@@ -89,8 +75,66 @@ impl Keymap {
.filter(move |binding| binding.action().partial_eq(action))
}
+ /// bindings_for_input returns a list of bindings that match the given input,
+ /// and a boolean indicating whether or not more bindings might match if
+ /// the input was longer.
+ ///
+ /// Precedence is defined by the depth in the tree (matches on the Editor take
+ /// precedence over matches on the Pane, then the Workspace, etc.). Bindings with
+ /// no context are treated as the same as the deepest context.
+ ///
+ /// In the case of multiple bindings at the same depth, the ones defined later in the
+ /// keymap take precedence (so user bindings take precedence over built-in bindings).
+ ///
+ /// If a user has disabled a binding with `"x": null` it will not be returned. Disabled
+ /// bindings are evaluated with the same precedence rules so you can disable a rule in
+ /// a given context only.
+ ///
+ /// In the case of multi-key bindings, the
+ pub fn bindings_for_input(
+ &self,
+ input: &[Keystroke],
+ context_stack: &[KeyContext],
+ ) -> (SmallVec<[KeyBinding; 1]>, bool) {
+ let possibilities = self.bindings().rev().filter_map(|binding| {
+ binding
+ .match_keystrokes(input)
+ .map(|pending| (binding, pending))
+ });
+
+ let mut bindings: SmallVec<[(KeyBinding, usize); 1]> = SmallVec::new();
+ let mut is_pending = None;
+
+ 'outer: for (binding, pending) in possibilities {
+ for depth in (0..=context_stack.len()).rev() {
+ if self.binding_enabled(binding, &context_stack[0..depth]) {
+ if is_pending.is_none() {
+ is_pending = Some(pending);
+ }
+ if !pending {
+ bindings.push((binding.clone(), depth));
+ continue 'outer;
+ }
+ }
+ }
+ }
+ bindings.sort_by(|a, b| a.1.cmp(&b.1).reverse());
+ let bindings = bindings
+ .into_iter()
+ .map_while(|(binding, _)| {
+ if binding.action.as_any().type_id() == (NoAction {}).type_id() {
+ None
+ } else {
+ Some(binding)
+ }
+ })
+ .collect();
+
+ return (bindings, is_pending.unwrap_or_default());
+ }
+
/// Check if the given binding is enabled, given a certain key context.
- pub fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
+ fn binding_enabled(&self, binding: &KeyBinding, context: &[KeyContext]) -> bool {
// If binding has a context predicate, it must match the current context,
if let Some(predicate) = &binding.context_predicate {
if !predicate.eval(context) {
@@ -98,22 +142,6 @@ impl Keymap {
}
}
- if let Some(disabled_predicates) = self.disabled_keystrokes.get(&binding.keystrokes) {
- for disabled_predicate in disabled_predicates {
- match disabled_predicate {
- // The binding must not be globally disabled.
- None => return false,
-
- // The binding must not be disabled in the current context.
- Some(predicate) => {
- if predicate.eval(context) {
- return false;
- }
- }
- }
- }
- }
-
true
}
}
@@ -168,16 +196,37 @@ mod tests {
keymap.add_bindings(bindings.clone());
// binding is only enabled in a specific context
- assert!(!keymap.binding_enabled(&bindings[0], &[KeyContext::parse("barf").unwrap()]));
- assert!(keymap.binding_enabled(&bindings[0], &[KeyContext::parse("editor").unwrap()]));
+ assert!(keymap
+ .bindings_for_input(
+ &[Keystroke::parse("ctrl-a").unwrap()],
+ &[KeyContext::parse("barf").unwrap()],
+ )
+ .0
+ .is_empty());
+ assert!(!keymap
+ .bindings_for_input(
+ &[Keystroke::parse("ctrl-a").unwrap()],
+ &[KeyContext::parse("editor").unwrap()],
+ )
+ .0
+ .is_empty());
// binding is disabled in a more specific context
- assert!(!keymap.binding_enabled(
- &bindings[0],
- &[KeyContext::parse("editor mode=full").unwrap()]
- ));
+ assert!(keymap
+ .bindings_for_input(
+ &[Keystroke::parse("ctrl-a").unwrap()],
+ &[KeyContext::parse("editor mode=full").unwrap()],
+ )
+ .0
+ .is_empty());
// binding is globally disabled
- assert!(!keymap.binding_enabled(&bindings[1], &[KeyContext::parse("barf").unwrap()]));
+ assert!(keymap
+ .bindings_for_input(
+ &[Keystroke::parse("ctrl-b").unwrap()],
+ &[KeyContext::parse("barf").unwrap()],
+ )
+ .0
+ .is_empty());
}
}
@@ -1,4 +1,4 @@
-use crate::{Action, KeyBindingContextPredicate, KeyMatch, Keystroke};
+use crate::{Action, KeyBindingContextPredicate, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
@@ -46,17 +46,18 @@ impl KeyBinding {
}
/// Check if the given keystrokes match this binding.
- pub fn match_keystrokes(&self, pending_keystrokes: &[Keystroke]) -> KeyMatch {
- if self.keystrokes.as_ref().starts_with(pending_keystrokes) {
- // If the binding is completed, push it onto the matches list
- if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
- KeyMatch::Matched
- } else {
- KeyMatch::Pending
+ pub fn match_keystrokes(&self, typed: &[Keystroke]) -> Option<bool> {
+ if self.keystrokes.len() < typed.len() {
+ return None;
+ }
+
+ for (target, typed) in self.keystrokes.iter().zip(typed.iter()) {
+ if !typed.should_match(target) {
+ return None;
}
- } else {
- KeyMatch::None
}
+
+ return Some(self.keystrokes.len() > typed.len());
}
/// Get the keystrokes associated with this binding
@@ -1,102 +0,0 @@
-use crate::{KeyBinding, KeyContext, Keymap, KeymapVersion, Keystroke};
-use smallvec::SmallVec;
-use std::{cell::RefCell, rc::Rc};
-
-pub(crate) struct KeystrokeMatcher {
- pending_keystrokes: Vec<Keystroke>,
- keymap: Rc<RefCell<Keymap>>,
- keymap_version: KeymapVersion,
-}
-
-pub struct KeymatchResult {
- pub bindings: SmallVec<[KeyBinding; 1]>,
- pub pending: bool,
-}
-
-impl KeystrokeMatcher {
- pub fn new(keymap: Rc<RefCell<Keymap>>) -> Self {
- let keymap_version = keymap.borrow().version();
- Self {
- pending_keystrokes: Vec::new(),
- keymap_version,
- keymap,
- }
- }
-
- pub fn has_pending_keystrokes(&self) -> bool {
- !self.pending_keystrokes.is_empty()
- }
-
- /// Pushes a keystroke onto the matcher.
- /// The result of the new keystroke is returned:
- /// - KeyMatch::None =>
- /// No match is valid for this key given any pending keystrokes.
- /// - KeyMatch::Pending =>
- /// There exist bindings which are still waiting for more keys.
- /// - KeyMatch::Complete(matches) =>
- /// One or more bindings have received the necessary key presses.
- /// Bindings added later will take precedence over earlier bindings.
- pub(crate) fn match_keystroke(
- &mut self,
- keystroke: &Keystroke,
- context_stack: &[KeyContext],
- ) -> KeymatchResult {
- let keymap = self.keymap.borrow();
-
- // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
- if keymap.version() != self.keymap_version {
- self.keymap_version = keymap.version();
- self.pending_keystrokes.clear();
- }
-
- let mut pending_key = None;
- let mut bindings = SmallVec::new();
-
- for binding in keymap.bindings().rev() {
- if !keymap.binding_enabled(binding, context_stack) {
- continue;
- }
-
- for candidate in keystroke.match_candidates() {
- self.pending_keystrokes.push(candidate.clone());
- match binding.match_keystrokes(&self.pending_keystrokes) {
- KeyMatch::Matched => {
- bindings.push(binding.clone());
- }
- KeyMatch::Pending => {
- pending_key.get_or_insert(candidate);
- }
- KeyMatch::None => {}
- }
- self.pending_keystrokes.pop();
- }
- }
-
- if bindings.is_empty() && pending_key.is_none() && !self.pending_keystrokes.is_empty() {
- drop(keymap);
- self.pending_keystrokes.remove(0);
- return self.match_keystroke(keystroke, context_stack);
- }
-
- let pending = if let Some(pending_key) = pending_key {
- self.pending_keystrokes.push(pending_key);
- true
- } else {
- self.pending_keystrokes.clear();
- false
- };
-
- KeymatchResult { bindings, pending }
- }
-}
-
-/// The result of matching a keystroke against a given keybinding.
-/// - KeyMatch::None => No match is valid for this key given any pending keystrokes.
-/// - KeyMatch::Pending => There exist bindings that is still waiting for more keys.
-/// - KeyMatch::Some(matches) => One or more bindings have received the necessary key presses.
-#[derive(Debug, PartialEq)]
-pub enum KeyMatch {
- None,
- Pending,
- Matched,
-}
@@ -1,6 +1,5 @@
use anyhow::anyhow;
use serde::Deserialize;
-use smallvec::SmallVec;
use std::fmt::Write;
/// A keystroke and associated metadata generated by the platform
@@ -25,33 +24,25 @@ impl Keystroke {
/// and on some keyboards the IME handler converts a sequence of keys into a
/// specific character (for example `"` is typed as `" space` on a brazilian keyboard).
///
- /// This method generates a list of potential keystroke candidates that could be matched
- /// against when resolving a keybinding.
- pub(crate) fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
- let mut possibilities = SmallVec::new();
- match self.ime_key.as_ref() {
- Some(ime_key) => {
- if ime_key != &self.key {
- possibilities.push(Keystroke {
- modifiers: Modifiers {
- control: self.modifiers.control,
- alt: false,
- shift: false,
- platform: false,
- function: false,
- },
- key: ime_key.to_string(),
- ime_key: None,
- });
- }
- possibilities.push(Keystroke {
- ime_key: None,
- ..self.clone()
- });
+ /// This method assumes that `self` was typed and `target' is in the keymap, and checks
+ /// both possibilities for self against the target.
+ pub(crate) fn should_match(&self, target: &Keystroke) -> bool {
+ if let Some(ime_key) = self
+ .ime_key
+ .as_ref()
+ .filter(|ime_key| ime_key != &&self.key)
+ {
+ let ime_modifiers = Modifiers {
+ control: self.modifiers.control,
+ ..Default::default()
+ };
+
+ if &target.key == ime_key && target.modifiers == ime_modifiers {
+ return true;
}
- None => possibilities.push(self.clone()),
}
- possibilities
+
+ target.modifiers == self.modifiers && target.key == self.key
}
/// key syntax is:
@@ -4,14 +4,14 @@ use crate::{
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, Flatten, FontId, Global, GlobalElementId, GlyphId, Hsla, ImageData,
- InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, KeyMatch, KeymatchResult,
- Keystroke, KeystrokeEvent, LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers,
+ InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, Keystroke,
+ KeystrokeEvent, LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers,
ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent,
Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
- RenderImageParams, RenderSvgParams, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString,
- Size, StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task,
- TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, View,
+ RenderImageParams, RenderSvgParams, Replay, ResizeEdge, ScaledPixels, Scene, Shadow,
+ SharedString, Size, StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine,
+ Task, TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, View,
VisualContext, WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
SUBPIXEL_VARIANTS,
@@ -574,34 +574,10 @@ pub(crate) enum DrawPhase {
#[derive(Default, Debug)]
struct PendingInput {
keystrokes: SmallVec<[Keystroke; 1]>,
- bindings: SmallVec<[KeyBinding; 1]>,
focus: Option<FocusId>,
timer: Option<Task<()>>,
}
-impl PendingInput {
- fn input(&self) -> String {
- self.keystrokes
- .iter()
- .flat_map(|k| k.ime_key.clone())
- .collect::<Vec<String>>()
- .join("")
- }
-
- fn used_by_binding(&self, binding: &KeyBinding) -> bool {
- if self.keystrokes.is_empty() {
- return true;
- }
- let keystroke = &self.keystrokes[0];
- for candidate in keystroke.match_candidates() {
- if binding.match_keystrokes(&[candidate]) == KeyMatch::Pending {
- return true;
- }
- }
- false
- }
-}
-
pub(crate) struct ElementStateBox {
pub(crate) inner: Box<dyn Any>,
#[cfg(debug_assertions)]
@@ -969,10 +945,7 @@ impl<'a> WindowContext<'a> {
}
self.window.focus = Some(handle.id);
- self.window
- .rendered_frame
- .dispatch_tree
- .clear_pending_keystrokes();
+ self.clear_pending_keystrokes();
self.refresh();
}
@@ -1074,17 +1047,6 @@ impl<'a> WindowContext<'a> {
});
}
- pub(crate) fn clear_pending_keystrokes(&mut self) {
- self.window
- .rendered_frame
- .dispatch_tree
- .clear_pending_keystrokes();
- self.window
- .next_frame
- .dispatch_tree
- .clear_pending_keystrokes();
- }
-
/// Schedules the given function to be run at the end of the current effect cycle, allowing entities
/// that are currently on the stack to be returned to the app.
pub fn defer(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) {
@@ -1453,14 +1415,6 @@ impl<'a> WindowContext<'a> {
self.draw_roots();
self.window.dirty_views.clear();
-
- self.window
- .next_frame
- .dispatch_tree
- .preserve_pending_keystrokes(
- &mut self.window.rendered_frame.dispatch_tree,
- self.window.focus,
- );
self.window.next_frame.window_active = self.window.active.get();
// Register requested input handler with the platform window.
@@ -3253,8 +3207,6 @@ impl<'a> WindowContext<'a> {
.dispatch_tree
.dispatch_path(node_id);
- let mut bindings: SmallVec<[KeyBinding; 1]> = SmallVec::new();
- let mut pending = false;
let mut keystroke: Option<Keystroke> = None;
if let Some(event) = event.downcast_ref::<ModifiersChangedEvent>() {
@@ -3272,23 +3224,11 @@ impl<'a> WindowContext<'a> {
_ => None,
};
if let Some(key) = key {
- let key = Keystroke {
+ keystroke = Some(Keystroke {
key: key.to_string(),
ime_key: None,
modifiers: Modifiers::default(),
- };
- let KeymatchResult {
- bindings: modifier_bindings,
- pending: pending_bindings,
- } = self
- .window
- .rendered_frame
- .dispatch_tree
- .dispatch_key(&key, &dispatch_path);
-
- keystroke = Some(key);
- bindings = modifier_bindings;
- pending = pending_bindings;
+ });
}
}
}
@@ -3300,73 +3240,68 @@ impl<'a> WindowContext<'a> {
self.window.pending_modifier.modifiers = event.modifiers
} else if let Some(key_down_event) = event.downcast_ref::<KeyDownEvent>() {
self.window.pending_modifier.saw_keystroke = true;
- let KeymatchResult {
- bindings: key_down_bindings,
- pending: key_down_pending,
- } = self
- .window
- .rendered_frame
- .dispatch_tree
- .dispatch_key(&key_down_event.keystroke, &dispatch_path);
-
keystroke = Some(key_down_event.keystroke.clone());
-
- bindings = key_down_bindings;
- pending = key_down_pending;
}
- if keystroke.is_none() {
+ let Some(keystroke) = keystroke else {
self.finish_dispatch_key_event(event, dispatch_path);
return;
+ };
+
+ let mut currently_pending = self.window.pending_input.take().unwrap_or_default();
+ if currently_pending.focus.is_some() && currently_pending.focus != self.window.focus {
+ currently_pending = PendingInput::default();
}
- 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 {
- currently_pending = PendingInput::default();
- }
- currently_pending.focus = self.window.focus;
- if let Some(keystroke) = keystroke {
- currently_pending.keystrokes.push(keystroke.clone());
- }
- for binding in bindings {
- currently_pending.bindings.push(binding);
- }
+ let match_result = self.window.rendered_frame.dispatch_tree.dispatch_key(
+ currently_pending.keystrokes,
+ keystroke,
+ &dispatch_path,
+ );
+ if !match_result.to_replay.is_empty() {
+ self.replay_pending_input(match_result.to_replay)
+ }
+ if !match_result.pending.is_empty() {
+ currently_pending.keystrokes = match_result.pending;
+ currently_pending.focus = self.window.focus;
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 {
+ let Some(currently_pending) = cx
+ .window
+ .pending_input
+ .take()
+ .filter(|pending| pending.focus == cx.window.focus)
+ else {
return;
};
- cx.replay_pending_input(currently_pending);
- cx.pending_input_changed();
+
+ let dispatch_path = cx
+ .window
+ .rendered_frame
+ .dispatch_tree
+ .dispatch_path(node_id);
+
+ let to_replay = cx
+ .window
+ .rendered_frame
+ .dispatch_tree
+ .flush_dispatch(currently_pending.keystrokes, &dispatch_path);
+
+ cx.replay_pending_input(to_replay)
})
.log_err();
}));
-
self.window.pending_input = Some(currently_pending);
self.pending_input_changed();
-
self.propagate_event = false;
return;
- } else if let Some(currently_pending) = self.window.pending_input.take() {
- self.pending_input_changed();
- if bindings
- .iter()
- .all(|binding| !currently_pending.used_by_binding(binding))
- {
- self.replay_pending_input(currently_pending)
- }
- }
-
- if !bindings.is_empty() {
- self.clear_pending_keystrokes();
}
+ self.pending_input_changed();
self.propagate_event = true;
- for binding in bindings {
+ for binding in match_result.bindings {
self.dispatch_action_on_node(node_id, binding.action.as_ref());
if !self.propagate_event {
self.dispatch_keystroke_observers(event, Some(binding.action));
@@ -3453,10 +3388,11 @@ impl<'a> WindowContext<'a> {
/// Determine whether a potential multi-stroke key binding is in progress on this window.
pub fn has_pending_keystrokes(&self) -> bool {
- self.window
- .rendered_frame
- .dispatch_tree
- .has_pending_keystrokes()
+ self.window.pending_input.is_some()
+ }
+
+ fn clear_pending_keystrokes(&mut self) {
+ self.window.pending_input.take();
}
/// Returns the currently pending input keystrokes that might result in a multi-stroke key binding.
@@ -3467,7 +3403,7 @@ impl<'a> WindowContext<'a> {
.map(|pending_input| pending_input.keystrokes.as_slice())
}
- fn replay_pending_input(&mut self, currently_pending: PendingInput) {
+ fn replay_pending_input(&mut self, replays: SmallVec<[Replay; 1]>) {
let node_id = self
.window
.focus
@@ -3479,42 +3415,36 @@ impl<'a> WindowContext<'a> {
})
.unwrap_or_else(|| self.window.rendered_frame.dispatch_tree.root_node_id());
- if self.window.focus != currently_pending.focus {
- return;
- }
-
- let input = currently_pending.input();
-
- self.propagate_event = true;
- for binding in currently_pending.bindings {
- self.dispatch_action_on_node(node_id, binding.action.as_ref());
- if !self.propagate_event {
- return;
- }
- }
-
let dispatch_path = self
.window
.rendered_frame
.dispatch_tree
.dispatch_path(node_id);
- for keystroke in currently_pending.keystrokes {
+ 'replay: for replay in replays {
let event = KeyDownEvent {
- keystroke,
+ keystroke: replay.keystroke.clone(),
is_held: false,
};
+ self.propagate_event = true;
+ for binding in replay.bindings {
+ self.dispatch_action_on_node(node_id, binding.action.as_ref());
+ if !self.propagate_event {
+ self.dispatch_keystroke_observers(&event, Some(binding.action));
+ continue 'replay;
+ }
+ }
+
self.dispatch_key_down_up_event(&event, &dispatch_path);
if !self.propagate_event {
- return;
+ continue 'replay;
}
- }
-
- if !input.is_empty() {
- if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
- input_handler.dispatch_input(&input, self);
- self.window.platform_window.set_input_handler(input_handler)
+ if let Some(input) = replay.keystroke.ime_key.as_ref().cloned() {
+ if let Some(mut input_handler) = self.window.platform_window.take_input_handler() {
+ input_handler.dispatch_input(&input, self);
+ self.window.platform_window.set_input_handler(input_handler)
+ }
}
}
}
@@ -6,7 +6,7 @@ use std::time::Duration;
use collections::HashMap;
use command_palette::CommandPalette;
-use editor::{display_map::DisplayRow, DisplayPoint};
+use editor::{actions::DeleteLine, display_map::DisplayRow, DisplayPoint};
use futures::StreamExt;
use gpui::{KeyBinding, Modifiers, MouseButton, TestAppContext};
pub use neovim_backed_test_context::*;
@@ -1317,3 +1317,99 @@ async fn test_command_alias(cx: &mut gpui::TestAppContext) {
cx.simulate_keystrokes(": Q");
cx.set_state("ΛHello world", Mode::Normal);
}
+
+#[gpui::test]
+async fn test_remap_adjacent_dog_cat(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.update(|cx| {
+ cx.bind_keys([
+ KeyBinding::new(
+ "d o g",
+ workspace::SendKeystrokes("πΆ".to_string()),
+ Some("vim_mode == insert"),
+ ),
+ KeyBinding::new(
+ "c a t",
+ workspace::SendKeystrokes("π±".to_string()),
+ Some("vim_mode == insert"),
+ ),
+ ])
+ });
+ cx.neovim.exec("imap dog πΆ").await;
+ cx.neovim.exec("imap cat π±").await;
+
+ cx.set_shared_state("Λ").await;
+ cx.simulate_shared_keystrokes("i d o g").await;
+ cx.shared_state().await.assert_eq("πΆΛ");
+
+ cx.set_shared_state("Λ").await;
+ cx.simulate_shared_keystrokes("i d o d o g").await;
+ cx.shared_state().await.assert_eq("doπΆΛ");
+
+ cx.set_shared_state("Λ").await;
+ cx.simulate_shared_keystrokes("i d o c a t").await;
+ cx.shared_state().await.assert_eq("doπ±Λ");
+}
+
+#[gpui::test]
+async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.update(|cx| {
+ cx.bind_keys([
+ KeyBinding::new(
+ "p i n",
+ workspace::SendKeystrokes("π".to_string()),
+ Some("vim_mode == insert"),
+ ),
+ KeyBinding::new(
+ "p i n e",
+ workspace::SendKeystrokes("π²".to_string()),
+ Some("vim_mode == insert"),
+ ),
+ KeyBinding::new(
+ "p i n e a p p l e",
+ workspace::SendKeystrokes("π".to_string()),
+ Some("vim_mode == insert"),
+ ),
+ ])
+ });
+ cx.neovim.exec("imap pin π").await;
+ cx.neovim.exec("imap pine π²").await;
+ cx.neovim.exec("imap pineapple π").await;
+
+ cx.set_shared_state("Λ").await;
+ cx.simulate_shared_keystrokes("i p i n").await;
+ cx.executor().advance_clock(Duration::from_millis(1000));
+ cx.run_until_parked();
+ cx.shared_state().await.assert_eq("πΛ");
+
+ cx.set_shared_state("Λ").await;
+ cx.simulate_shared_keystrokes("i p i n e").await;
+ cx.executor().advance_clock(Duration::from_millis(1000));
+ cx.run_until_parked();
+ cx.shared_state().await.assert_eq("π²Λ");
+
+ cx.set_shared_state("Λ").await;
+ cx.simulate_shared_keystrokes("i p i n e a p p l e").await;
+ cx.shared_state().await.assert_eq("πΛ");
+}
+
+#[gpui::test]
+async fn test_escape_while_waiting(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.set_shared_state("Λhi").await;
+ cx.simulate_shared_keystrokes("\" + escape x").await;
+ cx.shared_state().await.assert_eq("Λi");
+}
+
+#[gpui::test]
+async fn test_ctrl_w_override(cx: &mut gpui::TestAppContext) {
+ let mut cx = NeovimBackedTestContext::new(cx).await;
+ cx.update(|cx| {
+ cx.bind_keys([KeyBinding::new("ctrl-w", DeleteLine, None)]);
+ });
+ cx.neovim.exec("map <c-w> D").await;
+ cx.set_shared_state("Λhi").await;
+ cx.simulate_shared_keystrokes("ctrl-w").await;
+ cx.shared_state().await.assert_eq("Λ");
+}
@@ -409,6 +409,7 @@ impl Vim {
state.last_mode = last_mode;
state.mode = mode;
state.operator_stack.clear();
+ state.selected_register.take();
if mode == Mode::Normal || mode != last_mode {
state.current_tx.take();
state.current_anchor.take();
@@ -0,0 +1,4 @@
+{"Exec":{"command":"map <c-w> D"}}
+{"Put":{"state":"Λhi"}}
+{"Key":"ctrl-w"}
+{"Get":{"state":"Λ","mode":"Normal"}}
@@ -0,0 +1,6 @@
+{"Put":{"state":"Λhi"}}
+{"Key":"\""}
+{"Key":"+"}
+{"Key":"escape"}
+{"Key":"x"}
+{"Get":{"state":"Λi","mode":"Normal"}}
@@ -0,0 +1,24 @@
+{"Exec":{"command":"imap dog πΆ"}}
+{"Exec":{"command":"imap cat π±"}}
+{"Put":{"state":"Λ"}}
+{"Key":"i"}
+{"Key":"d"}
+{"Key":"o"}
+{"Key":"g"}
+{"Get":{"state":"πΆΛ","mode":"Insert"}}
+{"Put":{"state":"Λ"}}
+{"Key":"i"}
+{"Key":"d"}
+{"Key":"o"}
+{"Key":"d"}
+{"Key":"o"}
+{"Key":"g"}
+{"Get":{"state":"doπΆΛ","mode":"Insert"}}
+{"Put":{"state":"Λ"}}
+{"Key":"i"}
+{"Key":"d"}
+{"Key":"o"}
+{"Key":"c"}
+{"Key":"a"}
+{"Key":"t"}
+{"Get":{"state":"doπ±Λ","mode":"Insert"}}
@@ -0,0 +1,28 @@
+{"Exec":{"command":"imap pin π"}}
+{"Exec":{"command":"imap pine π²"}}
+{"Exec":{"command":"imap pineapple π"}}
+{"Put":{"state":"Λ"}}
+{"Key":"i"}
+{"Key":"p"}
+{"Key":"i"}
+{"Key":"n"}
+{"Get":{"state":"πΛ","mode":"Insert"}}
+{"Put":{"state":"Λ"}}
+{"Key":"i"}
+{"Key":"p"}
+{"Key":"i"}
+{"Key":"n"}
+{"Key":"e"}
+{"Get":{"state":"π²Λ","mode":"Insert"}}
+{"Put":{"state":"Λ"}}
+{"Key":"i"}
+{"Key":"p"}
+{"Key":"i"}
+{"Key":"n"}
+{"Key":"e"}
+{"Key":"a"}
+{"Key":"p"}
+{"Key":"p"}
+{"Key":"l"}
+{"Key":"e"}
+{"Get":{"state":"πΛ","mode":"Insert"}}