Detailed changes
@@ -33,7 +33,7 @@ pub trait Element: 'static + Send + Sync + IntoAnyElement<Self::ViewState> {
}
#[derive(Deref, DerefMut, Default, Clone, Debug, Eq, PartialEq, Hash)]
-pub(crate) struct GlobalElementId(SmallVec<[ElementId; 8]>);
+pub struct GlobalElementId(SmallVec<[ElementId; 8]>);
pub trait ElementIdentity: 'static + Send + Sync {
fn id(&self) -> Option<ElementId>;
@@ -1,10 +1,10 @@
use crate::{
Active, Anonymous, AnyElement, AppContext, BorrowWindow, Bounds, Click, DispatchPhase, Element,
ElementFocusability, ElementId, ElementIdentity, EventListeners, Focus, FocusHandle, Focusable,
- Hover, Identified, Interactive, IntoAnyElement, KeyDownEvent, KeyMatch, LayoutId,
- MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, NonFocusable, Overflow,
- ParentElement, Pixels, Point, ScrollWheelEvent, SharedString, Style, StyleRefinement, Styled,
- ViewContext,
+ GlobalElementId, Hover, Identified, Interactive, IntoAnyElement, KeyDownEvent, KeyMatch,
+ LayoutId, MouseClickEvent, MouseDownEvent, MouseMoveEvent, MouseUpEvent, NonFocusable,
+ Overflow, ParentElement, Pixels, Point, ScrollWheelEvent, SharedString, Style, StyleRefinement,
+ Styled, ViewContext,
};
use collections::HashMap;
use parking_lot::Mutex;
@@ -188,12 +188,12 @@ where
fn with_element_id<R>(
&mut self,
cx: &mut ViewContext<V>,
- f: impl FnOnce(&mut Self, &mut ViewContext<V>) -> R,
+ f: impl FnOnce(&mut Self, Option<GlobalElementId>, &mut ViewContext<V>) -> R,
) -> R {
if let Some(id) = self.id() {
- cx.with_element_id(id, |cx| f(self, cx))
+ cx.with_element_id(id, |global_id, cx| f(self, Some(global_id), cx))
} else {
- f(self, cx)
+ f(self, None, cx)
}
}
@@ -424,46 +424,49 @@ where
element_state: Option<Self::ElementState>,
cx: &mut ViewContext<Self::ViewState>,
) -> Self::ElementState {
- let element_state = element_state.unwrap_or_default();
- for listener in self.listeners.focus.iter().cloned() {
- cx.on_focus_changed(move |view, event, cx| listener(view, event, cx));
- }
-
- let mut key_listeners = mem::take(&mut self.listeners.key);
+ self.with_element_id(cx, |this, global_id, cx| {
+ let element_state = element_state.unwrap_or_default();
+ for listener in this.listeners.focus.iter().cloned() {
+ cx.on_focus_changed(move |view, event, cx| listener(view, event, cx));
+ }
- if let Some(id) = self.id() {
- key_listeners.push((
- TypeId::of::<KeyDownEvent>(),
- Arc::new(move |_, key_down, phase, cx| {
- if phase == DispatchPhase::Bubble {
- let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
- if let KeyMatch::Some(action) = cx.match_keystroke(&id, &key_down.keystroke)
- {
- return Some(action);
+ let mut key_listeners = mem::take(&mut this.listeners.key);
+
+ if let Some(global_id) = global_id {
+ key_listeners.push((
+ TypeId::of::<KeyDownEvent>(),
+ Arc::new(move |_, key_down, phase, cx| {
+ if phase == DispatchPhase::Bubble {
+ let key_down = key_down.downcast_ref::<KeyDownEvent>().unwrap();
+ if let KeyMatch::Some(action) =
+ cx.match_keystroke(&global_id, &key_down.keystroke)
+ {
+ return Some(action);
+ }
}
- }
- None
- }),
- ));
- }
+ None
+ }),
+ ));
+ }
- cx.with_key_listeners(&key_listeners, |cx| {
- if let Some(focus_handle) = self.focusability.focus_handle().cloned() {
- cx.with_focus(focus_handle, |cx| {
- for child in &mut self.children {
+ cx.with_key_listeners(&key_listeners, |cx| {
+ if let Some(focus_handle) = this.focusability.focus_handle().cloned() {
+ cx.with_focus(focus_handle, |cx| {
+ for child in &mut this.children {
+ child.initialize(view_state, cx);
+ }
+ })
+ } else {
+ for child in &mut this.children {
child.initialize(view_state, cx);
}
- })
- } else {
- for child in &mut self.children {
- child.initialize(view_state, cx);
}
- }
- });
- self.listeners.key = key_listeners;
+ });
+ this.listeners.key = key_listeners;
- element_state
+ element_state
+ })
}
fn layout(
@@ -474,7 +477,7 @@ where
) -> LayoutId {
let style = self.compute_style(Bounds::default(), element_state, cx);
style.apply_text_style(cx, |cx| {
- self.with_element_id(cx, |this, cx| {
+ self.with_element_id(cx, |this, _global_id, cx| {
let layout_ids = this
.children
.iter_mut()
@@ -492,7 +495,7 @@ where
element_state: &mut Self::ElementState,
cx: &mut ViewContext<Self::ViewState>,
) {
- self.with_element_id(cx, |this, cx| {
+ self.with_element_id(cx, |this, _global_id, cx| {
if let Some(group) = this.group.clone() {
cx.default_global::<GroupBounds>()
.0
@@ -1,5 +1,5 @@
use crate::{
- point, AnyBox, Bounds, DispatchPhase, FocusHandle, Keystroke, Modifiers, Pixels, Point,
+ point, Action, Bounds, DispatchPhase, FocusHandle, Keystroke, Modifiers, Pixels, Point,
ViewContext,
};
use smallvec::SmallVec;
@@ -256,7 +256,7 @@ pub type ScrollWheelListener<V> = Arc<
>;
pub type KeyListener<V> = Arc<
- dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) -> Option<AnyBox>
+ dyn Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext<V>) -> Option<Box<dyn Action>>
+ Send
+ Sync
+ 'static,
@@ -11,7 +11,7 @@ mod geometry;
mod hover;
mod image_cache;
mod interactive;
-mod keymap;
+mod keymap_matcher;
mod platform;
mod scene;
mod style;
@@ -39,7 +39,7 @@ pub use gpui3_macros::*;
pub use hover::*;
pub use image_cache::*;
pub use interactive::*;
-pub use keymap::*;
+pub use keymap_matcher::*;
pub use platform::*;
pub use refineable::*;
pub use scene::*;
@@ -1,16 +0,0 @@
-use crate::{AnyBox, Keystroke};
-
-#[derive(Default)]
-pub struct KeyMatcher;
-
-impl KeyMatcher {
- pub fn push_keystroke(&mut self, keystroke: Keystroke) -> KeyMatch {
- todo!()
- }
-}
-
-pub enum KeyMatch {
- None,
- Pending,
- Some(AnyBox),
-}
@@ -0,0 +1,488 @@
+mod binding;
+mod keymap;
+mod keymap_context;
+
+pub use binding::*;
+pub use keymap::*;
+pub use keymap_context::*;
+
+use crate::Keystroke;
+use smallvec::SmallVec;
+use std::any::{Any, TypeId};
+
+pub trait Action: Any + Send + Sync {
+ fn partial_eq(&self, action: &dyn Action) -> bool;
+ fn boxed_clone(&self) -> Box<dyn Action>;
+ fn as_any(&self) -> &dyn Any;
+}
+
+pub struct KeymapMatcher {
+ pending_keystrokes: Vec<Keystroke>,
+ keymap: Keymap,
+ keymap_version: KeymapVersion,
+}
+
+impl KeymapMatcher {
+ pub fn new(keymap: Keymap) -> Self {
+ Self {
+ pending_keystrokes: Vec::new(),
+ keymap_version: keymap.version(),
+ keymap,
+ }
+ }
+
+ pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &Binding> {
+ self.keymap.bindings_for_action(action_id)
+ }
+
+ pub fn clear_pending(&mut self) {
+ self.pending_keystrokes.clear();
+ }
+
+ 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 fn match_keystroke(
+ &mut self,
+ keystroke: &Keystroke,
+ context_stack: &[KeymapContext],
+ ) -> KeyMatch {
+ // Clear pending keystrokes if the keymap has changed since the last matched keystroke.
+ if self.keymap.version() != self.keymap_version {
+ self.keymap_version = self.keymap.version();
+ self.pending_keystrokes.clear();
+ }
+
+ let mut pending_key = None;
+
+ for binding in self.keymap.bindings().iter().rev() {
+ for candidate in keystroke.match_candidates() {
+ self.pending_keystrokes.push(candidate.clone());
+ match binding.match_keystrokes(&self.pending_keystrokes, context_stack) {
+ KeyMatch::Some(action) => {
+ self.clear_pending();
+ return KeyMatch::Some(action);
+ }
+ KeyMatch::Pending => {
+ pending_key.get_or_insert(candidate);
+ }
+ KeyMatch::None => {}
+ }
+ self.pending_keystrokes.pop();
+ }
+ }
+
+ if let Some(pending_key) = pending_key {
+ self.pending_keystrokes.push(pending_key);
+ }
+
+ if self.pending_keystrokes.is_empty() {
+ KeyMatch::None
+ } else {
+ KeyMatch::Pending
+ }
+ }
+
+ pub fn keystrokes_for_action(
+ &self,
+ action: &dyn Action,
+ contexts: &[KeymapContext],
+ ) -> Option<SmallVec<[Keystroke; 2]>> {
+ self.keymap
+ .bindings()
+ .iter()
+ .rev()
+ .find_map(|binding| binding.keystrokes_for_action(action, contexts))
+ }
+}
+
+impl Default for KeymapMatcher {
+ fn default() -> Self {
+ Self::new(Keymap::default())
+ }
+}
+
+pub enum KeyMatch {
+ None,
+ Pending,
+ Some(Box<dyn Action>),
+}
+
+impl KeyMatch {
+ pub fn is_some(&self) -> bool {
+ matches!(self, KeyMatch::Some(_))
+ }
+}
+
+// #[cfg(test)]
+// mod tests {
+// use anyhow::Result;
+// use serde::Deserialize;
+
+// use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
+
+// use super::*;
+
+// #[test]
+// fn test_keymap_and_view_ordering() -> Result<()> {
+// actions!(test, [EditorAction, ProjectPanelAction]);
+
+// let mut editor = KeymapContext::default();
+// editor.add_identifier("Editor");
+
+// let mut project_panel = KeymapContext::default();
+// project_panel.add_identifier("ProjectPanel");
+
+// // Editor 'deeper' in than project panel
+// let dispatch_path = vec![(2, editor), (1, project_panel)];
+
+// // But editor actions 'higher' up in keymap
+// let keymap = Keymap::new(vec![
+// Binding::new("left", EditorAction, Some("Editor")),
+// Binding::new("left", ProjectPanelAction, Some("ProjectPanel")),
+// ]);
+
+// let mut matcher = KeymapMatcher::new(keymap);
+
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("left")?, dispatch_path.clone()),
+// KeyMatch::Matches(vec![
+// (2, Box::new(EditorAction)),
+// (1, Box::new(ProjectPanelAction)),
+// ]),
+// );
+
+// Ok(())
+// }
+
+// #[test]
+// fn test_push_keystroke() -> Result<()> {
+// actions!(test, [B, AB, C, D, DA, E, EF]);
+
+// let mut context1 = KeymapContext::default();
+// context1.add_identifier("1");
+
+// let mut context2 = KeymapContext::default();
+// context2.add_identifier("2");
+
+// let dispatch_path = vec![(2, context2), (1, context1)];
+
+// let keymap = Keymap::new(vec![
+// Binding::new("a b", AB, Some("1")),
+// Binding::new("b", B, 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 = KeymapMatcher::new(keymap);
+
+// // Binding with pending prefix always takes precedence
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
+// KeyMatch::Pending,
+// );
+// // B alone doesn't match because a was pending, so AB is returned instead
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
+// KeyMatch::Matches(vec![(1, Box::new(AB))]),
+// );
+// assert!(!matcher.has_pending_keystrokes());
+
+// // Without an a prefix, B is dispatched like expected
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("b")?, dispatch_path.clone()),
+// KeyMatch::Matches(vec![(2, Box::new(B))]),
+// );
+// assert!(!matcher.has_pending_keystrokes());
+
+// // If a is prefixed, C will not be dispatched because there
+// // was a pending binding for it
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
+// KeyMatch::Pending,
+// );
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("c")?, dispatch_path.clone()),
+// KeyMatch::None,
+// );
+// 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.match_keystroke(Keystroke::parse("d")?, dispatch_path.clone()),
+// KeyMatch::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.match_keystroke(Keystroke::parse("a")?, dispatch_path.clone()),
+// KeyMatch::Matches(vec![(2, Box::new(DA))]),
+// );
+// assert!(!matcher.has_pending_keystrokes());
+
+// Ok(())
+// }
+
+// #[test]
+// fn test_keystroke_parsing() -> Result<()> {
+// assert_eq!(
+// Keystroke::parse("ctrl-p")?,
+// Keystroke {
+// key: "p".into(),
+// ctrl: true,
+// alt: false,
+// shift: false,
+// cmd: false,
+// function: false,
+// ime_key: None,
+// }
+// );
+
+// assert_eq!(
+// Keystroke::parse("alt-shift-down")?,
+// Keystroke {
+// key: "down".into(),
+// ctrl: false,
+// alt: true,
+// shift: true,
+// cmd: false,
+// function: false,
+// ime_key: None,
+// }
+// );
+
+// assert_eq!(
+// Keystroke::parse("shift-cmd--")?,
+// Keystroke {
+// key: "-".into(),
+// ctrl: false,
+// alt: false,
+// shift: true,
+// cmd: true,
+// function: false,
+// ime_key: None,
+// }
+// );
+
+// Ok(())
+// }
+
+// #[test]
+// fn test_context_predicate_parsing() -> Result<()> {
+// use KeymapContextPredicate::*;
+
+// assert_eq!(
+// KeymapContextPredicate::parse("a && (b == c || d != e)")?,
+// And(
+// Box::new(Identifier("a".into())),
+// Box::new(Or(
+// Box::new(Equal("b".into(), "c".into())),
+// Box::new(NotEqual("d".into(), "e".into())),
+// ))
+// )
+// );
+
+// assert_eq!(
+// KeymapContextPredicate::parse("!a")?,
+// Not(Box::new(Identifier("a".into())),)
+// );
+
+// Ok(())
+// }
+
+// #[test]
+// fn test_context_predicate_eval() {
+// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
+
+// let mut context = KeymapContext::default();
+// context.add_identifier("a");
+// assert!(!predicate.eval(&[context]));
+
+// let mut context = KeymapContext::default();
+// context.add_identifier("a");
+// context.add_identifier("b");
+// assert!(predicate.eval(&[context]));
+
+// let mut context = KeymapContext::default();
+// context.add_identifier("a");
+// context.add_key("c", "x");
+// assert!(!predicate.eval(&[context]));
+
+// let mut context = KeymapContext::default();
+// context.add_identifier("a");
+// context.add_key("c", "d");
+// assert!(predicate.eval(&[context]));
+
+// let predicate = KeymapContextPredicate::parse("!a").unwrap();
+// assert!(predicate.eval(&[KeymapContext::default()]));
+// }
+
+// #[test]
+// fn test_context_child_predicate_eval() {
+// let predicate = KeymapContextPredicate::parse("a && b > c").unwrap();
+// let contexts = [
+// context_set(&["e", "f"]),
+// context_set(&["c", "d"]), // match this context
+// context_set(&["a", "b"]),
+// ];
+
+// assert!(!predicate.eval(&contexts[0..]));
+// assert!(predicate.eval(&contexts[1..]));
+// assert!(!predicate.eval(&contexts[2..]));
+
+// let predicate = KeymapContextPredicate::parse("a && b > c && !d > e").unwrap();
+// let contexts = [
+// context_set(&["f"]),
+// context_set(&["e"]), // only match this context
+// context_set(&["c"]),
+// context_set(&["a", "b"]),
+// context_set(&["e"]),
+// context_set(&["c", "d"]),
+// context_set(&["a", "b"]),
+// ];
+
+// assert!(!predicate.eval(&contexts[0..]));
+// assert!(predicate.eval(&contexts[1..]));
+// assert!(!predicate.eval(&contexts[2..]));
+// assert!(!predicate.eval(&contexts[3..]));
+// assert!(!predicate.eval(&contexts[4..]));
+// assert!(!predicate.eval(&contexts[5..]));
+// assert!(!predicate.eval(&contexts[6..]));
+
+// fn context_set(names: &[&str]) -> KeymapContext {
+// let mut keymap = KeymapContext::new();
+// names
+// .iter()
+// .for_each(|name| keymap.add_identifier(name.to_string()));
+// keymap
+// }
+// }
+
+// #[test]
+// fn test_matcher() -> Result<()> {
+// #[derive(Clone, Deserialize, PartialEq, Eq, Debug)]
+// pub struct A(pub String);
+// impl_actions!(test, [A]);
+// actions!(test, [B, Ab, Dollar, Quote, Ess, Backtick]);
+
+// #[derive(Clone, Debug, Eq, PartialEq)]
+// struct ActionArg {
+// a: &'static str,
+// }
+
+// let keymap = Keymap::new(vec![
+// Binding::new("a", A("x".to_string()), Some("a")),
+// Binding::new("b", B, Some("a")),
+// Binding::new("a b", Ab, Some("a || b")),
+// Binding::new("$", Dollar, Some("a")),
+// Binding::new("\"", Quote, Some("a")),
+// Binding::new("alt-s", Ess, Some("a")),
+// Binding::new("ctrl-`", Backtick, Some("a")),
+// ]);
+
+// let mut context_a = KeymapContext::default();
+// context_a.add_identifier("a");
+
+// let mut context_b = KeymapContext::default();
+// context_b.add_identifier("b");
+
+// let mut matcher = KeymapMatcher::new(keymap);
+
+// // Basic match
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
+// );
+// matcher.clear_pending();
+
+// // Multi-keystroke match
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
+// KeyMatch::Pending
+// );
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(Ab))])
+// );
+// matcher.clear_pending();
+
+// // Failed matches don't interfere with matching subsequent keys
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("x")?, vec![(1, context_a.clone())]),
+// KeyMatch::None
+// );
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_a.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(A("x".to_string())))])
+// );
+// matcher.clear_pending();
+
+// // Pending keystrokes are cleared when the context changes
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("a")?, vec![(1, context_b.clone())]),
+// KeyMatch::Pending
+// );
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_a.clone())]),
+// KeyMatch::None
+// );
+// matcher.clear_pending();
+
+// let mut context_c = KeymapContext::default();
+// context_c.add_identifier("c");
+
+// // Pending keystrokes are maintained per-view
+// assert_eq!(
+// matcher.match_keystroke(
+// Keystroke::parse("a")?,
+// vec![(1, context_b.clone()), (2, context_c.clone())]
+// ),
+// KeyMatch::Pending
+// );
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("b")?, vec![(1, context_b.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(Ab))])
+// );
+
+// // handle Czech $ (option + 4 key)
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("alt-รง->$")?, vec![(1, context_a.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(Dollar))])
+// );
+
+// // handle Brazillian quote (quote key then space key)
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("space->\"")?, vec![(1, context_a.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(Quote))])
+// );
+
+// // handle ctrl+` on a brazillian keyboard
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("ctrl-->`")?, vec![(1, context_a.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(Backtick))])
+// );
+
+// // handle alt-s on a US keyboard
+// assert_eq!(
+// matcher.match_keystroke(Keystroke::parse("alt-s->ร")?, vec![(1, context_a.clone())]),
+// KeyMatch::Matches(vec![(1, Box::new(Ess))])
+// );
+
+// Ok(())
+// }
+// }
@@ -0,0 +1,80 @@
+use crate::{Action, KeyMatch, KeymapContext, KeymapContextPredicate, Keystroke};
+use anyhow::Result;
+use smallvec::SmallVec;
+
+pub struct Binding {
+ action: Box<dyn Action>,
+ pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
+ pub(super) context_predicate: Option<KeymapContextPredicate>,
+}
+
+impl Binding {
+ pub fn new<A: Action>(keystrokes: &str, action: A, context_predicate: Option<&str>) -> Self {
+ Self::load(keystrokes, Box::new(action), context_predicate).unwrap()
+ }
+
+ pub fn load(keystrokes: &str, action: Box<dyn Action>, context: Option<&str>) -> Result<Self> {
+ let context = if let Some(context) = context {
+ Some(KeymapContextPredicate::parse(context)?)
+ } else {
+ None
+ };
+
+ let keystrokes = keystrokes
+ .split_whitespace()
+ .map(Keystroke::parse)
+ .collect::<Result<_>>()?;
+
+ Ok(Self {
+ keystrokes,
+ action,
+ context_predicate: context,
+ })
+ }
+
+ pub fn matches_context(&self, contexts: &[KeymapContext]) -> bool {
+ self.context_predicate
+ .as_ref()
+ .map(|predicate| predicate.eval(contexts))
+ .unwrap_or(true)
+ }
+
+ pub fn match_keystrokes(
+ &self,
+ pending_keystrokes: &[Keystroke],
+ contexts: &[KeymapContext],
+ ) -> KeyMatch {
+ if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
+ && self.matches_context(contexts)
+ {
+ // If the binding is completed, push it onto the matches list
+ if self.keystrokes.as_ref().len() == pending_keystrokes.len() {
+ KeyMatch::Some(self.action.boxed_clone())
+ } else {
+ KeyMatch::Pending
+ }
+ } else {
+ KeyMatch::None
+ }
+ }
+
+ pub fn keystrokes_for_action(
+ &self,
+ action: &dyn Action,
+ contexts: &[KeymapContext],
+ ) -> Option<SmallVec<[Keystroke; 2]>> {
+ if self.action.partial_eq(action) && self.matches_context(contexts) {
+ Some(self.keystrokes.clone())
+ } else {
+ None
+ }
+ }
+
+ pub fn keystrokes(&self) -> &[Keystroke] {
+ self.keystrokes.as_slice()
+ }
+
+ pub fn action(&self) -> &dyn Action {
+ self.action.as_ref()
+ }
+}
@@ -0,0 +1,400 @@
+use crate::{Binding, KeymapContextPredicate, Keystroke};
+use collections::HashSet;
+use smallvec::SmallVec;
+use std::{any::TypeId, collections::HashMap};
+
+#[derive(Copy, Clone, Eq, PartialEq, Default)]
+pub struct KeymapVersion(usize);
+
+#[derive(Default)]
+pub struct Keymap {
+ bindings: Vec<Binding>,
+ binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
+ disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
+ version: KeymapVersion,
+}
+
+impl Keymap {
+ pub fn new(bindings: Vec<Binding>) -> Self {
+ let mut this = Self::default();
+ this.add_bindings(bindings);
+ this
+ }
+
+ pub fn version(&self) -> KeymapVersion {
+ self.version
+ }
+
+ pub(crate) fn bindings_for_action(
+ &self,
+ action_id: TypeId,
+ ) -> impl Iterator<Item = &'_ Binding> {
+ self.binding_indices_by_action_id
+ .get(&action_id)
+ .map(SmallVec::as_slice)
+ .unwrap_or(&[])
+ .iter()
+ .map(|ix| &self.bindings[*ix])
+ .filter(|binding| !self.binding_disabled(binding))
+ }
+
+ pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
+ // todo!("no action")
+ // let no_action_id = (NoAction {}).id();
+ let mut new_bindings = Vec::new();
+ let has_new_disabled_keystrokes = false;
+ for binding in bindings {
+ // if binding.action().id() == no_action_id {
+ // has_new_disabled_keystrokes |= self
+ // .disabled_keystrokes
+ // .entry(binding.keystrokes)
+ // .or_default()
+ // .insert(binding.context_predicate);
+ // } else {
+ new_bindings.push(binding);
+ // }
+ }
+
+ if has_new_disabled_keystrokes {
+ self.binding_indices_by_action_id.retain(|_, indices| {
+ indices.retain(|ix| {
+ let binding = &self.bindings[*ix];
+ match self.disabled_keystrokes.get(&binding.keystrokes) {
+ Some(disabled_predicates) => {
+ !disabled_predicates.contains(&binding.context_predicate)
+ }
+ None => true,
+ }
+ });
+ !indices.is_empty()
+ });
+ }
+
+ for new_binding in new_bindings {
+ if !self.binding_disabled(&new_binding) {
+ self.binding_indices_by_action_id
+ .entry(new_binding.action().as_any().type_id())
+ .or_default()
+ .push(self.bindings.len());
+ self.bindings.push(new_binding);
+ }
+ }
+
+ self.version.0 += 1;
+ }
+
+ pub fn clear(&mut self) {
+ self.bindings.clear();
+ self.binding_indices_by_action_id.clear();
+ self.disabled_keystrokes.clear();
+ self.version.0 += 1;
+ }
+
+ pub fn bindings(&self) -> Vec<&Binding> {
+ self.bindings
+ .iter()
+ .filter(|binding| !self.binding_disabled(binding))
+ .collect()
+ }
+
+ fn binding_disabled(&self, binding: &Binding) -> bool {
+ match self.disabled_keystrokes.get(&binding.keystrokes) {
+ Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
+ None => false,
+ }
+ }
+}
+
+// #[cfg(test)]
+// mod tests {
+// use crate::actions;
+
+// use super::*;
+
+// actions!(
+// keymap_test,
+// [Present1, Present2, Present3, Duplicate, Missing]
+// );
+
+// #[test]
+// fn regular_keymap() {
+// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
+// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
+// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
+// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+// let missing = Binding::new("ctrl-r", Missing {}, None);
+// let all_bindings = [
+// &present_1,
+// &present_2,
+// &present_3,
+// &keystroke_duplicate_to_1,
+// &full_duplicate_to_2,
+// &missing,
+// ];
+
+// let mut keymap = Keymap::default();
+// assert_absent(&keymap, &all_bindings);
+// assert!(keymap.bindings().is_empty());
+
+// keymap.add_bindings([present_1.clone(), present_2.clone(), present_3.clone()]);
+// assert_absent(&keymap, &[&keystroke_duplicate_to_1, &missing]);
+// assert_present(
+// &keymap,
+// &[(&present_1, "q"), (&present_2, "w"), (&present_3, "e")],
+// );
+
+// keymap.add_bindings([
+// keystroke_duplicate_to_1.clone(),
+// full_duplicate_to_2.clone(),
+// ]);
+// assert_absent(&keymap, &[&missing]);
+// assert!(
+// !keymap.binding_disabled(&keystroke_duplicate_to_1),
+// "Duplicate binding 1 was added and should not be disabled"
+// );
+// assert!(
+// !keymap.binding_disabled(&full_duplicate_to_2),
+// "Duplicate binding 2 was added and should not be disabled"
+// );
+
+// assert_eq!(
+// keymap
+// .bindings_for_action(keystroke_duplicate_to_1.action().id())
+// .map(|binding| &binding.keystrokes)
+// .flatten()
+// .collect::<Vec<_>>(),
+// vec![&Keystroke {
+// ctrl: true,
+// alt: false,
+// shift: false,
+// cmd: false,
+// function: false,
+// key: "q".to_string(),
+// ime_key: None,
+// }],
+// "{keystroke_duplicate_to_1:?} should have the expected keystroke in the keymap"
+// );
+// assert_eq!(
+// keymap
+// .bindings_for_action(full_duplicate_to_2.action().id())
+// .map(|binding| &binding.keystrokes)
+// .flatten()
+// .collect::<Vec<_>>(),
+// vec![
+// &Keystroke {
+// ctrl: true,
+// alt: false,
+// shift: false,
+// cmd: false,
+// function: false,
+// key: "w".to_string(),
+// ime_key: None,
+// },
+// &Keystroke {
+// ctrl: true,
+// alt: false,
+// shift: false,
+// cmd: false,
+// function: false,
+// key: "w".to_string(),
+// ime_key: None,
+// }
+// ],
+// "{full_duplicate_to_2:?} should have a duplicated keystroke in the keymap"
+// );
+
+// let updated_bindings = keymap.bindings();
+// let expected_updated_bindings = vec![
+// &present_1,
+// &present_2,
+// &present_3,
+// &keystroke_duplicate_to_1,
+// &full_duplicate_to_2,
+// ];
+// assert_eq!(
+// updated_bindings.len(),
+// expected_updated_bindings.len(),
+// "Unexpected updated keymap bindings {updated_bindings:?}"
+// );
+// for (i, expected) in expected_updated_bindings.iter().enumerate() {
+// let keymap_binding = &updated_bindings[i];
+// assert_eq!(
+// keymap_binding.context_predicate, expected.context_predicate,
+// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
+// );
+// assert_eq!(
+// keymap_binding.keystrokes, expected.keystrokes,
+// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
+// );
+// }
+
+// keymap.clear();
+// assert_absent(&keymap, &all_bindings);
+// assert!(keymap.bindings().is_empty());
+// }
+
+// #[test]
+// fn keymap_with_ignored() {
+// let present_1 = Binding::new("ctrl-q", Present1 {}, None);
+// let present_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+// let present_3 = Binding::new("ctrl-e", Present3 {}, Some("editor"));
+// let keystroke_duplicate_to_1 = Binding::new("ctrl-q", Duplicate {}, None);
+// let full_duplicate_to_2 = Binding::new("ctrl-w", Present2 {}, Some("pane"));
+// let ignored_1 = Binding::new("ctrl-q", NoAction {}, None);
+// let ignored_2 = Binding::new("ctrl-w", NoAction {}, Some("pane"));
+// let ignored_3_with_other_context =
+// Binding::new("ctrl-e", NoAction {}, Some("other_context"));
+
+// let mut keymap = Keymap::default();
+
+// keymap.add_bindings([
+// ignored_1.clone(),
+// ignored_2.clone(),
+// ignored_3_with_other_context.clone(),
+// ]);
+// assert_absent(&keymap, &[&present_3]);
+// assert_disabled(
+// &keymap,
+// &[
+// &present_1,
+// &present_2,
+// &ignored_1,
+// &ignored_2,
+// &ignored_3_with_other_context,
+// ],
+// );
+// assert!(keymap.bindings().is_empty());
+// keymap.clear();
+
+// keymap.add_bindings([
+// present_1.clone(),
+// present_2.clone(),
+// present_3.clone(),
+// ignored_1.clone(),
+// ignored_2.clone(),
+// ignored_3_with_other_context.clone(),
+// ]);
+// assert_present(&keymap, &[(&present_3, "e")]);
+// assert_disabled(
+// &keymap,
+// &[
+// &present_1,
+// &present_2,
+// &ignored_1,
+// &ignored_2,
+// &ignored_3_with_other_context,
+// ],
+// );
+// keymap.clear();
+
+// keymap.add_bindings([
+// present_1.clone(),
+// present_2.clone(),
+// present_3.clone(),
+// ignored_1.clone(),
+// ]);
+// assert_present(&keymap, &[(&present_2, "w"), (&present_3, "e")]);
+// assert_disabled(&keymap, &[&present_1, &ignored_1]);
+// assert_absent(&keymap, &[&ignored_2, &ignored_3_with_other_context]);
+// keymap.clear();
+
+// keymap.add_bindings([
+// present_1.clone(),
+// present_2.clone(),
+// present_3.clone(),
+// keystroke_duplicate_to_1.clone(),
+// full_duplicate_to_2.clone(),
+// ignored_1.clone(),
+// ignored_2.clone(),
+// ignored_3_with_other_context.clone(),
+// ]);
+// assert_present(&keymap, &[(&present_3, "e")]);
+// assert_disabled(
+// &keymap,
+// &[
+// &present_1,
+// &present_2,
+// &keystroke_duplicate_to_1,
+// &full_duplicate_to_2,
+// &ignored_1,
+// &ignored_2,
+// &ignored_3_with_other_context,
+// ],
+// );
+// keymap.clear();
+// }
+
+// #[track_caller]
+// fn assert_present(keymap: &Keymap, expected_bindings: &[(&Binding, &str)]) {
+// let keymap_bindings = keymap.bindings();
+// assert_eq!(
+// expected_bindings.len(),
+// keymap_bindings.len(),
+// "Unexpected keymap bindings {keymap_bindings:?}"
+// );
+// for (i, (expected, expected_key)) in expected_bindings.iter().enumerate() {
+// assert!(
+// !keymap.binding_disabled(expected),
+// "{expected:?} should not be disabled as it was added into keymap for element {i}"
+// );
+// assert_eq!(
+// keymap
+// .bindings_for_action(expected.action().id())
+// .map(|binding| &binding.keystrokes)
+// .flatten()
+// .collect::<Vec<_>>(),
+// vec![&Keystroke {
+// ctrl: true,
+// alt: false,
+// shift: false,
+// cmd: false,
+// function: false,
+// key: expected_key.to_string(),
+// ime_key: None,
+// }],
+// "{expected:?} should have the expected keystroke with key '{expected_key}' in the keymap for element {i}"
+// );
+
+// let keymap_binding = &keymap_bindings[i];
+// assert_eq!(
+// keymap_binding.context_predicate, expected.context_predicate,
+// "Unexpected context predicate for keymap {i} element: {keymap_binding:?}"
+// );
+// assert_eq!(
+// keymap_binding.keystrokes, expected.keystrokes,
+// "Unexpected keystrokes for keymap {i} element: {keymap_binding:?}"
+// );
+// }
+// }
+
+// #[track_caller]
+// fn assert_absent(keymap: &Keymap, bindings: &[&Binding]) {
+// for binding in bindings.iter() {
+// assert!(
+// !keymap.binding_disabled(binding),
+// "{binding:?} should not be disabled in the keymap where was not added"
+// );
+// assert_eq!(
+// keymap.bindings_for_action(binding.action().id()).count(),
+// 0,
+// "{binding:?} should have no actions in the keymap where was not added"
+// );
+// }
+// }
+
+// #[track_caller]
+// fn assert_disabled(keymap: &Keymap, bindings: &[&Binding]) {
+// for binding in bindings.iter() {
+// assert!(
+// keymap.binding_disabled(binding),
+// "{binding:?} should be disabled in the keymap"
+// );
+// assert_eq!(
+// keymap.bindings_for_action(binding.action().id()).count(),
+// 0,
+// "{binding:?} should have no actions in the keymap where it was disabled"
+// );
+// }
+// }
+// }
@@ -0,0 +1,325 @@
+use anyhow::{anyhow, Result};
+use collections::{HashMap, HashSet};
+use std::borrow::Cow;
+
+#[derive(Clone, Debug, Default, Eq, PartialEq)]
+pub struct KeymapContext {
+ set: HashSet<Cow<'static, str>>,
+ map: HashMap<Cow<'static, str>, Cow<'static, str>>,
+}
+
+impl KeymapContext {
+ pub fn new() -> Self {
+ KeymapContext {
+ set: HashSet::default(),
+ map: HashMap::default(),
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.set.clear();
+ self.map.clear();
+ }
+
+ pub fn extend(&mut self, other: &Self) {
+ for v in &other.set {
+ self.set.insert(v.clone());
+ }
+ for (k, v) in &other.map {
+ self.map.insert(k.clone(), v.clone());
+ }
+ }
+
+ pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
+ self.set.insert(identifier.into());
+ }
+
+ pub fn add_key<S1: Into<Cow<'static, str>>, S2: Into<Cow<'static, str>>>(
+ &mut self,
+ key: S1,
+ value: S2,
+ ) {
+ self.map.insert(key.into(), value.into());
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub enum KeymapContextPredicate {
+ Identifier(String),
+ Equal(String, String),
+ NotEqual(String, String),
+ Child(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
+ Not(Box<KeymapContextPredicate>),
+ And(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
+ Or(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
+}
+
+impl KeymapContextPredicate {
+ pub fn parse(source: &str) -> Result<Self> {
+ let source = Self::skip_whitespace(source);
+ let (predicate, rest) = Self::parse_expr(source, 0)?;
+ if let Some(next) = rest.chars().next() {
+ Err(anyhow!("unexpected character {next:?}"))
+ } else {
+ Ok(predicate)
+ }
+ }
+
+ pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
+ let Some(context) = contexts.first() else {
+ return false;
+ };
+ match self {
+ Self::Identifier(name) => (&context.set).contains(name.as_str()),
+ Self::Equal(left, right) => context
+ .map
+ .get(left.as_str())
+ .map(|value| value == right)
+ .unwrap_or(false),
+ Self::NotEqual(left, right) => context
+ .map
+ .get(left.as_str())
+ .map(|value| value != right)
+ .unwrap_or(true),
+ Self::Not(pred) => !pred.eval(contexts),
+ Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts),
+ Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
+ Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
+ }
+ }
+
+ fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
+ type Op =
+ fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
+
+ let (mut predicate, rest) = Self::parse_primary(source)?;
+ source = rest;
+
+ 'parse: loop {
+ for (operator, precedence, constructor) in [
+ (">", PRECEDENCE_CHILD, Self::new_child as Op),
+ ("&&", PRECEDENCE_AND, Self::new_and as Op),
+ ("||", PRECEDENCE_OR, Self::new_or as Op),
+ ("==", PRECEDENCE_EQ, Self::new_eq as Op),
+ ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
+ ] {
+ if source.starts_with(operator) && precedence >= min_precedence {
+ source = Self::skip_whitespace(&source[operator.len()..]);
+ let (right, rest) = Self::parse_expr(source, precedence + 1)?;
+ predicate = constructor(predicate, right)?;
+ source = rest;
+ continue 'parse;
+ }
+ }
+ break;
+ }
+
+ Ok((predicate, source))
+ }
+
+ fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
+ let next = source
+ .chars()
+ .next()
+ .ok_or_else(|| anyhow!("unexpected eof"))?;
+ match next {
+ '(' => {
+ source = Self::skip_whitespace(&source[1..]);
+ let (predicate, rest) = Self::parse_expr(source, 0)?;
+ if rest.starts_with(')') {
+ source = Self::skip_whitespace(&rest[1..]);
+ Ok((predicate, source))
+ } else {
+ Err(anyhow!("expected a ')'"))
+ }
+ }
+ '!' => {
+ let source = Self::skip_whitespace(&source[1..]);
+ let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
+ Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
+ }
+ _ if next.is_alphanumeric() || next == '_' => {
+ let len = source
+ .find(|c: char| !(c.is_alphanumeric() || c == '_'))
+ .unwrap_or(source.len());
+ let (identifier, rest) = source.split_at(len);
+ source = Self::skip_whitespace(rest);
+ Ok((
+ KeymapContextPredicate::Identifier(identifier.into()),
+ source,
+ ))
+ }
+ _ => Err(anyhow!("unexpected character {next:?}")),
+ }
+ }
+
+ fn skip_whitespace(source: &str) -> &str {
+ let len = source
+ .find(|c: char| !c.is_whitespace())
+ .unwrap_or(source.len());
+ &source[len..]
+ }
+
+ fn new_or(self, other: Self) -> Result<Self> {
+ Ok(Self::Or(Box::new(self), Box::new(other)))
+ }
+
+ fn new_and(self, other: Self) -> Result<Self> {
+ Ok(Self::And(Box::new(self), Box::new(other)))
+ }
+
+ fn new_child(self, other: Self) -> Result<Self> {
+ Ok(Self::Child(Box::new(self), Box::new(other)))
+ }
+
+ fn new_eq(self, other: Self) -> Result<Self> {
+ if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
+ Ok(Self::Equal(left, right))
+ } else {
+ Err(anyhow!("operands must be identifiers"))
+ }
+ }
+
+ fn new_neq(self, other: Self) -> Result<Self> {
+ if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
+ Ok(Self::NotEqual(left, right))
+ } else {
+ Err(anyhow!("operands must be identifiers"))
+ }
+ }
+}
+
+const PRECEDENCE_CHILD: u32 = 1;
+const PRECEDENCE_OR: u32 = 2;
+const PRECEDENCE_AND: u32 = 3;
+const PRECEDENCE_EQ: u32 = 4;
+const PRECEDENCE_NOT: u32 = 5;
+
+#[cfg(test)]
+mod tests {
+ use super::KeymapContextPredicate::{self, *};
+
+ #[test]
+ fn test_parse_identifiers() {
+ // Identifiers
+ assert_eq!(
+ KeymapContextPredicate::parse("abc12").unwrap(),
+ Identifier("abc12".into())
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("_1a").unwrap(),
+ Identifier("_1a".into())
+ );
+ }
+
+ #[test]
+ fn test_parse_negations() {
+ assert_eq!(
+ KeymapContextPredicate::parse("!abc").unwrap(),
+ Not(Box::new(Identifier("abc".into())))
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse(" ! ! abc").unwrap(),
+ Not(Box::new(Not(Box::new(Identifier("abc".into())))))
+ );
+ }
+
+ #[test]
+ fn test_parse_equality_operators() {
+ assert_eq!(
+ KeymapContextPredicate::parse("a == b").unwrap(),
+ Equal("a".into(), "b".into())
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("c!=d").unwrap(),
+ NotEqual("c".into(), "d".into())
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("c == !d")
+ .unwrap_err()
+ .to_string(),
+ "operands must be identifiers"
+ );
+ }
+
+ #[test]
+ fn test_parse_boolean_operators() {
+ assert_eq!(
+ KeymapContextPredicate::parse("a || b").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("a || !b && c").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(And(
+ Box::new(Not(Box::new(Identifier("b".into())))),
+ Box::new(Identifier("c".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
+ Or(
+ Box::new(And(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )),
+ Box::new(And(
+ Box::new(Identifier("c".into())),
+ Box::new(Identifier("d".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
+ Or(
+ Box::new(And(
+ Box::new(Equal("a".into(), "b".into())),
+ Box::new(Identifier("c".into()))
+ )),
+ Box::new(And(
+ Box::new(Equal("d".into(), "e".into())),
+ Box::new(Identifier("f".into()))
+ ))
+ )
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse("a && b && c && d").unwrap(),
+ And(
+ Box::new(And(
+ Box::new(And(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into()))
+ )),
+ Box::new(Identifier("c".into())),
+ )),
+ Box::new(Identifier("d".into()))
+ ),
+ );
+ }
+
+ #[test]
+ fn test_parse_parenthesized_expressions() {
+ assert_eq!(
+ KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
+ And(
+ Box::new(Identifier("a".into())),
+ Box::new(Or(
+ Box::new(Equal("b".into(), "c".into())),
+ Box::new(NotEqual("d".into(), "e".into())),
+ )),
+ ),
+ );
+ assert_eq!(
+ KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
+ Or(
+ Box::new(Identifier("a".into())),
+ Box::new(Identifier("b".into())),
+ )
+ );
+ }
+}
@@ -1,29 +1,54 @@
use anyhow::anyhow;
use serde::Deserialize;
+use smallvec::SmallVec;
use std::fmt::Write;
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
-pub struct Modifiers {
- pub control: bool,
- pub alt: bool,
- pub shift: bool,
- pub command: bool,
- pub function: bool,
-}
-
-impl Modifiers {
- pub fn modified(&self) -> bool {
- self.control || self.alt || self.shift || self.command || self.function
- }
-}
-
-#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Hash)]
+#[derive(Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
pub struct Keystroke {
- pub key: String,
pub modifiers: Modifiers,
+ /// key is the character printed on the key that was pressed
+ /// e.g. for option-s, key is "s"
+ pub key: String,
+ /// ime_key is the character inserted by the IME engine when that key was pressed.
+ /// e.g. for option-s, ime_key is "ร"
+ pub ime_key: Option<String>,
}
impl Keystroke {
+ // When matching a key we cannot know whether the user intended to type
+ // the ime_key or the key. On some non-US keyboards keys we use in our
+ // bindings are behind option (for example `$` is typed `alt-รง` on a Czech keyboard),
+ // and on some keyboards the IME handler converts a sequence of keys into a
+ // specific character (for example `"` is typed as `" space` on a brazillian keyboard).
+ pub fn match_candidates(&self) -> SmallVec<[Keystroke; 2]> {
+ let mut possibilities = SmallVec::new();
+ match self.ime_key.as_ref() {
+ None => possibilities.push(self.clone()),
+ Some(ime_key) => {
+ possibilities.push(Keystroke {
+ modifiers: Modifiers {
+ control: self.modifiers.control,
+ alt: false,
+ shift: false,
+ command: false,
+ function: false,
+ },
+ key: ime_key.to_string(),
+ ime_key: None,
+ });
+ possibilities.push(Keystroke {
+ ime_key: None,
+ ..self.clone()
+ });
+ }
+ }
+ possibilities
+ }
+
+ /// key syntax is:
+ /// [ctrl-][alt-][shift-][cmd-][fn-]key[->ime_key]
+ /// ime_key is only used for generating test events,
+ /// when matching a key with an ime_key set will be matched without it.
pub fn parse(source: &str) -> anyhow::Result<Self> {
let mut control = false;
let mut alt = false;
@@ -31,6 +56,7 @@ impl Keystroke {
let mut command = false;
let mut function = false;
let mut key = None;
+ let mut ime_key = None;
let mut components = source.split('-').peekable();
while let Some(component) = components.next() {
@@ -41,10 +67,14 @@ impl Keystroke {
"cmd" => command = true,
"fn" => function = true,
_ => {
- if let Some(component) = components.peek() {
- if component.is_empty() && source.ends_with('-') {
+ if let Some(next) = components.peek() {
+ if next.is_empty() && source.ends_with('-') {
key = Some(String::from("-"));
break;
+ } else if next.len() > 1 && next.starts_with('>') {
+ key = Some(String::from(component));
+ ime_key = Some(String::from(&next[1..]));
+ components.next();
} else {
return Err(anyhow!("Invalid keystroke `{}`", source));
}
@@ -66,40 +96,25 @@ impl Keystroke {
function,
},
key,
+ ime_key,
})
}
-
- pub fn modified(&self) -> bool {
- self.modifiers.modified()
- }
}
impl std::fmt::Display for Keystroke {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- let Modifiers {
- control,
- alt,
- shift,
- command,
- function,
- } = self.modifiers;
-
- if control {
+ if self.modifiers.control {
f.write_char('^')?;
}
- if alt {
- f.write_char('โ')?;
+ if self.modifiers.alt {
+ f.write_char('โฅ')?;
}
- if command {
+ if self.modifiers.command {
f.write_char('โ')?;
}
- if shift {
+ if self.modifiers.shift {
f.write_char('โง')?;
}
- if function {
- f.write_char('๐')?;
- }
-
let key = match self.key.as_str() {
"backspace" => 'โซ',
"up" => 'โ',
@@ -119,3 +134,18 @@ impl std::fmt::Display for Keystroke {
f.write_char(key)
}
}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, Default, Deserialize, Hash)]
+pub struct Modifiers {
+ pub control: bool,
+ pub alt: bool,
+ pub shift: bool,
+ pub command: bool,
+ pub function: bool,
+}
+
+impl Modifiers {
+ pub fn modified(&self) -> bool {
+ self.control || self.alt || self.shift || self.command || self.function
+ }
+}
@@ -325,6 +325,7 @@ unsafe fn parse_keystroke(native_event: id) -> Keystroke {
function,
},
key,
+ ime_key: None,
}
}
@@ -1043,6 +1043,7 @@ extern "C" fn handle_key_event(this: &Object, native_event: id, key_equivalent:
// we don't match cmd/fn because they don't seem to use IME
modifiers: Default::default(),
key: ime_text.clone().unwrap(),
+ ime_key: None, // todo!("handle IME key")
},
};
handled = callback(InputEvent::KeyDown(event_with_ime_text));
@@ -1203,6 +1204,7 @@ extern "C" fn cancel_operation(this: &Object, _sel: Sel, _sender: id) {
let keystroke = Keystroke {
modifiers: Default::default(),
key: ".".into(),
+ ime_key: None,
};
let event = InputEvent::KeyDown(KeyDownEvent {
keystroke: keystroke.clone(),
@@ -219,8 +219,8 @@ impl TextSystem {
Ok(lines)
}
- pub fn end_frame(&self) {
- self.line_layout_cache.end_frame()
+ pub fn start_frame(&self) {
+ self.line_layout_cache.start_frame()
}
pub fn line_wrapper(
@@ -167,7 +167,7 @@ impl LineLayoutCache {
}
}
- pub fn end_frame(&self) {
+ pub fn start_frame(&self) {
let mut prev_frame = self.prev_frame.lock();
let mut curr_frame = self.curr_frame.write();
std::mem::swap(&mut *prev_frame, &mut *curr_frame);
@@ -164,7 +164,7 @@ impl<V: Send + Sync + 'static> ViewObject for View<V> {
}
fn initialize(&mut self, cx: &mut WindowContext) -> AnyBox {
- cx.with_element_id(self.entity_id(), |cx| {
+ cx.with_element_id(self.entity_id(), |_global_id, cx| {
self.state.update(cx, |state, cx| {
let mut any_element = Box::new((self.render)(state, cx));
any_element.initialize(state, cx);
@@ -174,7 +174,7 @@ impl<V: Send + Sync + 'static> ViewObject for View<V> {
}
fn layout(&mut self, element: &mut AnyBox, cx: &mut WindowContext) -> LayoutId {
- cx.with_element_id(self.entity_id(), |cx| {
+ cx.with_element_id(self.entity_id(), |_global_id, cx| {
self.state.update(cx, |state, cx| {
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
element.layout(state, cx)
@@ -183,7 +183,7 @@ impl<V: Send + Sync + 'static> ViewObject for View<V> {
}
fn paint(&mut self, _: Bounds<Pixels>, element: &mut AnyBox, cx: &mut WindowContext) {
- cx.with_element_id(self.entity_id(), |cx| {
+ cx.with_element_id(self.entity_id(), |_global_id, cx| {
self.state.update(cx, |state, cx| {
let element = element.downcast_mut::<AnyElement<V>>().unwrap();
element.paint(state, None, cx);
@@ -1,12 +1,13 @@
use crate::{
- px, size, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace, BorrowAppContext,
- Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect, Element, EntityId,
- EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla, ImageData,
- InputEvent, IsZero, KeyListener, KeyMatch, Keystroke, LayoutId, MainThread, MainThreadOnly,
- MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform, PlatformAtlas, PlatformWindow, Point,
- PolychromeSprite, Quad, Reference, RenderGlyphParams, RenderImageParams, RenderSvgParams,
- ScaledPixels, SceneBuilder, Shadow, SharedString, Size, Style, Subscription, TaffyLayoutEngine,
- Task, Underline, UnderlineStyle, WeakHandle, WindowOptions, SUBPIXEL_VARIANTS,
+ px, size, Action, AnyBox, AnyView, AppContext, AsyncWindowContext, AvailableSpace,
+ BorrowAppContext, Bounds, BoxShadow, Context, Corners, DevicePixels, DisplayId, Edges, Effect,
+ Element, EntityId, EventEmitter, FocusEvent, FontId, GlobalElementId, GlyphId, Handle, Hsla,
+ ImageData, InputEvent, IsZero, KeyListener, KeyMatch, KeymapMatcher, Keystroke, LayoutId,
+ MainThread, MainThreadOnly, MonochromeSprite, MouseMoveEvent, Path, Pixels, Platform,
+ PlatformAtlas, PlatformWindow, Point, PolychromeSprite, Quad, Reference, RenderGlyphParams,
+ RenderImageParams, RenderSvgParams, ScaledPixels, SceneBuilder, Shadow, SharedString, Size,
+ Style, Subscription, TaffyLayoutEngine, Task, Underline, UnderlineStyle, WeakHandle,
+ WindowOptions, SUBPIXEL_VARIANTS,
};
use anyhow::Result;
use collections::HashMap;
@@ -46,7 +47,10 @@ pub enum DispatchPhase {
type AnyListener = Arc<dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) + Send + Sync + 'static>;
type AnyKeyListener = Arc<
- dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) -> Option<AnyBox> + Send + Sync + 'static,
+ dyn Fn(&dyn Any, DispatchPhase, &mut WindowContext) -> Option<Box<dyn Action>>
+ + Send
+ + Sync
+ + 'static,
>;
type AnyFocusListener = Arc<dyn Fn(&FocusEvent, &mut WindowContext) + Send + Sync + 'static>;
@@ -144,8 +148,10 @@ pub struct Window {
layout_engine: TaffyLayoutEngine,
pub(crate) root_view: Option<AnyView>,
pub(crate) element_id_stack: GlobalElementId,
- prev_element_states: HashMap<GlobalElementId, AnyBox>,
+ prev_frame_element_states: HashMap<GlobalElementId, AnyBox>,
element_states: HashMap<GlobalElementId, AnyBox>,
+ prev_frame_key_matchers: HashMap<GlobalElementId, KeymapMatcher>,
+ key_matchers: HashMap<GlobalElementId, KeymapMatcher>,
z_index_stack: StackingOrder,
content_mask_stack: Vec<ContentMask<Pixels>>,
mouse_listeners: HashMap<TypeId, Vec<(StackingOrder, AnyListener)>>,
@@ -217,8 +223,10 @@ impl Window {
layout_engine: TaffyLayoutEngine::new(),
root_view: None,
element_id_stack: GlobalElementId::default(),
- prev_element_states: HashMap::default(),
+ prev_frame_element_states: HashMap::default(),
element_states: HashMap::default(),
+ prev_frame_key_matchers: HashMap::default(),
+ key_matchers: HashMap::default(),
z_index_stack: StackingOrder(SmallVec::new()),
content_mask_stack: Vec::new(),
mouse_listeners: HashMap::default(),
@@ -767,7 +775,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
cx.window.root_view = Some(root_view);
let scene = cx.window.scene_builder.build();
- cx.end_frame();
cx.run_on_main(view, |_, cx| {
cx.window
@@ -797,13 +804,28 @@ impl<'a, 'w> WindowContext<'a, 'w> {
}
fn start_frame(&mut self) {
- // Make the current element states the previous, and then clear the current.
- // The empty element states map will be populated for any element states we
- // reference during the upcoming frame.
+ self.text_system().start_frame();
+
let window = &mut *self.window;
- mem::swap(&mut window.element_states, &mut window.prev_element_states);
+
+ // Move the current frame element states to the previous frame.
+ // The new empty element states map will be populated for any element states we
+ // reference during the upcoming frame.
+ mem::swap(
+ &mut window.element_states,
+ &mut window.prev_frame_element_states,
+ );
window.element_states.clear();
+ // Make the current key matchers the previous, and then clear the current.
+ // An empty key matcher map will be created for every identified element in the
+ // upcoming frame.
+ mem::swap(
+ &mut window.key_matchers,
+ &mut window.prev_frame_key_matchers,
+ );
+ window.key_matchers.clear();
+
// Clear mouse event listeners, because elements add new element listeners
// when the upcoming frame is painted.
window.mouse_listeners.values_mut().for_each(Vec::clear);
@@ -816,10 +838,6 @@ impl<'a, 'w> WindowContext<'a, 'w> {
window.key_events_enabled = true;
}
- fn end_frame(&mut self) {
- self.text_system().end_frame();
- }
-
fn dispatch_event(&mut self, event: InputEvent) -> bool {
if let Some(any_mouse_event) = event.mouse_event() {
if let Some(MouseMoveEvent { position, .. }) = any_mouse_event.downcast_ref() {
@@ -905,15 +923,32 @@ impl<'a, 'w> WindowContext<'a, 'w> {
true
}
- pub fn match_keystroke(&mut self, element_id: &ElementId, keystroke: &Keystroke) -> KeyMatch {
- todo!();
+ pub fn match_keystroke(
+ &mut self,
+ element_id: &GlobalElementId,
+ keystroke: &Keystroke,
+ ) -> KeyMatch {
+ let key_match = self
+ .window
+ .key_matchers
+ .get_mut(element_id)
+ .unwrap()
+ .match_keystroke(keystroke, &[]);
+
+ if key_match.is_some() {
+ for matcher in self.window.key_matchers.values_mut() {
+ matcher.clear_pending();
+ }
+ }
+
+ key_match
}
- fn dispatch_action(&mut self, action: AnyBox, listeners: &[(TypeId, AnyKeyListener)]) {
+ fn dispatch_action(&mut self, action: Box<dyn Action>, listeners: &[(TypeId, AnyKeyListener)]) {
let action_type = action.type_id();
for (event_type, listener) in listeners {
if action_type == *event_type {
- listener(action.as_ref(), DispatchPhase::Capture, self);
+ listener(action.as_any(), DispatchPhase::Capture, self);
if !self.window.propagate {
break;
}
@@ -923,7 +958,7 @@ impl<'a, 'w> WindowContext<'a, 'w> {
if self.window.propagate {
for (event_type, listener) in listeners.iter().rev() {
if action_type == *event_type {
- listener(action.as_ref(), DispatchPhase::Capture, self);
+ listener(action.as_any(), DispatchPhase::Capture, self);
if !self.window.propagate {
break;
}
@@ -998,10 +1033,11 @@ pub trait BorrowWindow: BorrowAppContext {
fn with_element_id<R>(
&mut self,
id: impl Into<ElementId>,
- f: impl FnOnce(&mut Self) -> R,
+ f: impl FnOnce(GlobalElementId, &mut Self) -> R,
) -> R {
self.window_mut().element_id_stack.push(id.into());
- let result = f(self);
+ let global_id = self.window_mut().element_id_stack.clone();
+ let result = f(global_id, self);
self.window_mut().element_id_stack.pop();
result
}
@@ -1023,13 +1059,12 @@ pub trait BorrowWindow: BorrowAppContext {
id: ElementId,
f: impl FnOnce(Option<S>, &mut Self) -> (R, S),
) -> R {
- self.with_element_id(id, |cx| {
- let global_id = cx.window_mut().element_id_stack.clone();
+ self.with_element_id(id, |global_id, cx| {
if let Some(any) = cx
.window_mut()
.element_states
.remove(&global_id)
- .or_else(|| cx.window_mut().prev_element_states.remove(&global_id))
+ .or_else(|| cx.window_mut().prev_frame_element_states.remove(&global_id))
{
// Using the extra inner option to avoid needing to reallocate a new box.
let mut state_box = any