Detailed changes
@@ -1,16 +1,23 @@
+use crate::SharedString;
use anyhow::{anyhow, Result};
use collections::{HashMap, HashSet};
-use std::borrow::Cow;
+use std::any::Any;
+
+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;
+}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
-pub struct KeymapContext {
- set: HashSet<Cow<'static, str>>,
- map: HashMap<Cow<'static, str>, Cow<'static, str>>,
+pub struct ActionContext {
+ set: HashSet<SharedString>,
+ map: HashMap<SharedString, SharedString>,
}
-impl KeymapContext {
+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())),
@@ -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 {
@@ -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::*;
@@ -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())
@@ -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,
@@ -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
@@ -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::*;