Merge pull request #1827 from zed-industries/fix-keymap-resolution-fallback

Keymap resolution fallbacks
This commit is contained in:
Kay Simmons 2022-10-27 13:50:38 -07:00 committed by Max Brunsfeld
parent 6e2d3aae68
commit 62d4473c3f
2 changed files with 134 additions and 127 deletions

View file

@ -1544,17 +1544,18 @@ impl MutableAppContext {
{ {
MatchResult::None => false, MatchResult::None => false,
MatchResult::Pending => true, MatchResult::Pending => true,
MatchResult::Match { view_id, action } => { MatchResult::Matches(matches) => {
if self.handle_dispatch_action_from_effect( for (view_id, action) in matches {
window_id, if self.handle_dispatch_action_from_effect(
Some(view_id), window_id,
action.as_ref(), Some(view_id),
) { action.as_ref(),
self.keystroke_matcher.clear_pending(); ) {
true self.keystroke_matcher.clear_pending();
} else { return true;
false }
} }
false
} }
} }
} else { } else {

View file

@ -13,16 +13,11 @@ extern "C" {
} }
pub struct Matcher { pub struct Matcher {
pending: HashMap<usize, Pending>, pending_views: HashMap<usize, Context>,
pending_keystrokes: Vec<Keystroke>,
keymap: Keymap, keymap: Keymap,
} }
#[derive(Default)]
struct Pending {
keystrokes: Vec<Keystroke>,
context: Option<Context>,
}
#[derive(Default)] #[derive(Default)]
pub struct Keymap { pub struct Keymap {
bindings: Vec<Binding>, bindings: Vec<Binding>,
@ -77,21 +72,21 @@ where
pub enum MatchResult { pub enum MatchResult {
None, None,
Pending, Pending,
Match { Matches(Vec<(usize, Box<dyn Action>)>),
view_id: usize,
action: Box<dyn Action>,
},
} }
impl Debug for MatchResult { impl Debug for MatchResult {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
MatchResult::None => f.debug_struct("MatchResult2::None").finish(), MatchResult::None => f.debug_struct("MatchResult::None").finish(),
MatchResult::Pending => f.debug_struct("MatchResult2::Pending").finish(), MatchResult::Pending => f.debug_struct("MatchResult::Pending").finish(),
MatchResult::Match { view_id, action } => f MatchResult::Matches(matches) => f
.debug_struct("MatchResult::Match") .debug_list()
.field("view_id", view_id) .entries(
.field("action", &action.name()) matches
.iter()
.map(|(view_id, action)| format!("{view_id}, {}", action.name())),
)
.finish(), .finish(),
} }
} }
@ -102,13 +97,14 @@ impl PartialEq for MatchResult {
match (self, other) { match (self, other) {
(MatchResult::None, MatchResult::None) => true, (MatchResult::None, MatchResult::None) => true,
(MatchResult::Pending, MatchResult::Pending) => true, (MatchResult::Pending, MatchResult::Pending) => true,
( (MatchResult::Matches(matches), MatchResult::Matches(other_matches)) => {
MatchResult::Match { view_id, action }, matches.len() == other_matches.len()
MatchResult::Match { && matches.iter().zip(other_matches.iter()).all(
view_id: other_view_id, |((view_id, action), (other_view_id, other_action))| {
action: other_action, view_id == other_view_id && action.eq(other_action.as_ref())
}, },
) => view_id == other_view_id && action.eq(other_action.as_ref()), )
}
_ => false, _ => false,
} }
} }
@ -119,23 +115,24 @@ impl Eq for MatchResult {}
impl Matcher { impl Matcher {
pub fn new(keymap: Keymap) -> Self { pub fn new(keymap: Keymap) -> Self {
Self { Self {
pending: HashMap::new(), pending_views: HashMap::new(),
pending_keystrokes: Vec::new(),
keymap, keymap,
} }
} }
pub fn set_keymap(&mut self, keymap: Keymap) { pub fn set_keymap(&mut self, keymap: Keymap) {
self.pending.clear(); self.clear_pending();
self.keymap = keymap; self.keymap = keymap;
} }
pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) { pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
self.pending.clear(); self.clear_pending();
self.keymap.add_bindings(bindings); self.keymap.add_bindings(bindings);
} }
pub fn clear_bindings(&mut self) { pub fn clear_bindings(&mut self) {
self.pending.clear(); self.clear_pending();
self.keymap.clear(); self.keymap.clear();
} }
@ -144,11 +141,12 @@ impl Matcher {
} }
pub fn clear_pending(&mut self) { pub fn clear_pending(&mut self) {
self.pending.clear(); self.pending_keystrokes.clear();
self.pending_views.clear();
} }
pub fn has_pending_keystrokes(&self) -> bool { pub fn has_pending_keystrokes(&self) -> bool {
!self.pending.is_empty() !self.pending_keystrokes.is_empty()
} }
pub fn push_keystroke( pub fn push_keystroke(
@ -157,53 +155,53 @@ impl Matcher {
dispatch_path: Vec<(usize, Context)>, dispatch_path: Vec<(usize, Context)>,
) -> MatchResult { ) -> MatchResult {
let mut any_pending = false; let mut any_pending = false;
let mut matched_bindings = Vec::new();
let first_keystroke = self.pending_keystrokes.is_empty();
self.pending_keystrokes.push(keystroke);
let first_keystroke = self.pending.is_empty();
for (view_id, context) in dispatch_path { for (view_id, context) in dispatch_path {
if !first_keystroke && !self.pending.contains_key(&view_id) { // Don't require pending view entry if there are no pending keystrokes
if !first_keystroke && !self.pending_views.contains_key(&view_id) {
continue; continue;
} }
let pending = self.pending.entry(view_id).or_default(); // If there is a previous view context, invalidate that view if it
// has changed
if let Some(pending_context) = pending.context.as_ref() { if let Some(previous_view_context) = self.pending_views.remove(&view_id) {
if pending_context != &context { if previous_view_context != context {
pending.keystrokes.clear(); continue;
} }
} }
pending.keystrokes.push(keystroke.clone()); // Find the bindings which map the pending keystrokes and current context
let mut retain_pending = false;
for binding in self.keymap.bindings.iter().rev() { for binding in self.keymap.bindings.iter().rev() {
if binding.keystrokes.starts_with(&pending.keystrokes) if binding.keystrokes.starts_with(&self.pending_keystrokes)
&& binding && binding
.context_predicate .context_predicate
.as_ref() .as_ref()
.map(|c| c.eval(&context)) .map(|c| c.eval(&context))
.unwrap_or(true) .unwrap_or(true)
{ {
if binding.keystrokes.len() == pending.keystrokes.len() { // If the binding is completed, push it onto the matches list
self.pending.remove(&view_id); if binding.keystrokes.len() == self.pending_keystrokes.len() {
return MatchResult::Match { matched_bindings.push((view_id, binding.action.boxed_clone()));
view_id,
action: binding.action.boxed_clone(),
};
} else { } else {
retain_pending = true; // Otherwise, the binding is still pending
pending.context = Some(context.clone()); self.pending_views.insert(view_id, context.clone());
any_pending = true;
} }
} }
} }
if retain_pending {
any_pending = true;
} else {
self.pending.remove(&view_id);
}
} }
if any_pending { if !any_pending {
self.clear_pending();
}
if !matched_bindings.is_empty() {
MatchResult::Matches(matched_bindings)
} else if any_pending {
MatchResult::Pending MatchResult::Pending
} else { } else {
MatchResult::None MatchResult::None
@ -499,7 +497,7 @@ mod tests {
#[test] #[test]
fn test_push_keystroke() -> Result<()> { fn test_push_keystroke() -> Result<()> {
actions!(test, [B, AB, C]); actions!(test, [B, AB, C, D, DA]);
let mut ctx1 = Context::default(); let mut ctx1 = Context::default();
ctx1.set.insert("1".into()); ctx1.set.insert("1".into());
@ -513,39 +511,58 @@ mod tests {
Binding::new("a b", AB, Some("1")), Binding::new("a b", AB, Some("1")),
Binding::new("b", B, Some("2")), Binding::new("b", B, Some("2")),
Binding::new("c", C, Some("2")), Binding::new("c", C, Some("2")),
Binding::new("d", D, Some("1")),
Binding::new("d", D, Some("2")),
Binding::new("d a", DA, Some("2")),
]); ]);
let mut matcher = Matcher::new(keymap); let mut matcher = Matcher::new(keymap);
// Binding with pending prefix always takes precedence
assert_eq!( assert_eq!(
matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
MatchResult::Pending, MatchResult::Pending,
matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone())
); );
// B alone doesn't match because a was pending, so AB is returned instead
assert_eq!( assert_eq!(
MatchResult::Match { matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
view_id: 1, MatchResult::Matches(vec![(1, Box::new(AB))]),
action: Box::new(AB)
},
matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone())
); );
assert!(matcher.pending.is_empty()); assert!(!matcher.has_pending_keystrokes());
// Without an a prefix, B is dispatched like expected
assert_eq!( assert_eq!(
MatchResult::Match { matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
view_id: 2, MatchResult::Matches(vec![(2, Box::new(B))]),
action: Box::new(B)
},
matcher.push_keystroke(Keystroke::parse("b")?, dispatch_path.clone())
); );
assert!(matcher.pending.is_empty()); assert!(!matcher.has_pending_keystrokes());
// If a is prefixed, C will not be dispatched because there
// was a pending binding for it
assert_eq!( assert_eq!(
matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
MatchResult::Pending, MatchResult::Pending,
matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone())
); );
assert_eq!( assert_eq!(
matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
MatchResult::None, MatchResult::None,
matcher.push_keystroke(Keystroke::parse("c")?, dispatch_path.clone())
); );
assert!(matcher.pending.is_empty()); assert!(!matcher.has_pending_keystrokes());
// If a single keystroke matches multiple bindings in the tree
// all of them are returned so that we can fallback if the action
// handler decides to propagate the action
assert_eq!(
matcher.push_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
MatchResult::Matches(vec![(2, Box::new(D)), (1, Box::new(D))]),
);
// If none of the d action handlers consume the binding, a pending
// binding may then be used
assert_eq!(
matcher.push_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
MatchResult::Matches(vec![(2, Box::new(DA))]),
);
assert!(!matcher.has_pending_keystrokes());
Ok(()) Ok(())
} }
@ -666,71 +683,60 @@ mod tests {
// Basic match // Basic match
assert_eq!( assert_eq!(
downcast(&matcher.test_keystroke("a", vec![(1, ctx_a.clone())])), matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_a.clone())]),
Some(&A("x".to_string())) MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
); );
matcher.clear_pending();
// Multi-keystroke match // Multi-keystroke match
assert!(matcher
.test_keystroke("a", vec![(1, ctx_b.clone())])
.is_none());
assert_eq!( assert_eq!(
downcast(&matcher.test_keystroke("b", vec![(1, ctx_b.clone())])), matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_b.clone())]),
Some(&Ab) MatchResult::Pending
); );
assert_eq!(
matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, ctx_b.clone())]),
MatchResult::Matches(vec![(1, Box::new(Ab))])
);
matcher.clear_pending();
// Failed matches don't interfere with matching subsequent keys // Failed matches don't interfere with matching subsequent keys
assert!(matcher
.test_keystroke("x", vec![(1, ctx_a.clone())])
.is_none());
assert_eq!( assert_eq!(
downcast(&matcher.test_keystroke("a", vec![(1, ctx_a.clone())])), matcher.push_keystroke(Keystroke::parse("x")?, vec![(1, ctx_a.clone())]),
Some(&A("x".to_string())) MatchResult::None
); );
assert_eq!(
matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_a.clone())]),
MatchResult::Matches(vec![(1, Box::new(A("x".to_string())))])
);
matcher.clear_pending();
// Pending keystrokes are cleared when the context changes // Pending keystrokes are cleared when the context changes
assert!(&matcher
.test_keystroke("a", vec![(1, ctx_b.clone())])
.is_none());
assert_eq!( assert_eq!(
downcast(&matcher.test_keystroke("b", vec![(1, ctx_a.clone())])), matcher.push_keystroke(Keystroke::parse("a")?, vec![(1, ctx_b.clone())]),
Some(&B) MatchResult::Pending
); );
assert_eq!(
matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, ctx_a.clone())]),
MatchResult::None
);
matcher.clear_pending();
let mut ctx_c = Context::default(); let mut ctx_c = Context::default();
ctx_c.set.insert("c".into()); ctx_c.set.insert("c".into());
// Pending keystrokes are maintained per-view // Pending keystrokes are maintained per-view
assert!(matcher
.test_keystroke("a", vec![(1, ctx_b.clone()), (2, ctx_c.clone())])
.is_none());
assert_eq!( assert_eq!(
downcast(&matcher.test_keystroke("b", vec![(1, ctx_b.clone())])), matcher.push_keystroke(
Some(&Ab) Keystroke::parse("a")?,
vec![(1, ctx_b.clone()), (2, ctx_c.clone())]
),
MatchResult::Pending
);
assert_eq!(
matcher.push_keystroke(Keystroke::parse("b")?, vec![(1, ctx_b.clone())]),
MatchResult::Matches(vec![(1, Box::new(Ab))])
); );
Ok(()) Ok(())
} }
fn downcast<A: Action>(action: &Option<Box<dyn Action>>) -> Option<&A> {
action
.as_ref()
.and_then(|action| action.as_any().downcast_ref())
}
impl Matcher {
fn test_keystroke(
&mut self,
keystroke: &str,
dispatch_path: Vec<(usize, Context)>,
) -> Option<Box<dyn Action>> {
if let MatchResult::Match { action, .. } =
self.push_keystroke(Keystroke::parse(keystroke).unwrap(), dispatch_path)
{
Some(action.boxed_clone())
} else {
None
}
}
}
} }