Checkpoint

This commit is contained in:
Antonio Scandurra
2023-10-19 19:51:05 +02:00
parent 93ff79febf
commit 90d34c1251
7 changed files with 98 additions and 91 deletions
@@ -1,16 +1,23 @@
use crate::SharedString;
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet};
use std::borrow::Cow;
use std::any::Any;
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct KeymapContext {
set: HashSet<Cow<'static, str>>,
map: HashMap<Cow<'static, str>, Cow<'static, str>>,
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;
}
impl KeymapContext {
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct ActionContext {
set: HashSet<SharedString>,
map: HashMap<SharedString, SharedString>,
}
impl ActionContext {
pub fn new() -> Self {
KeymapContext {
ActionContext {
set: HashSet::default(),
map: HashMap::default(),
}
@@ -30,31 +37,27 @@ impl KeymapContext {
}
}
pub fn add_identifier<I: Into<Cow<'static, str>>>(&mut self, identifier: I) {
pub fn add_identifier<I: Into<SharedString>>(&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,
) {
pub fn add_key<S1: Into<SharedString>, S2: Into<SharedString>>(&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>),
pub enum ActionContextPredicate {
Identifier(SharedString),
Equal(SharedString, SharedString),
NotEqual(SharedString, SharedString),
Child(Box<ActionContextPredicate>, Box<ActionContextPredicate>),
Not(Box<ActionContextPredicate>),
And(Box<ActionContextPredicate>, Box<ActionContextPredicate>),
Or(Box<ActionContextPredicate>, Box<ActionContextPredicate>),
}
impl KeymapContextPredicate {
impl ActionContextPredicate {
pub fn parse(source: &str) -> Result<Self> {
let source = Self::skip_whitespace(source);
let (predicate, rest) = Self::parse_expr(source, 0)?;
@@ -65,20 +68,20 @@ impl KeymapContextPredicate {
}
}
pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
pub fn eval(&self, contexts: &[ActionContext]) -> bool {
let Some(context) = contexts.first() else {
return false;
};
match self {
Self::Identifier(name) => (&context.set).contains(name.as_str()),
Self::Identifier(name) => context.set.contains(&name),
Self::Equal(left, right) => context
.map
.get(left.as_str())
.get(&left)
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.map
.get(left.as_str())
.get(&left)
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
@@ -90,7 +93,7 @@ impl KeymapContextPredicate {
fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
type Op =
fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
fn(ActionContextPredicate, ActionContextPredicate) -> Result<ActionContextPredicate>;
let (mut predicate, rest) = Self::parse_primary(source)?;
source = rest;
@@ -136,7 +139,7 @@ impl KeymapContextPredicate {
'!' => {
let source = Self::skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
Ok((ActionContextPredicate::Not(Box::new(predicate)), source))
}
_ if next.is_alphanumeric() || next == '_' => {
let len = source
@@ -145,7 +148,7 @@ impl KeymapContextPredicate {
let (identifier, rest) = source.split_at(len);
source = Self::skip_whitespace(rest);
Ok((
KeymapContextPredicate::Identifier(identifier.into()),
ActionContextPredicate::Identifier(identifier.to_string().into()),
source,
))
}
@@ -197,17 +200,17 @@ const PRECEDENCE_NOT: u32 = 5;
#[cfg(test)]
mod tests {
use super::KeymapContextPredicate::{self, *};
use super::ActionContextPredicate::{self, *};
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
KeymapContextPredicate::parse("abc12").unwrap(),
ActionContextPredicate::parse("abc12").unwrap(),
Identifier("abc12".into())
);
assert_eq!(
KeymapContextPredicate::parse("_1a").unwrap(),
ActionContextPredicate::parse("_1a").unwrap(),
Identifier("_1a".into())
);
}
@@ -215,11 +218,11 @@ mod tests {
#[test]
fn test_parse_negations() {
assert_eq!(
KeymapContextPredicate::parse("!abc").unwrap(),
ActionContextPredicate::parse("!abc").unwrap(),
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
KeymapContextPredicate::parse(" ! ! abc").unwrap(),
ActionContextPredicate::parse(" ! ! abc").unwrap(),
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
@@ -227,15 +230,15 @@ mod tests {
#[test]
fn test_parse_equality_operators() {
assert_eq!(
KeymapContextPredicate::parse("a == b").unwrap(),
ActionContextPredicate::parse("a == b").unwrap(),
Equal("a".into(), "b".into())
);
assert_eq!(
KeymapContextPredicate::parse("c!=d").unwrap(),
ActionContextPredicate::parse("c!=d").unwrap(),
NotEqual("c".into(), "d".into())
);
assert_eq!(
KeymapContextPredicate::parse("c == !d")
ActionContextPredicate::parse("c == !d")
.unwrap_err()
.to_string(),
"operands must be identifiers"
@@ -245,14 +248,14 @@ mod tests {
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
KeymapContextPredicate::parse("a || b").unwrap(),
ActionContextPredicate::parse("a || b").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
KeymapContextPredicate::parse("a || !b && c").unwrap(),
ActionContextPredicate::parse("a || !b && c").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(And(
@@ -262,7 +265,7 @@ mod tests {
)
);
assert_eq!(
KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
ActionContextPredicate::parse("a && b || c&&d").unwrap(),
Or(
Box::new(And(
Box::new(Identifier("a".into())),
@@ -275,7 +278,7 @@ mod tests {
)
);
assert_eq!(
KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
ActionContextPredicate::parse("a == b && c || d == e && f").unwrap(),
Or(
Box::new(And(
Box::new(Equal("a".into(), "b".into())),
@@ -288,7 +291,7 @@ mod tests {
)
);
assert_eq!(
KeymapContextPredicate::parse("a && b && c && d").unwrap(),
ActionContextPredicate::parse("a && b && c && d").unwrap(),
And(
Box::new(And(
Box::new(And(
@@ -305,7 +308,7 @@ mod tests {
#[test]
fn test_parse_parenthesized_expressions() {
assert_eq!(
KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
ActionContextPredicate::parse("a && (b == c || d != e)").unwrap(),
And(
Box::new(Identifier("a".into())),
Box::new(Or(
@@ -315,7 +318,7 @@ mod tests {
),
);
assert_eq!(
KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
ActionContextPredicate::parse(" ( a || b ) ").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
+14 -4
View File
@@ -10,14 +10,14 @@ use smallvec::SmallVec;
use crate::{
current_platform, image_cache::ImageCache, AssetSource, Context, DisplayId, Executor,
FocusEvent, FocusHandle, FocusId, LayoutId, MainThread, MainThreadOnly, Platform,
SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View, Window,
WindowContext, WindowHandle, WindowId,
FocusEvent, FocusHandle, FocusId, KeyBinding, Keymap, LayoutId, MainThread, MainThreadOnly,
Platform, SubscriberSet, SvgRenderer, Task, TextStyle, TextStyleRefinement, TextSystem, View,
Window, WindowContext, WindowHandle, WindowId,
};
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet, VecDeque};
use futures::Future;
use parking_lot::Mutex;
use parking_lot::{Mutex, RwLock};
use slotmap::SlotMap;
use std::{
any::{type_name, Any, TypeId},
@@ -67,6 +67,7 @@ impl App {
unit_entity,
entities,
windows: SlotMap::with_key(),
keymap: Arc::new(RwLock::new(Keymap::default())),
pending_notifications: Default::default(),
pending_effects: Default::default(),
observers: SubscriberSet::new(),
@@ -111,6 +112,7 @@ pub struct AppContext {
pub(crate) unit_entity: Handle<()>,
pub(crate) entities: EntityMap,
pub(crate) windows: SlotMap<WindowId, Option<Window>>,
keymap: Arc<RwLock<Keymap>>,
pub(crate) pending_notifications: HashSet<EntityId>,
pending_effects: VecDeque<Effect>,
pub(crate) observers: SubscriberSet<EntityId, Handler>,
@@ -403,6 +405,14 @@ impl AppContext {
pub(crate) fn pop_text_style(&mut self) {
self.text_style_stack.pop();
}
pub fn bind_keys(&mut self, bindings: impl IntoIterator<Item = KeyBinding>) {
self.keymap.write().add_bindings(bindings);
let window_ids = self.windows.keys().collect::<SmallVec<[_; 8]>>();
for window_id in window_ids {
self.update_window(window_id, |cx| cx.notify()).unwrap();
}
}
}
impl Context for AppContext {
+2
View File
@@ -1,3 +1,4 @@
mod action;
mod active;
mod app;
mod assets;
@@ -24,6 +25,7 @@ mod util;
mod view;
mod window;
pub use action::*;
pub use active::*;
pub use anyhow::Result;
pub use app::*;
+8 -8
View File
@@ -1,21 +1,21 @@
use crate::{Action, KeyMatch, KeymapContext, KeymapContextPredicate, Keystroke};
use crate::{Action, ActionContext, ActionContextPredicate, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
pub struct Binding {
pub struct KeyBinding {
action: Box<dyn Action>,
pub(super) keystrokes: SmallVec<[Keystroke; 2]>,
pub(super) context_predicate: Option<KeymapContextPredicate>,
pub(super) context_predicate: Option<ActionContextPredicate>,
}
impl Binding {
impl KeyBinding {
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)?)
Some(ActionContextPredicate::parse(context)?)
} else {
None
};
@@ -32,7 +32,7 @@ impl Binding {
})
}
pub fn matches_context(&self, contexts: &[KeymapContext]) -> bool {
pub fn matches_context(&self, contexts: &[ActionContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
@@ -42,7 +42,7 @@ impl Binding {
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
contexts: &[KeymapContext],
contexts: &[ActionContext],
) -> KeyMatch {
if self.keystrokes.as_ref().starts_with(&pending_keystrokes)
&& self.matches_context(contexts)
@@ -61,7 +61,7 @@ impl Binding {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[KeymapContext],
contexts: &[ActionContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
if self.action.partial_eq(action) && self.matches_context(contexts) {
Some(self.keystrokes.clone())
+8 -8
View File
@@ -1,4 +1,4 @@
use crate::{Binding, KeymapContextPredicate, Keystroke};
use crate::{ActionContextPredicate, KeyBinding, Keystroke};
use collections::HashSet;
use smallvec::SmallVec;
use std::{any::TypeId, collections::HashMap};
@@ -8,14 +8,14 @@ pub struct KeymapVersion(usize);
#[derive(Default)]
pub struct Keymap {
bindings: Vec<Binding>,
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeymapContextPredicate>>>,
disabled_keystrokes: HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<ActionContextPredicate>>>,
version: KeymapVersion,
}
impl Keymap {
pub fn new(bindings: Vec<Binding>) -> Self {
pub fn new(bindings: Vec<KeyBinding>) -> Self {
let mut this = Self::default();
this.add_bindings(bindings);
this
@@ -25,7 +25,7 @@ impl Keymap {
self.version
}
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ Binding> {
pub fn bindings_for_action(&self, action_id: TypeId) -> impl Iterator<Item = &'_ KeyBinding> {
self.binding_indices_by_action_id
.get(&action_id)
.map(SmallVec::as_slice)
@@ -35,7 +35,7 @@ impl Keymap {
.filter(|binding| !self.binding_disabled(binding))
}
pub fn add_bindings<T: IntoIterator<Item = Binding>>(&mut self, bindings: T) {
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
// todo!("no action")
// let no_action_id = (NoAction {}).id();
let mut new_bindings = Vec::new();
@@ -87,14 +87,14 @@ impl Keymap {
self.version.0 += 1;
}
pub fn bindings(&self) -> Vec<&Binding> {
pub fn bindings(&self) -> Vec<&KeyBinding> {
self.bindings
.iter()
.filter(|binding| !self.binding_disabled(binding))
.collect()
}
fn binding_disabled(&self, binding: &Binding) -> bool {
fn binding_disabled(&self, binding: &KeyBinding) -> bool {
match self.disabled_keystrokes.get(&binding.keystrokes) {
Some(disabled_predicates) => disabled_predicates.contains(&binding.context_predicate),
None => false,
+19 -25
View File
@@ -1,13 +1,7 @@
use crate::{Keymap, KeymapContext, KeymapVersion, Keystroke};
use crate::{Action, ActionContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::RwLock;
use smallvec::SmallVec;
use std::{any::Any, sync::Arc};
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;
}
use std::sync::Arc;
pub struct KeyMatcher {
pending_keystrokes: Vec<Keystroke>,
@@ -50,7 +44,7 @@ impl KeyMatcher {
pub fn match_keystroke(
&mut self,
keystroke: &Keystroke,
context_stack: &[KeymapContext],
context_stack: &[ActionContext],
) -> KeyMatch {
let keymap = self.keymap.read();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
@@ -92,7 +86,7 @@ impl KeyMatcher {
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[KeymapContext],
contexts: &[ActionContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.read()
@@ -120,7 +114,7 @@ impl KeyMatch {
// use anyhow::Result;
// use serde::Deserialize;
// use crate::{actions, impl_actions, keymap_matcher::KeymapContext};
// use crate::{actions, impl_actions, keymap_matcher::ActionContext};
// use super::*;
@@ -128,10 +122,10 @@ impl KeyMatch {
// fn test_keymap_and_view_ordering() -> Result<()> {
// actions!(test, [EditorAction, ProjectPanelAction]);
// let mut editor = KeymapContext::default();
// let mut editor = ActionContext::default();
// editor.add_identifier("Editor");
// let mut project_panel = KeymapContext::default();
// let mut project_panel = ActionContext::default();
// project_panel.add_identifier("ProjectPanel");
// // Editor 'deeper' in than project panel
@@ -160,10 +154,10 @@ impl KeyMatch {
// fn test_push_keystroke() -> Result<()> {
// actions!(test, [B, AB, C, D, DA, E, EF]);
// let mut context1 = KeymapContext::default();
// let mut context1 = ActionContext::default();
// context1.add_identifier("1");
// let mut context2 = KeymapContext::default();
// let mut context2 = ActionContext::default();
// context2.add_identifier("2");
// let dispatch_path = vec![(2, context2), (1, context1)];
@@ -300,27 +294,27 @@ impl KeyMatch {
// fn test_context_predicate_eval() {
// let predicate = KeymapContextPredicate::parse("a && b || c == d").unwrap();
// let mut context = KeymapContext::default();
// let mut context = ActionContext::default();
// context.add_identifier("a");
// assert!(!predicate.eval(&[context]));
// let mut context = KeymapContext::default();
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_identifier("b");
// assert!(predicate.eval(&[context]));
// let mut context = KeymapContext::default();
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_key("c", "x");
// assert!(!predicate.eval(&[context]));
// let mut context = KeymapContext::default();
// let mut context = ActionContext::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()]));
// assert!(predicate.eval(&[ActionContext::default()]));
// }
// #[test]
@@ -355,8 +349,8 @@ impl KeyMatch {
// assert!(!predicate.eval(&contexts[5..]));
// assert!(!predicate.eval(&contexts[6..]));
// fn context_set(names: &[&str]) -> KeymapContext {
// let mut keymap = KeymapContext::new();
// fn context_set(names: &[&str]) -> ActionContext {
// let mut keymap = ActionContext::new();
// names
// .iter()
// .for_each(|name| keymap.add_identifier(name.to_string()));
@@ -386,10 +380,10 @@ impl KeyMatch {
// Binding::new("ctrl-`", Backtick, Some("a")),
// ]);
// let mut context_a = KeymapContext::default();
// let mut context_a = ActionContext::default();
// context_a.add_identifier("a");
// let mut context_b = KeymapContext::default();
// let mut context_b = ActionContext::default();
// context_b.add_identifier("b");
// let mut matcher = KeymapMatcher::new(keymap);
@@ -434,7 +428,7 @@ impl KeyMatch {
// );
// matcher.clear_pending();
// let mut context_c = KeymapContext::default();
// let mut context_c = ActionContext::default();
// context_c.add_identifier("c");
// // Pending keystrokes are maintained per-view
-2
View File
@@ -1,9 +1,7 @@
mod binding;
mod keymap;
mod keymap_context;
mod matcher;
pub use binding::*;
pub use keymap::*;
pub use keymap_context::*;
pub use matcher::*;