Tidy
This commit is contained in:
@@ -117,7 +117,7 @@ pub(crate) struct Replay {
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct MatchResult {
|
||||
pub(crate) struct DispatchResult {
|
||||
pub(crate) pending: SmallVec<[Keystroke; 1]>,
|
||||
pub(crate) bindings: SmallVec<[KeyBinding; 1]>,
|
||||
pub(crate) to_replay: SmallVec<[Replay; 1]>,
|
||||
@@ -402,164 +402,95 @@ 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()
|
||||
}
|
||||
|
||||
fn match_input(
|
||||
fn bindings_for_input(
|
||||
&self,
|
||||
input: &[Keystroke],
|
||||
dispatch_path: &SmallVec<[DispatchNodeId; 32]>,
|
||||
) -> (SmallVec<[KeyBinding; 1]>, bool) {
|
||||
let keymap = self.keymap.borrow();
|
||||
let context_stack: SmallVec<[KeyContext; 4]> = dispatch_path
|
||||
.iter()
|
||||
.filter_map(|node_id| self.node(*node_id).context.clone())
|
||||
.collect();
|
||||
|
||||
// We want the following behaviour:
|
||||
// - If there are multiple bindings matching completely,
|
||||
// return them in order of depth in the context.
|
||||
// - If there are some bindings that are pending,
|
||||
// they take priority over bindings defined earlier in the keymap.
|
||||
|
||||
let mut context_stack: SmallVec<[KeyContext; 4]> = SmallVec::new();
|
||||
for node_id in dispatch_path {
|
||||
let node = self.node(*node_id);
|
||||
|
||||
if let Some(context) = node.context.clone() {
|
||||
context_stack.push(context);
|
||||
}
|
||||
}
|
||||
|
||||
let possibilities = keymap.bindings().rev().filter_map(|binding| {
|
||||
binding
|
||||
.match_keystrokes(input)
|
||||
.map(|pending| (binding, pending))
|
||||
});
|
||||
|
||||
let mut bindings: SmallVec<[(KeyBinding, usize); 1]> = SmallVec::new();
|
||||
let mut first_pending = None;
|
||||
|
||||
for (binding, pending) in possibilities {
|
||||
for depth in (0..=context_stack.len()).rev() {
|
||||
if keymap.binding_enabled(binding, &context_stack[0..depth]) {
|
||||
if first_pending.is_none() {
|
||||
first_pending = Some(pending);
|
||||
}
|
||||
if !pending {
|
||||
bindings.push((binding.clone(), depth));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bindings.sort_by(|a, b| a.1.cmp(&b.1).reverse());
|
||||
let bindings = bindings.into_iter().map(|(binding, _)| binding).collect();
|
||||
|
||||
// Precedence:
|
||||
// - if the user has defined a longer keystroke (e.g. ,=), we should say 'pending' for the builtin ','
|
||||
// - if the user has defined a shorter keystroke (e.g. ctrl-w), we should say 'not pending' for the builtin `ctrl-w q`
|
||||
// - hypoethetically, if they have defined *both*, then what?
|
||||
|
||||
// - ,: vim::Next
|
||||
// - ,=: custom wev
|
||||
// - ,: custom wev
|
||||
|
||||
// -ctrl-w q: vim::Close
|
||||
// -ctrl-w: terminal::Close
|
||||
// -ctrl-w q: vim::Close
|
||||
|
||||
return (bindings, first_pending.unwrap_or_default());
|
||||
self.keymap
|
||||
.borrow()
|
||||
.bindings_for_input(&input, &context_stack)
|
||||
}
|
||||
|
||||
/// dispatch_key processes the keystroke
|
||||
/// input should be set to the value of `pending` from the previous call to dispatch_key.
|
||||
/// In the common case, input will be empty, and the MatchResult will return just actions to do.
|
||||
/// If input is not empty, then either:
|
||||
/// - input + keystroke matches a set of bindings, which will be returned.
|
||||
/// - input + keystroke is still a prefix, so only pending
|
||||
/// - input + keystroke did not match anything. In that case, the caller will need to replay
|
||||
/// what the original input should have done before handling any of the returned actions.
|
||||
/// 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]>,
|
||||
) -> MatchResult {
|
||||
println!("---------- {:?} {:?} ---------------", input, keystroke);
|
||||
) -> DispatchResult {
|
||||
input.push(keystroke.clone());
|
||||
let (bindings, pending) = self.match_input(&input, dispatch_path);
|
||||
let (bindings, pending) = self.bindings_for_input(&input, dispatch_path);
|
||||
|
||||
if pending {
|
||||
return MatchResult {
|
||||
return DispatchResult {
|
||||
pending: input,
|
||||
..Default::default()
|
||||
};
|
||||
}
|
||||
|
||||
if !bindings.is_empty() {
|
||||
return MatchResult {
|
||||
} else if !bindings.is_empty() {
|
||||
return DispatchResult {
|
||||
bindings,
|
||||
..Default::default()
|
||||
};
|
||||
} else if input.len() == 1 {
|
||||
return DispatchResult::default();
|
||||
}
|
||||
|
||||
input.pop();
|
||||
if input.len() == 0 {
|
||||
return MatchResult::default();
|
||||
}
|
||||
|
||||
let mut to_replay: SmallVec<[Replay; 1]> = Default::default();
|
||||
let (suffix, mut to_replay) = self.replay_prefix(input, dispatch_path);
|
||||
|
||||
// At this point we know that we have multiple keys in flight, and the
|
||||
// full set matches nothing.
|
||||
// Try matching with prefixes of the full set, and if that doesn't work,
|
||||
// replay the first input key, and recurse.
|
||||
|
||||
for last in (0..input.len()).rev() {
|
||||
let (bindings, _) = self.match_input(&input[0..=last], dispatch_path);
|
||||
if !bindings.is_empty() {
|
||||
to_replay.push(Replay {
|
||||
keystroke: input.drain(0..=last).last().unwrap(),
|
||||
bindings,
|
||||
});
|
||||
break;
|
||||
}
|
||||
if last == 0 {
|
||||
to_replay.push(Replay {
|
||||
keystroke: input.remove(0),
|
||||
..Default::default()
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = self.dispatch_key(input, keystroke, dispatch_path);
|
||||
let mut result = self.dispatch_key(suffix, keystroke, dispatch_path);
|
||||
to_replay.extend(result.to_replay);
|
||||
result.to_replay = to_replay;
|
||||
return result;
|
||||
}
|
||||
|
||||
/// flush converts any remaining pending input to replay events.
|
||||
/// This is called when a user hits the timeout mid-way through typing a key.
|
||||
pub fn flush(
|
||||
/// 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))
|
||||
}
|
||||
|
||||
to_replay
|
||||
}
|
||||
|
||||
/// 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<[Replay; 1]> {
|
||||
) -> (SmallVec<[Keystroke; 1]>, SmallVec<[Replay; 1]>) {
|
||||
let mut to_replay: SmallVec<[Replay; 1]> = Default::default();
|
||||
|
||||
// At this point we know that we have multiple keys in flight, and the
|
||||
// full set matches nothing.
|
||||
// Try matching with prefixes of the full set, and if that doesn't work,
|
||||
// replay the first input key, and recurse.
|
||||
|
||||
for last in (0..input.len()).rev() {
|
||||
let (bindings, _) = self.match_input(&input[0..=last], dispatch_path);
|
||||
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(),
|
||||
@@ -567,20 +498,14 @@ impl DispatchTree {
|
||||
});
|
||||
break;
|
||||
}
|
||||
if last == 0 {
|
||||
to_replay.push(Replay {
|
||||
keystroke: input.remove(0),
|
||||
..Default::default()
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if input.len() > 0 {
|
||||
to_replay.extend(self.flush(input, dispatch_path))
|
||||
if to_replay.is_empty() {
|
||||
to_replay.push(Replay {
|
||||
keystroke: input.remove(0),
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
to_replay
|
||||
(input, to_replay)
|
||||
}
|
||||
|
||||
pub fn dispatch_path(&self, target: DispatchNodeId) -> SmallVec<[DispatchNodeId; 32]> {
|
||||
|
||||
@@ -5,7 +5,7 @@ pub use binding::*;
|
||||
pub use context::*;
|
||||
|
||||
use crate::{Action, Keystroke, NoAction};
|
||||
use collections::{HashMap, HashSet};
|
||||
use collections::HashMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::any::{Any, TypeId};
|
||||
|
||||
@@ -19,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,
|
||||
}
|
||||
|
||||
@@ -39,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;
|
||||
@@ -64,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;
|
||||
}
|
||||
|
||||
@@ -87,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) {
|
||||
@@ -96,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
|
||||
}
|
||||
}
|
||||
@@ -166,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3253,19 +3253,6 @@ impl<'a> WindowContext<'a> {
|
||||
currently_pending = PendingInput::default();
|
||||
}
|
||||
|
||||
// base case:
|
||||
// d: returns [`delete`, '']
|
||||
// j: returns [None, 'j']
|
||||
// k: returns [`escape`, '']
|
||||
// l: returns ['j`, `escape`, '']
|
||||
// ctrl-s: returns ['j`, `save`, '']
|
||||
|
||||
// c: returns ['', None, 'c']
|
||||
// a: returns ['', None, 'ca']
|
||||
// c: returns ['ca', None, 'c']
|
||||
// a: returns ['', None' ,'ca']
|
||||
// t: returns ['', `dispatch`, '']
|
||||
|
||||
let match_result = self.window.rendered_frame.dispatch_tree.dispatch_key(
|
||||
currently_pending.keystrokes,
|
||||
keystroke,
|
||||
@@ -3300,7 +3287,7 @@ impl<'a> WindowContext<'a> {
|
||||
.window
|
||||
.rendered_frame
|
||||
.dispatch_tree
|
||||
.flush(currently_pending.keystrokes, &dispatch_path);
|
||||
.flush_dispatch(currently_pending.keystrokes, &dispatch_path);
|
||||
|
||||
cx.replay_pending_input(to_replay)
|
||||
})
|
||||
@@ -3313,7 +3300,6 @@ impl<'a> WindowContext<'a> {
|
||||
}
|
||||
|
||||
self.pending_input_changed();
|
||||
|
||||
self.propagate_event = true;
|
||||
for binding in match_result.bindings {
|
||||
self.dispatch_action_on_node(node_id, binding.action.as_ref());
|
||||
|
||||
@@ -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::*;
|
||||
@@ -1393,3 +1393,23 @@ async fn test_remap_nested_pineapple(cx: &mut gpui::TestAppContext) {
|
||||
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("ˇ");
|
||||
}
|
||||
|
||||
@@ -400,6 +400,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();
|
||||
|
||||
4
crates/vim/test_data/test_ctrl_w_override.json
Normal file
4
crates/vim/test_data/test_ctrl_w_override.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{"Exec":{"command":"map <c-w> D"}}
|
||||
{"Put":{"state":"ˇhi"}}
|
||||
{"Key":"ctrl-w"}
|
||||
{"Get":{"state":"ˇ","mode":"Normal"}}
|
||||
6
crates/vim/test_data/test_escape_while_waiting.json
Normal file
6
crates/vim/test_data/test_escape_while_waiting.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{"Put":{"state":"ˇhi"}}
|
||||
{"Key":"\""}
|
||||
{"Key":"+"}
|
||||
{"Key":"escape"}
|
||||
{"Key":"x"}
|
||||
{"Get":{"state":"ˇi","mode":"Normal"}}
|
||||
Reference in New Issue
Block a user