use std::ops::{Bound, RangeBounds};
use crossterm::event::{
KeyCode, KeyEvent, KeyEventKind, KeyEventState, MediaKeyCode, ModifierKeyCode,
};
pub use crate::__bindings__ as bindings;
use crate::{
mode::KeyMod,
text::{Text, txt},
};
#[derive(Clone)]
pub struct Bindings {
pub title: Option<Text>,
pub list: Vec<(Vec<Binding>, Text, Option<Bindings>)>,
}
impl Bindings {
pub fn matches_sequence(&self, seq: &[KeyEvent]) -> bool {
let mut list = &self.list;
seq.iter().all(|key_event| {
if let Some((.., bindings)) = list.iter().find(matches_event(*key_event)) {
list = &bindings.as_ref().unwrap_or(self).list;
true
} else {
false
}
})
}
pub fn sequence_has_followup(&self, seq: &[KeyEvent]) -> bool {
let mut list = &self.list;
seq.iter().all(|key_event| {
if let Some((.., bindings)) = list.iter().find(matches_event(*key_event))
&& let Some(bindings) = bindings
{
list = &bindings.list;
true
} else {
false
}
})
}
pub fn bindings_for(&self, seq: &[KeyEvent]) -> Option<&Bindings> {
let mut bindings = self;
if seq.is_empty() {
Some(self)
} else {
seq.iter()
.map_while(|key_event| {
let (.., nested) = bindings.list.iter().find(matches_event(*key_event))?;
bindings = nested.as_ref()?;
Some(bindings)
})
.nth(seq.len() - 1)
}
}
pub fn description_for<'a>(&'a self, seq: &[KeyEvent]) -> Option<&'a Text> {
let mut bindings = Some(self);
seq.iter()
.map_while(|key_event| {
let (_, text, nested) = bindings?.list.iter().find(matches_event(*key_event))?;
bindings = nested.as_ref();
Some(text)
})
.last()
}
pub fn description_for_mut<'a>(&'a mut self, seq: &[KeyEvent]) -> Option<&'a mut Text> {
let mut bindings = Some(self);
seq.iter()
.map_while(move |key_event| {
let (_, text, nested) =
bindings.take()?.list.iter_mut().find(|(list, ..)| {
list.iter().any(|binding| binding.matches(*key_event))
})?;
bindings = nested.as_mut();
Some(text)
})
.last()
}
}
fn matches_event(
key_event: KeyEvent,
) -> impl FnMut(&&(Vec<Binding>, Text, Option<Bindings>)) -> bool {
move |(list, ..)| list.iter().any(|binding| binding.matches(key_event))
}
impl std::fmt::Debug for Bindings {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Bindings")
.field("list", &self.list)
.finish()
}
}
#[derive(Debug, Clone, Copy)]
pub enum Binding {
CharRange(Bound<char>, Bound<char>, KeyMod),
FnRange(Bound<u8>, Bound<u8>, KeyMod),
AnyModifier(KeyMod),
AnyMedia(KeyMod),
Any(Option<KeyMod>),
Event(KeyCode, KeyMod),
}
impl Binding {
pub fn new(code: KeyCode, modif: KeyMod) -> Self {
Self::Event(code, modif)
}
pub fn anything() -> Self {
Self::Any(None)
}
pub fn as_text(&self) -> Text {
let mut builder = Text::builder();
if !matches!(self, Binding::Event(..))
&& self.modifier().is_some_and(|modif| modif != KeyMod::NONE)
{
builder.push(txt!("[key.angle]<"));
builder.push(super::modifier_text(
self.modifier().unwrap_or(KeyMod::NONE),
))
}
builder.push(match self {
Binding::CharRange(b0, b1, _) => match (b0, b1) {
(Bound::Included(lhs), Bound::Included(rhs)) => {
txt!("[key.char]'{lhs}'[key.range]..=[key.char]'{rhs}'")
}
(Bound::Included(lhs), Bound::Excluded(rhs)) => {
txt!("[key.char]'{lhs}'[key.range]..[key.char]'{rhs}'")
}
(Bound::Included(char), Bound::Unbounded) => {
txt!("[key.char]'{char}'[key.range]..")
}
(Bound::Excluded(lhs), Bound::Included(rhs)) => {
txt!("[key.char]'{lhs}'[key.range]>..=[key.char]'{rhs}'")
}
(Bound::Excluded(lhs), Bound::Excluded(rhs)) => {
txt!("[key.char]'{lhs}'[key.range]>..[key.char]'{rhs}'")
}
(Bound::Excluded(char), Bound::Unbounded) => {
txt!("[key.char]'{char}'[key.range]>..")
}
(Bound::Unbounded, Bound::Included(char)) => {
txt!("[key.range]..=[key.char]'{char}'")
}
(Bound::Unbounded, Bound::Excluded(char)) => {
txt!("[key.range]..[key.char]'{char}'")
}
(Bound::Unbounded, Bound::Unbounded) => txt!("[key.any.char]{{char}}"),
},
Binding::FnRange(b0, b1, _) => match (b0, b1) {
(Bound::Included(lhs), Bound::Included(rhs)) => {
txt!("[key.special]F{lhs}[key.range]..=[key.special]F{rhs}")
}
(Bound::Included(lhs), Bound::Excluded(rhs)) => {
txt!("[key.special]F{lhs}[key.range]..[key.special]F{rhs}")
}
(Bound::Included(num), Bound::Unbounded) => {
txt!("[key.special]F{num}[key.range]=..")
}
(Bound::Excluded(lhs), Bound::Included(rhs)) => {
txt!("[key.special]F{lhs}[key.range]>..=[key.special]F{rhs}")
}
(Bound::Excluded(lhs), Bound::Excluded(rhs)) => {
txt!("[key.special]F{lhs}[key.range]>..[key.special]F{rhs}")
}
(Bound::Excluded(num), Bound::Unbounded) => {
txt!("[key.special]F{num}[key.range]>..")
}
(Bound::Unbounded, Bound::Included(num)) => {
txt!("[key.range]..=[key.special]F{num}")
}
(Bound::Unbounded, Bound::Excluded(num)) => {
txt!("[key.range]..[key.special]F{num}")
}
(Bound::Unbounded, Bound::Unbounded) => txt!("[key.any]{{f key}}"),
},
Binding::AnyModifier(_) => txt!("[key.any]{{modifier}}"),
Binding::AnyMedia(_) => txt!("[key.any]{{media key}}"),
Binding::Any(_) => txt!("[key.any]{{any}}"),
Binding::Event(code, modif) => super::keys_to_text(&[KeyEvent::new(*code, *modif)]),
});
if !matches!(self, Binding::Event(..))
&& self.modifier().is_some_and(|modif| modif != KeyMod::NONE)
{
builder.push(txt!("[key.angle]>"));
}
builder.build()
}
pub fn modifier(&self) -> Option<KeyMod> {
match self {
Binding::CharRange(.., modif)
| Binding::FnRange(.., modif)
| Binding::AnyModifier(modif)
| Binding::AnyMedia(modif)
| Binding::Event(_, modif) => Some(*modif),
Binding::Any(modif) => *modif,
}
}
pub fn as_key_event(&self) -> Option<KeyEvent> {
let &Binding::Event(code, modifiers) = self else {
return None;
};
Some(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state: KeyEventState::NONE,
})
}
pub fn matches(&self, key_event: KeyEvent) -> bool {
fn contains<T: Ord>(b0: Bound<T>, b1: Bound<T>, subject: T) -> bool {
match b0 {
Bound::Included(b0) if subject < b0 => return false,
Bound::Excluded(b0) if subject <= b0 => return false,
_ => {}
}
match b1 {
Bound::Included(b1) if subject <= b1 => true,
Bound::Excluded(b1) if subject < b1 => true,
Bound::Unbounded => true,
_ => false,
}
}
if key_event.is_release() {
return false;
}
match *self {
Binding::CharRange(b0, b1, modifiers) => {
if let KeyCode::Char(char) = key_event.code {
key_event.modifiers == modifiers && contains(b0, b1, char)
} else {
false
}
}
Binding::FnRange(b0, b1, modifiers) => {
if let KeyCode::F(num) = key_event.code {
key_event.modifiers == modifiers && contains(b0, b1, num)
} else {
false
}
}
Binding::AnyModifier(modifiers) => {
matches!(key_event.code, KeyCode::Modifier(_)) && key_event.modifiers == modifiers
}
Binding::AnyMedia(modifiers) => {
matches!(key_event.code, KeyCode::Media(_)) && key_event.modifiers == modifiers
}
Binding::Any(modif) => modif.is_none_or(|modif| modif == key_event.modifiers),
Binding::Event(code, modif) => code == key_event.code && modif == key_event.modifiers,
}
}
}
macro_rules! implFromRange {
($($range:ident)::+) => {
impl From<($($range)::+<char>, KeyMod)> for Binding {
fn from((chars, modif): ($($range)::+<char>, KeyMod)) -> Self {
Binding::CharRange(
chars.start_bound().cloned(),
chars.end_bound().cloned(),
modif
)
}
}
impl From<($($range)::+<u8>, KeyMod)> for Binding {
fn from((fns, modif): ($($range)::+<u8>, KeyMod)) -> Self {
Binding::FnRange(fns.start_bound().cloned(), fns.end_bound().cloned(), modif)
}
}
};
}
implFromRange!(std::ops::Range);
implFromRange!(std::ops::RangeFrom);
implFromRange!(std::ops::RangeInclusive);
implFromRange!(std::ops::RangeTo);
implFromRange!(std::ops::RangeToInclusive);
impl From<(char, KeyMod)> for Binding {
fn from((char, modif): (char, KeyMod)) -> Self {
Binding::Event(KeyCode::Char(char), modif)
}
}
impl From<(u8, KeyMod)> for Binding {
fn from((num, modif): (u8, KeyMod)) -> Self {
Binding::Event(KeyCode::F(num), modif)
}
}
impl From<(MediaKeyCode, KeyMod)> for Binding {
fn from((media, modif): (MediaKeyCode, KeyMod)) -> Self {
Binding::Event(KeyCode::Media(media), modif)
}
}
impl From<(ModifierKeyCode, KeyMod)> for Binding {
fn from((modifier, modif): (ModifierKeyCode, KeyMod)) -> Self {
Binding::Event(KeyCode::Modifier(modifier), modif)
}
}
#[macro_export]
#[doc(hidden)]
macro_rules! __bindings__ {
(match _ $match:tt) => {{
#[allow(clippy::vec_init_then_push)]
let bindings: Vec<_> = $crate::mode::bindings!(@bindings $match);
#[allow(clippy::vec_init_then_push)]
let descriptions = $crate::mode::bindings!(@descriptions $match);
#[allow(clippy::vec_init_then_push)]
let followups = $crate::mode::bindings!(@followups $match);
$crate::mode::Bindings {
title: None,
list: bindings
.into_iter()
.zip(descriptions)
.zip(followups)
.map(|((b, d), f)| (b, d, f))
.collect()
}
}};
(@bindings { $($patterns:tt)* }) => {{
let mut list = vec![Vec::new()];
$crate::mode::bindings!(@binding_entry list: $($patterns)*);
list
}};
(@binding_entry $list:ident:) => {};
(@binding_entry
$list:ident: $modif:ident$excl:tt($($tokens:tt)*) | $($rest:tt)*
) => {
let last = $list.last_mut().unwrap();
$modif$excl(@bindings [] last, $($tokens)*);
$crate::mode::bindings!(@binding_entry $list: $($rest)*);
};
(@binding_entry
$list:ident: $modif:ident$excl:tt($($tokens:tt)*) => $result:expr $(, $($rest:tt)*)?
) => {
let last = $list.last_mut().unwrap();
$modif$excl(@bindings [] last, $($tokens)*);
$list.push(Vec::new());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@binding_entry
$list:ident: $modif:ident$excl:tt($($tokens:tt)*) => $result:tt $(,)? $($rest:tt)*
) => {
let last = $list.last_mut().unwrap();
$modif$excl(@bindings last, [] $($tokens)*);
$list.push(Vec::new());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@binding_entry $list:ident: _ | $($rest:tt)*) => {
$list.last_mut().unwrap().push($crate::mode::Binding::anything());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@binding_entry $list:ident: _ => $result:expr, $($rest:tt)*) => {
$list.last_mut().unwrap().push($crate::mode::Binding::anything());
$list.push(Vec::new());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@binding_entry $list:ident: _ => $result:tt $(,)? $($rest:tt)*) => {
$list.last_mut().unwrap().push($crate::mode::Binding::anything());
$list.push(Vec::new());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@binding_entry $list:ident: $pattern:expr => $result:expr, $($rest:tt)*) => {
$list.last_mut().push($crate::mode::Binding::anything());
$list.push(Vec::new());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@binding_entry $list:ident: $binding_pat:expr => $matcher:tt $(,)? $($rest:tt)*) => {
$list.last_mut().unwrap().push($binding_pat);
$list.push(Vec::new());
$crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
};
(@descriptions { $($patterns:tt)* }) => {{
let mut list = Vec::new();
$crate::mode::bindings!(@description_entry list: $($patterns)*);
list
}};
(@description_entry $list:ident:) => {};
(@description_entry
$list:ident:
$pattern:pat => ($text:expr, $($matcher:tt)+)
$(,$($rest:tt)*)?
) => {
$list.push($text);
$crate::mode::bindings!(@description_entry $list: $($($rest)*)?);
};
(@description_entry $list:ident: $pattern:pat => $text:expr $(,$($rest:tt)*)?) => {
$list.push($text);
$crate::mode::bindings!(@description_entry $list: $($($rest)*)?);
};
(@followups { $($patterns:tt)+ }) => {{
let mut list = Vec::new();
$crate::mode::bindings!(@followup_entry list: $($patterns)+);
list
}};
(@followup_entry $list:ident:) => {};
(@followup_entry
$list:ident:
$pattern:pat => ($texts:expr, match _ $match:tt)
$(,$($rest:tt)*)?
) => {
$list.push(Some($crate::mode::bindings! { match _ $match }));
$crate::mode::bindings!(@followup_entry $list: $($($rest)*)?);
};
(@followup_entry
$list:ident:
$pattern:pat => ($text:expr, $bindings:expr)
$(,$($rest:tt)*)?
) => {
$list.push(Some($bindings));
$crate::mode::bindings!(@followup_entry $list: $($($rest)*)?);
};
(@followup_entry $list:ident: $pattern:pat => $text:expr $(,$($rest:tt)*)?) => {
$list.push(None);
$crate::mode::bindings!(@followup_entry $list: $($($rest)*)?);
};
}