Remove 2 suffix from gpui

Co-authored-by: Mikayla <mikayla@zed.dev>
This commit is contained in:
Max Brunsfeld 2024-01-03 12:59:39 -08:00
parent 3c81dda8e2
commit f5ba22659b
225 changed files with 8511 additions and 41063 deletions

View file

@ -0,0 +1,90 @@
use crate::{Action, KeyBindingContextPredicate, KeyContext, KeyMatch, Keystroke};
use anyhow::Result;
use smallvec::SmallVec;
pub struct KeyBinding {
pub(crate) action: Box<dyn Action>,
pub(crate) keystrokes: SmallVec<[Keystroke; 2]>,
pub(crate) context_predicate: Option<KeyBindingContextPredicate>,
}
impl Clone for KeyBinding {
fn clone(&self) -> Self {
KeyBinding {
action: self.action.boxed_clone(),
keystrokes: self.keystrokes.clone(),
context_predicate: self.context_predicate.clone(),
}
}
}
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(KeyBindingContextPredicate::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: &[KeyContext]) -> bool {
self.context_predicate
.as_ref()
.map(|predicate| predicate.eval(contexts))
.unwrap_or(true)
}
pub fn match_keystrokes(
&self,
pending_keystrokes: &[Keystroke],
contexts: &[KeyContext],
) -> 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(vec![self.action.boxed_clone()])
} else {
KeyMatch::Pending
}
} else {
KeyMatch::None
}
}
pub fn keystrokes_for_action(
&self,
action: &dyn Action,
contexts: &[KeyContext],
) -> 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()
}
}

View file

@ -0,0 +1,452 @@
use crate::SharedString;
use anyhow::{anyhow, Result};
use smallvec::SmallVec;
use std::fmt;
#[derive(Clone, Default, Eq, PartialEq, Hash)]
pub struct KeyContext(SmallVec<[ContextEntry; 1]>);
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
struct ContextEntry {
key: SharedString,
value: Option<SharedString>,
}
impl<'a> TryFrom<&'a str> for KeyContext {
type Error = anyhow::Error;
fn try_from(value: &'a str) -> Result<Self> {
Self::parse(value)
}
}
impl KeyContext {
pub fn parse(source: &str) -> Result<Self> {
let mut context = Self::default();
let source = skip_whitespace(source);
Self::parse_expr(source, &mut context)?;
Ok(context)
}
fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
if source.is_empty() {
return Ok(());
}
let key = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[key.len()..]);
if let Some(suffix) = source.strip_prefix('=') {
source = skip_whitespace(suffix);
let value = source
.chars()
.take_while(|c| is_identifier_char(*c))
.collect::<String>();
source = skip_whitespace(&source[value.len()..]);
context.set(key, value);
} else {
context.add(key);
}
Self::parse_expr(source, context)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn clear(&mut self) {
self.0.clear();
}
pub fn extend(&mut self, other: &Self) {
for entry in &other.0 {
if !self.contains(&entry.key) {
self.0.push(entry.clone());
}
}
}
pub fn add<I: Into<SharedString>>(&mut self, identifier: I) {
let key = identifier.into();
if !self.contains(&key) {
self.0.push(ContextEntry { key, value: None })
}
}
pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
let key = key.into();
if !self.contains(&key) {
self.0.push(ContextEntry {
key,
value: Some(value.into()),
})
}
}
pub fn contains(&self, key: &str) -> bool {
self.0.iter().any(|entry| entry.key.as_ref() == key)
}
pub fn get(&self, key: &str) -> Option<&SharedString> {
self.0
.iter()
.find(|entry| entry.key.as_ref() == key)?
.value
.as_ref()
}
}
impl fmt::Debug for KeyContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut entries = self.0.iter().peekable();
while let Some(entry) = entries.next() {
if let Some(ref value) = entry.value {
write!(f, "{}={}", entry.key, value)?;
} else {
write!(f, "{}", entry.key)?;
}
if entries.peek().is_some() {
write!(f, " ")?;
}
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub enum KeyBindingContextPredicate {
Identifier(SharedString),
Equal(SharedString, SharedString),
NotEqual(SharedString, SharedString),
Child(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
Not(Box<KeyBindingContextPredicate>),
And(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
Or(
Box<KeyBindingContextPredicate>,
Box<KeyBindingContextPredicate>,
),
}
impl KeyBindingContextPredicate {
pub fn parse(source: &str) -> Result<Self> {
let source = 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: &[KeyContext]) -> bool {
let Some(context) = contexts.last() else {
return false;
};
match self {
Self::Identifier(name) => context.contains(name),
Self::Equal(left, right) => context
.get(left)
.map(|value| value == right)
.unwrap_or(false),
Self::NotEqual(left, right) => context
.get(left)
.map(|value| value != right)
.unwrap_or(true),
Self::Not(pred) => !pred.eval(contexts),
Self::Child(parent, child) => {
parent.eval(&contexts[..contexts.len() - 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(
KeyBindingContextPredicate,
KeyBindingContextPredicate,
) -> Result<KeyBindingContextPredicate>;
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 = 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 = skip_whitespace(&source[1..]);
let (predicate, rest) = Self::parse_expr(source, 0)?;
if rest.starts_with(')') {
source = skip_whitespace(&rest[1..]);
Ok((predicate, source))
} else {
Err(anyhow!("expected a ')'"))
}
}
'!' => {
let source = skip_whitespace(&source[1..]);
let (predicate, source) = Self::parse_expr(source, PRECEDENCE_NOT)?;
Ok((KeyBindingContextPredicate::Not(Box::new(predicate)), source))
}
_ if is_identifier_char(next) => {
let len = source
.find(|c: char| !is_identifier_char(c))
.unwrap_or(source.len());
let (identifier, rest) = source.split_at(len);
source = skip_whitespace(rest);
Ok((
KeyBindingContextPredicate::Identifier(identifier.to_string().into()),
source,
))
}
_ => Err(anyhow!("unexpected character {next:?}")),
}
}
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;
fn is_identifier_char(c: char) -> bool {
c.is_alphanumeric() || c == '_' || c == '-'
}
fn skip_whitespace(source: &str) -> &str {
let len = source
.find(|c: char| !c.is_whitespace())
.unwrap_or(source.len());
&source[len..]
}
#[cfg(test)]
mod tests {
use super::*;
use crate as gpui;
use KeyBindingContextPredicate::*;
#[test]
fn test_actions_definition() {
{
actions!(test, [A, B, C, D, E, F, G]);
}
{
actions!(
test,
[
A,
B,
C,
D,
E,
F,
G, // Don't wrap, test the trailing comma
]
);
}
}
#[test]
fn test_parse_context() {
let mut expected = KeyContext::default();
expected.add("baz");
expected.set("foo", "bar");
assert_eq!(KeyContext::parse("baz foo=bar").unwrap(), expected);
assert_eq!(KeyContext::parse("baz foo = bar").unwrap(), expected);
assert_eq!(
KeyContext::parse(" baz foo = bar baz").unwrap(),
expected
);
assert_eq!(KeyContext::parse(" baz foo = bar").unwrap(), expected);
}
#[test]
fn test_parse_identifiers() {
// Identifiers
assert_eq!(
KeyBindingContextPredicate::parse("abc12").unwrap(),
Identifier("abc12".into())
);
assert_eq!(
KeyBindingContextPredicate::parse("_1a").unwrap(),
Identifier("_1a".into())
);
}
#[test]
fn test_parse_negations() {
assert_eq!(
KeyBindingContextPredicate::parse("!abc").unwrap(),
Not(Box::new(Identifier("abc".into())))
);
assert_eq!(
KeyBindingContextPredicate::parse(" ! ! abc").unwrap(),
Not(Box::new(Not(Box::new(Identifier("abc".into())))))
);
}
#[test]
fn test_parse_equality_operators() {
assert_eq!(
KeyBindingContextPredicate::parse("a == b").unwrap(),
Equal("a".into(), "b".into())
);
assert_eq!(
KeyBindingContextPredicate::parse("c!=d").unwrap(),
NotEqual("c".into(), "d".into())
);
assert_eq!(
KeyBindingContextPredicate::parse("c == !d")
.unwrap_err()
.to_string(),
"operands must be identifiers"
);
}
#[test]
fn test_parse_boolean_operators() {
assert_eq!(
KeyBindingContextPredicate::parse("a || b").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into()))
)
);
assert_eq!(
KeyBindingContextPredicate::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!(
KeyBindingContextPredicate::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!(
KeyBindingContextPredicate::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!(
KeyBindingContextPredicate::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!(
KeyBindingContextPredicate::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!(
KeyBindingContextPredicate::parse(" ( a || b ) ").unwrap(),
Or(
Box::new(Identifier("a".into())),
Box::new(Identifier("b".into())),
)
);
}
}

View file

@ -0,0 +1,400 @@
use crate::{KeyBinding, KeyBindingContextPredicate, Keystroke, NoAction};
use collections::HashSet;
use smallvec::SmallVec;
use std::{
any::{Any, TypeId},
collections::HashMap,
};
#[derive(Copy, Clone, Eq, PartialEq, Default)]
pub struct KeymapVersion(usize);
#[derive(Default)]
pub struct Keymap {
bindings: Vec<KeyBinding>,
binding_indices_by_action_id: HashMap<TypeId, SmallVec<[usize; 3]>>,
disabled_keystrokes:
HashMap<SmallVec<[Keystroke; 2]>, HashSet<Option<KeyBindingContextPredicate>>>,
version: KeymapVersion,
}
impl Keymap {
pub fn new(bindings: Vec<KeyBinding>) -> Self {
let mut this = Self::default();
this.add_bindings(bindings);
this
}
pub fn version(&self) -> KeymapVersion {
self.version
}
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)
.unwrap_or(&[])
.iter()
.map(|ix| &self.bindings[*ix])
.filter(|binding| !self.binding_disabled(binding))
}
pub fn add_bindings<T: IntoIterator<Item = KeyBinding>>(&mut self, bindings: T) {
let no_action_id = &(NoAction {}).type_id();
let mut new_bindings = Vec::new();
let mut has_new_disabled_keystrokes = false;
for binding in bindings {
if binding.action.type_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<&KeyBinding> {
self.bindings
.iter()
.filter(|binding| !self.binding_disabled(binding))
.collect()
}
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,
}
}
}
// #[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"
// );
// }
// }
// }

View file

@ -0,0 +1,474 @@
use crate::{Action, KeyContext, Keymap, KeymapVersion, Keystroke};
use parking_lot::Mutex;
use smallvec::SmallVec;
use std::sync::Arc;
pub struct KeystrokeMatcher {
pending_keystrokes: Vec<Keystroke>,
keymap: Arc<Mutex<Keymap>>,
keymap_version: KeymapVersion,
}
impl KeystrokeMatcher {
pub fn new(keymap: Arc<Mutex<Keymap>>) -> Self {
let keymap_version = keymap.lock().version();
Self {
pending_keystrokes: Vec::new(),
keymap_version,
keymap,
}
}
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: &[KeyContext],
) -> KeyMatch {
let keymap = self.keymap.lock();
// Clear pending keystrokes if the keymap has changed since the last matched keystroke.
if keymap.version() != self.keymap_version {
self.keymap_version = keymap.version();
self.pending_keystrokes.clear();
}
let mut pending_key = None;
let mut found_actions = Vec::new();
for binding in 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(mut actions) => {
found_actions.append(&mut actions);
}
KeyMatch::Pending => {
pending_key.get_or_insert(candidate);
}
KeyMatch::None => {}
}
self.pending_keystrokes.pop();
}
}
if !found_actions.is_empty() {
self.pending_keystrokes.clear();
return KeyMatch::Some(found_actions);
}
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: &[KeyContext],
) -> Option<SmallVec<[Keystroke; 2]>> {
self.keymap
.lock()
.bindings()
.iter()
.rev()
.find_map(|binding| binding.keystrokes_for_action(action, contexts))
}
}
#[derive(Debug)]
pub enum KeyMatch {
None,
Pending,
Some(Vec<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::ActionContext};
// use super::*;
// #[test]
// fn test_keymap_and_view_ordering() -> Result<()> {
// actions!(test, [EditorAction, ProjectPanelAction]);
// let mut editor = ActionContext::default();
// editor.add_identifier("Editor");
// let mut project_panel = ActionContext::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 = ActionContext::default();
// context1.add_identifier("1");
// let mut context2 = ActionContext::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 = ActionContext::default();
// context.add_identifier("a");
// assert!(!predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_identifier("b");
// assert!(predicate.eval(&[context]));
// let mut context = ActionContext::default();
// context.add_identifier("a");
// context.add_key("c", "x");
// assert!(!predicate.eval(&[context]));
// 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(&[ActionContext::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]) -> ActionContext {
// let mut keymap = ActionContext::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 = ActionContext::default();
// context_a.add_identifier("a");
// let mut context_b = ActionContext::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 = ActionContext::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(())
// }
// }

View file

@ -0,0 +1,9 @@
mod binding;
mod context;
mod keymap;
mod matcher;
pub use binding::*;
pub use context::*;
pub use keymap::*;
pub use matcher::*;