use crate::{Key, KeyInput, KeySeq, Match, Result};
use std::time::{Duration, Instant};
#[cfg(feature = "arbitrary")]
use arbitrary::Arbitrary;
#[derive(Clone, PartialEq, Eq, Debug)]
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
pub struct Keybind<A> {
pub seq: KeySeq,
pub action: A,
}
impl<A> Keybind<A> {
pub fn new<S: Into<KeySeq>>(seq: S, action: A) -> Self {
Self {
seq: seq.into(),
action,
}
}
}
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Keybinds<A> {
binds: Vec<Keybind<A>>,
ongoing: Vec<KeyInput>,
last_input: Option<Instant>,
timeout: Duration,
}
impl<A> Default for Keybinds<A> {
fn default() -> Self {
Self::new(vec![])
}
}
impl<A> Keybinds<A> {
pub fn new(binds: Vec<Keybind<A>>) -> Self {
Self {
binds,
ongoing: vec![],
last_input: None,
timeout: DEFAULT_TIMEOUT,
}
}
pub fn push(&mut self, bind: Keybind<A>) {
self.binds.push(bind);
self.reset();
}
pub fn bind(&mut self, key_sequence: &str, action: A) -> Result<()> {
let seq: KeySeq = key_sequence.parse()?;
self.push(Keybind::new(seq, action));
Ok(())
}
fn handle_timeout(&mut self) {
let now = Instant::now();
let is_timeout = self
.last_input
.is_some_and(|t| now.duration_since(t) > self.timeout);
if is_timeout {
self.ongoing.clear();
}
self.last_input = Some(now);
}
pub fn dispatch<I: Into<KeyInput>>(&mut self, input: I) -> Option<&A> {
let input = input.into();
if input.key() == Key::Ignored {
return None;
}
self.handle_timeout();
self.ongoing.push(input);
let mut is_ongoing = false;
for bind in self.binds.iter() {
match bind.seq.match_to(&self.ongoing) {
Match::Matched => {
self.ongoing.clear();
self.last_input = None;
return Some(&bind.action);
}
Match::Prefix => is_ongoing = true,
Match::Unmatch => continue,
}
}
if !is_ongoing {
self.ongoing.clear();
self.last_input = None;
}
None
}
pub fn set_timeout(&mut self, timeout: Duration) {
self.timeout = timeout;
}
pub fn reset(&mut self) {
self.ongoing.clear();
self.last_input = None;
}
pub fn timeout(&self) -> Duration {
self.timeout
}
pub fn as_slice(&self) -> &[Keybind<A>] {
self.binds.as_slice()
}
pub fn is_ongoing(&self) -> bool {
self.last_input.is_some()
}
pub fn ongoing_inputs(&self) -> &[KeyInput] {
self.ongoing.as_slice()
}
pub fn into_vec(self) -> Vec<Keybind<A>> {
self.binds
}
}
impl<A> FromIterator<Keybind<A>> for Keybinds<A> {
fn from_iter<T: IntoIterator<Item = Keybind<A>>>(iter: T) -> Self {
Keybinds::new(iter.into_iter().collect())
}
}
impl<A> Extend<Keybind<A>> for Keybinds<A> {
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = Keybind<A>>,
{
self.binds.extend(iter);
self.reset();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Key, Mods};
use std::thread::sleep;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum A {
Action1,
Action2,
Action3,
Action4,
Action5,
}
#[test]
fn handle_input() {
let binds = vec![
Keybind::new('a', A::Action1),
Keybind::new(KeyInput::new('a', Mods::CTRL), A::Action2),
Keybind::new(['B', 'c'], A::Action3),
Keybind::new(['H', 'e', 'l', 'l', 'o'], A::Action4),
Keybind::new(Key::Up, A::Action5),
];
let mut keybinds = Keybinds::new(binds.clone());
for bind in binds {
keybinds.reset();
let len = bind.seq.as_slice().len();
for (idx, input) in bind.seq.as_slice().iter().copied().enumerate() {
let is_last = idx + 1 == len;
let expected = is_last.then_some(bind.action);
let actual = keybinds.dispatch(input);
assert_eq!(actual, expected.as_ref(), "bind={bind:?}");
assert_eq!(keybinds.is_ongoing(), !is_last, "bind={bind:?}");
}
}
}
#[test]
fn discard_ongoing_nothing_matched() {
let mut keybinds = Keybinds::new(vec![Keybind::new('a', A::Action1)]);
assert_eq!(keybinds.dispatch('x'), None);
assert_eq!(keybinds.dispatch('y'), None);
assert_eq!(keybinds.dispatch('a'), Some(&A::Action1));
assert_eq!(keybinds.dispatch('z'), None);
assert_eq!(keybinds.dispatch('a'), Some(&A::Action1));
}
#[test]
fn keybinds_from_iter() {
let expected = vec![
Keybind::new('a', A::Action1),
Keybind::new(
[
KeyInput::new('b', Mods::CTRL),
KeyInput::new('c', Mods::MOD),
],
A::Action2,
),
];
let binds: Keybinds<_> = expected.iter().cloned().collect();
assert_eq!(binds.as_slice(), &expected);
}
#[test]
fn dispatcher_ongoing_matching() {
let mut keybinds = Keybinds::new(vec![Keybind::new(['a', 'b'], A::Action1)]);
assert!(!keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &[]);
keybinds.dispatch('x');
assert!(!keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &[]);
keybinds.dispatch('a');
assert!(keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &['a'.into()]);
keybinds.dispatch('b');
assert!(!keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &[]);
keybinds.dispatch('y');
assert!(!keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &[]);
keybinds.dispatch('a');
assert!(keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &['a'.into()]);
keybinds.dispatch('z');
assert!(!keybinds.is_ongoing());
assert_eq!(keybinds.ongoing_inputs(), &[]);
}
#[test]
fn dispatcher_set_timeout() {
let mut keybinds = Keybinds::<A>::default();
assert_eq!(keybinds.timeout(), DEFAULT_TIMEOUT);
let d = Duration::from_secs(2);
keybinds.set_timeout(d);
assert_eq!(keybinds.timeout(), d);
}
#[test]
fn dispatcher_ignore_keys() {
let mut keybinds = Keybinds::new(vec![Keybind::new(['a', 'b'], A::Action1)]);
keybinds.dispatch('a');
assert_eq!(keybinds.dispatch(Key::Ignored), None);
assert_eq!(keybinds.dispatch('b'), Some(&A::Action1));
}
#[test]
fn dispatcher_timeout_input() {
let mut keybinds = Keybinds::new(vec![Keybind::new(['a', 'b'], A::Action1)]);
keybinds.set_timeout(Duration::from_millis(10));
keybinds.dispatch('a');
assert_eq!(keybinds.dispatch('b'), Some(&A::Action1));
keybinds.dispatch('a');
sleep(Duration::from_millis(50));
assert_eq!(keybinds.dispatch('b'), None);
keybinds.dispatch('a');
assert_eq!(keybinds.dispatch('b'), Some(&A::Action1));
}
#[test]
fn keybinds_bind() {
let mut keybinds = Keybinds::default();
keybinds.bind("x", A::Action1).unwrap();
keybinds.bind("a b", A::Action2).unwrap();
keybinds.bind("", A::Action1).unwrap_err();
assert_eq!(keybinds.dispatch('x'), Some(&A::Action1));
keybinds.dispatch('a');
assert_eq!(keybinds.dispatch('b'), Some(&A::Action2));
keybinds.dispatch('a');
assert!(keybinds.is_ongoing());
keybinds.bind("y", A::Action1).unwrap();
assert!(!keybinds.is_ongoing());
}
#[test]
fn dispatcher_reset() {
let mut keybinds = Keybinds::new(vec![Keybind::new(['a', 'b'], A::Action1)]);
keybinds.dispatch('a');
assert!(keybinds.is_ongoing());
keybinds.reset();
assert!(!keybinds.is_ongoing());
}
#[test]
fn default_keybinds() {
let mut binds = Keybinds::<()>::default();
assert!(binds.as_slice().is_empty());
assert_eq!(binds.dispatch('a'), None);
assert!(!binds.is_ongoing());
}
#[test]
fn distinguish_bindings_with_modifiers() {
let mut keybinds = Keybinds::new(vec![
Keybind::new(KeyInput::new('a', Mods::CTRL | Mods::ALT), A::Action1),
Keybind::new(KeyInput::new('a', Mods::CTRL), A::Action2),
Keybind::new('a', A::Action3),
]);
assert_eq!(keybinds.dispatch('a'), Some(&A::Action3));
assert_eq!(
keybinds.dispatch(KeyInput::new('a', Mods::CTRL)),
Some(&A::Action2),
);
assert_eq!(
keybinds.dispatch(KeyInput::new('a', Mods::CTRL | Mods::ALT)),
Some(&A::Action1),
);
assert_eq!(
keybinds.dispatch(KeyInput::new('a', Mods::CTRL | Mods::ALT | Mods::WIN)),
None,
);
}
#[test]
fn keybinds_priority_order() {
let mut keybinds = Keybinds::new(vec![
Keybind::new('a', A::Action1),
Keybind::new('a', A::Action2),
Keybind::new('a', A::Action3),
]);
assert_eq!(keybinds.dispatch('a'), Some(&A::Action1));
}
#[test]
fn smaller_seq_is_prioritized() {
let mut keybinds = Keybinds::new(vec![
Keybind::new('a', A::Action1),
Keybind::new(['a', 'a'], A::Action2),
Keybind::new(['a', 'b'], A::Action3),
]);
assert_eq!(keybinds.dispatch('a'), Some(&A::Action1));
assert_eq!(keybinds.dispatch('a'), Some(&A::Action1));
assert_eq!(keybinds.dispatch('b'), None);
}
#[test]
fn non_ascii_space() {
let mut keybinds = Keybinds::new(vec![Keybind::new(' ', A::Action1)]);
assert_eq!(keybinds.dispatch(' '), Some(&A::Action1));
let mut keybinds = Keybinds::default();
keybinds.bind(" ", A::Action1).unwrap();
keybinds.bind("Ctrl+ ", A::Action2).unwrap();
assert_eq!(keybinds.dispatch(' '), Some(&A::Action1));
assert_eq!(
keybinds.dispatch(KeyInput::new(' ', Mods::CTRL)),
Some(&A::Action2),
);
}
#[test]
fn keybinds_push() {
let mut keybinds = Keybinds::default();
assert_eq!(keybinds.dispatch('a'), None);
keybinds.push(Keybind::new('a', A::Action1));
assert_eq!(keybinds.dispatch('a'), Some(&A::Action1));
keybinds.push(Keybind::new(['b', 'c'], A::Action2));
assert_eq!(keybinds.dispatch('b'), None);
assert!(keybinds.is_ongoing());
keybinds.push(Keybind::new('c', A::Action3));
assert!(!keybinds.is_ongoing());
}
#[test]
fn keybinds_extend() {
let mut keybinds = Keybinds::new(vec![Keybind::new(['x', 'y'], A::Action1)]);
assert_eq!(keybinds.dispatch('x'), None);
assert!(keybinds.is_ongoing());
keybinds.extend([
Keybind::new('a', A::Action1),
Keybind::new('b', A::Action1),
Keybind::new('c', A::Action1),
]);
assert!(!keybinds.is_ongoing());
}
}