Skip to main content

duat_core/mode/
bindings.rs

1use std::ops::{Bound, RangeBounds};
2
3use crossterm::event::{
4    KeyCode, KeyEvent, KeyEventKind, KeyEventState, MediaKeyCode, ModifierKeyCode,
5};
6
7pub use crate::__bindings__ as bindings;
8use crate::{
9    mode::KeyMod,
10    text::{Text, txt},
11};
12
13/// A list of key bindings available in a given [`Mode`]
14///
15/// This list is used for two purposes:
16///
17/// - Provide information about which keys are available in any given
18///   `Mode`.
19/// - Tell the user when they typed in a unavailable binding.
20/// - Warn them when they [map] or [alias] to a unavailable binding.
21///
22/// You should _always_ create this struct via
23///
24/// [`Mode`]: super::Mode
25/// [map]: super::map
26/// [alias]: super::alias
27#[derive(Clone)]
28pub struct Bindings {
29    /// An optional title for this `Bindings`
30    ///
31    /// This should be used to more accurately describe an overall
32    /// "theme" for all keybindings listed.
33    pub title: Option<Text>,
34    /// Descriptions for each of the key bindings
35    ///
36    /// The bindings of the first element are na _alternation_, not a
37    /// _sequence_.
38    ///
39    /// Direct implementation is not recommended, use the
40    /// [`bindings!`] macro instead.
41    pub list: Vec<(Vec<Binding>, Text, Option<Bindings>)>,
42}
43
44impl Bindings {
45    /// Wether these `MappedBindings` accepts the sequence of
46    /// [`KeyEvent`]s
47    pub fn matches_sequence(&self, seq: &[KeyEvent]) -> bool {
48        let mut list = &self.list;
49        seq.iter().all(|key_event| {
50            if let Some((.., bindings)) = list.iter().find(matches_event(*key_event)) {
51                list = &bindings.as_ref().unwrap_or(self).list;
52                true
53            } else {
54                false
55            }
56        })
57    }
58
59    /// Wether the given sequence of [`KeyEvent`]s has a followup
60    /// in these `MappedBindings`
61    pub fn sequence_has_followup(&self, seq: &[KeyEvent]) -> bool {
62        let mut list = &self.list;
63        seq.iter().all(|key_event| {
64            if let Some((.., bindings)) = list.iter().find(matches_event(*key_event))
65                && let Some(bindings) = bindings
66            {
67                list = &bindings.list;
68                true
69            } else {
70                false
71            }
72        })
73    }
74
75    /// Which `Bindings` are available, given the passed sequence
76    pub fn bindings_for(&self, seq: &[KeyEvent]) -> Option<&Bindings> {
77        let mut bindings = self;
78        if seq.is_empty() {
79            Some(self)
80        } else {
81            seq.iter()
82                .map_while(|key_event| {
83                    let (.., nested) = bindings.list.iter().find(matches_event(*key_event))?;
84                    bindings = nested.as_ref()?;
85                    Some(bindings)
86                })
87                .nth(seq.len() - 1)
88        }
89    }
90
91    /// The description for a particular sequence of bound [keys]
92    ///
93    /// [keys]: KeyEvent
94    pub fn description_for<'a>(&'a self, seq: &[KeyEvent]) -> Option<&'a Text> {
95        let mut bindings = Some(self);
96        seq.iter()
97            .map_while(|key_event| {
98                let (_, text, nested) = bindings?.list.iter().find(matches_event(*key_event))?;
99                bindings = nested.as_ref();
100                Some(text)
101            })
102            .last()
103    }
104
105    /// The description for a particular sequence of bound [keys]
106    ///
107    /// [keys]: KeyEvent
108    pub fn description_for_mut<'a>(&'a mut self, seq: &[KeyEvent]) -> Option<&'a mut Text> {
109        let mut bindings = Some(self);
110        seq.iter()
111            .map_while(move |key_event| {
112                let (_, text, nested) =
113                    bindings.take()?.list.iter_mut().find(|(list, ..)| {
114                        list.iter().any(|binding| binding.matches(*key_event))
115                    })?;
116                bindings = nested.as_mut();
117                Some(text)
118            })
119            .last()
120    }
121}
122
123fn matches_event(
124    key_event: KeyEvent,
125) -> impl FnMut(&&(Vec<Binding>, Text, Option<Bindings>)) -> bool {
126    move |(list, ..)| list.iter().any(|binding| binding.matches(key_event))
127}
128
129impl std::fmt::Debug for Bindings {
130    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131        f.debug_struct("Bindings")
132            .field("list", &self.list)
133            .finish()
134    }
135}
136
137/// Possible ways to map keys
138///
139/// This struct serves the purpose of allowing the use of pattern-like
140/// syntax in order to match keys in the [`bindings!`] macro, while
141/// still creating a finitely known list of keys, which can then be
142/// used for documentation.
143#[derive(Debug, Clone, Copy)]
144pub enum Binding {
145    /// A range of [`KeyEvent`]s with [`KeyCode::Char`], like
146    /// `KeyCode::Char('0'..='9')`
147    CharRange(Bound<char>, Bound<char>, KeyMod),
148    /// A range of [`KeyEvent`]s with [`KeyCode::F`], like
149    /// `KeyCode::F(1..=3)`
150    FnRange(Bound<u8>, Bound<u8>, KeyMod),
151    /// Any modifier key, like [`ModifierKeyCode::LeftShift`]
152    ///
153    /// Unlikely to be bound
154    AnyModifier(KeyMod),
155    /// Any media key, like [`MediaKeyCode::MuteVolume`]
156    AnyMedia(KeyMod),
157    /// Any [`KeyCode`], might match anything, given the optional
158    /// [`KeyMod`]
159    Any(Option<KeyMod>),
160    /// A specific [`KeyCode`]/[`KeyMod`] combo
161    Event(KeyCode, KeyMod),
162}
163
164impl Binding {
165    /// Returns a new [concrete `Binding`]
166    ///
167    /// [concrete `Binding`]: Binding::Event
168    pub fn new(code: KeyCode, modif: KeyMod) -> Self {
169        Self::Event(code, modif)
170    }
171
172    /// Returns a `Binding` that could match _anything_
173    pub fn anything() -> Self {
174        Self::Any(None)
175    }
176
177    /// The default [`Text`] formatting for a `Binding`
178    ///
179    /// This function makes use of the `key.char`, `key.mod`,
180    /// `key.special`, `key.range` and `key.any` [`Form`]s.
181    ///
182    /// [`Form`]: crate::form::Form
183    pub fn as_text(&self) -> Text {
184        let mut builder = Text::builder();
185
186        if !matches!(self, Binding::Event(..))
187            && self.modifier().is_some_and(|modif| modif != KeyMod::NONE)
188        {
189            builder.push(txt!("[key.angle]<"));
190            builder.push(super::modifier_text(
191                self.modifier().unwrap_or(KeyMod::NONE),
192            ))
193        }
194
195        builder.push(match self {
196            Binding::CharRange(b0, b1, _) => match (b0, b1) {
197                (Bound::Included(lhs), Bound::Included(rhs)) => {
198                    txt!("[key.char]'{lhs}'[key.range]..=[key.char]'{rhs}'")
199                }
200                (Bound::Included(lhs), Bound::Excluded(rhs)) => {
201                    txt!("[key.char]'{lhs}'[key.range]..[key.char]'{rhs}'")
202                }
203                (Bound::Included(char), Bound::Unbounded) => {
204                    txt!("[key.char]'{char}'[key.range]..")
205                }
206                (Bound::Excluded(lhs), Bound::Included(rhs)) => {
207                    txt!("[key.char]'{lhs}'[key.range]>..=[key.char]'{rhs}'")
208                }
209                (Bound::Excluded(lhs), Bound::Excluded(rhs)) => {
210                    txt!("[key.char]'{lhs}'[key.range]>..[key.char]'{rhs}'")
211                }
212                (Bound::Excluded(char), Bound::Unbounded) => {
213                    txt!("[key.char]'{char}'[key.range]>..")
214                }
215                (Bound::Unbounded, Bound::Included(char)) => {
216                    txt!("[key.range]..=[key.char]'{char}'")
217                }
218                (Bound::Unbounded, Bound::Excluded(char)) => {
219                    txt!("[key.range]..[key.char]'{char}'")
220                }
221                (Bound::Unbounded, Bound::Unbounded) => txt!("[key.any.char]{{char}}"),
222            },
223            Binding::FnRange(b0, b1, _) => match (b0, b1) {
224                (Bound::Included(lhs), Bound::Included(rhs)) => {
225                    txt!("[key.special]F{lhs}[key.range]..=[key.special]F{rhs}")
226                }
227                (Bound::Included(lhs), Bound::Excluded(rhs)) => {
228                    txt!("[key.special]F{lhs}[key.range]..[key.special]F{rhs}")
229                }
230                (Bound::Included(num), Bound::Unbounded) => {
231                    txt!("[key.special]F{num}[key.range]=..")
232                }
233                (Bound::Excluded(lhs), Bound::Included(rhs)) => {
234                    txt!("[key.special]F{lhs}[key.range]>..=[key.special]F{rhs}")
235                }
236                (Bound::Excluded(lhs), Bound::Excluded(rhs)) => {
237                    txt!("[key.special]F{lhs}[key.range]>..[key.special]F{rhs}")
238                }
239                (Bound::Excluded(num), Bound::Unbounded) => {
240                    txt!("[key.special]F{num}[key.range]>..")
241                }
242                (Bound::Unbounded, Bound::Included(num)) => {
243                    txt!("[key.range]..=[key.special]F{num}")
244                }
245                (Bound::Unbounded, Bound::Excluded(num)) => {
246                    txt!("[key.range]..[key.special]F{num}")
247                }
248                (Bound::Unbounded, Bound::Unbounded) => txt!("[key.any]{{f key}}"),
249            },
250            Binding::AnyModifier(_) => txt!("[key.any]{{modifier}}"),
251            Binding::AnyMedia(_) => txt!("[key.any]{{media key}}"),
252            Binding::Any(_) => txt!("[key.any]{{any}}"),
253            Binding::Event(code, modif) => super::keys_to_text(&[KeyEvent::new(*code, *modif)]),
254        });
255
256        if !matches!(self, Binding::Event(..))
257            && self.modifier().is_some_and(|modif| modif != KeyMod::NONE)
258        {
259            builder.push(txt!("[key.angle]>"));
260        }
261
262        builder.build()
263    }
264
265    /// The [`KeyMod`] for this `Binding`
266    ///
267    /// Can be [`None`] in case the `Binding` accepts any [`KeyEvent`]
268    /// whatsoever.
269    pub fn modifier(&self) -> Option<KeyMod> {
270        match self {
271            Binding::CharRange(.., modif)
272            | Binding::FnRange(.., modif)
273            | Binding::AnyModifier(modif)
274            | Binding::AnyMedia(modif)
275            | Binding::Event(_, modif) => Some(*modif),
276            Binding::Any(modif) => *modif,
277        }
278    }
279
280    /// A [`KeyEvent`], with assumptions about less used options
281    ///
282    /// Only returns [`Some`] if this is [`Binding::Event`]
283    /// with a concrete [`KeyCode`] and [`KeyMod`].
284    pub fn as_key_event(&self) -> Option<KeyEvent> {
285        let &Binding::Event(code, modifiers) = self else {
286            return None;
287        };
288
289        Some(KeyEvent {
290            code,
291            modifiers,
292            kind: KeyEventKind::Press,
293            state: KeyEventState::NONE,
294        })
295    }
296
297    /// Wether a [`KeyEvent`] would be matched by this `Binding`
298    pub fn matches(&self, key_event: KeyEvent) -> bool {
299        fn contains<T: Ord>(b0: Bound<T>, b1: Bound<T>, subject: T) -> bool {
300            match b0 {
301                Bound::Included(b0) if subject < b0 => return false,
302                Bound::Excluded(b0) if subject <= b0 => return false,
303                _ => {}
304            }
305            match b1 {
306                Bound::Included(b1) if subject <= b1 => true,
307                Bound::Excluded(b1) if subject < b1 => true,
308                Bound::Unbounded => true,
309                _ => false,
310            }
311        }
312
313        if key_event.is_release() {
314            return false;
315        }
316
317        match *self {
318            Binding::CharRange(b0, b1, modifiers) => {
319                if let KeyCode::Char(char) = key_event.code {
320                    key_event.modifiers == modifiers && contains(b0, b1, char)
321                } else {
322                    false
323                }
324            }
325            Binding::FnRange(b0, b1, modifiers) => {
326                if let KeyCode::F(num) = key_event.code {
327                    key_event.modifiers == modifiers && contains(b0, b1, num)
328                } else {
329                    false
330                }
331            }
332            Binding::AnyModifier(modifiers) => {
333                matches!(key_event.code, KeyCode::Modifier(_)) && key_event.modifiers == modifiers
334            }
335            Binding::AnyMedia(modifiers) => {
336                matches!(key_event.code, KeyCode::Media(_)) && key_event.modifiers == modifiers
337            }
338            Binding::Any(modif) => modif.is_none_or(|modif| modif == key_event.modifiers),
339            Binding::Event(code, modif) => code == key_event.code && modif == key_event.modifiers,
340        }
341    }
342}
343
344macro_rules! implFromRange {
345    ($($range:ident)::+) => {
346        impl From<($($range)::+<char>, KeyMod)> for Binding {
347            fn from((chars, modif): ($($range)::+<char>, KeyMod)) -> Self {
348                Binding::CharRange(
349                    chars.start_bound().cloned(),
350                    chars.end_bound().cloned(),
351                    modif
352                )
353            }
354        }
355
356        impl From<($($range)::+<u8>, KeyMod)> for Binding {
357            fn from((fns, modif): ($($range)::+<u8>, KeyMod)) -> Self {
358                Binding::FnRange(fns.start_bound().cloned(), fns.end_bound().cloned(), modif)
359            }
360        }
361    };
362}
363
364implFromRange!(std::ops::Range);
365implFromRange!(std::ops::RangeFrom);
366implFromRange!(std::ops::RangeInclusive);
367implFromRange!(std::ops::RangeTo);
368implFromRange!(std::ops::RangeToInclusive);
369
370impl From<(char, KeyMod)> for Binding {
371    fn from((char, modif): (char, KeyMod)) -> Self {
372        Binding::Event(KeyCode::Char(char), modif)
373    }
374}
375
376impl From<(u8, KeyMod)> for Binding {
377    fn from((num, modif): (u8, KeyMod)) -> Self {
378        Binding::Event(KeyCode::F(num), modif)
379    }
380}
381
382impl From<(MediaKeyCode, KeyMod)> for Binding {
383    fn from((media, modif): (MediaKeyCode, KeyMod)) -> Self {
384        Binding::Event(KeyCode::Media(media), modif)
385    }
386}
387
388impl From<(ModifierKeyCode, KeyMod)> for Binding {
389    fn from((modifier, modif): (ModifierKeyCode, KeyMod)) -> Self {
390        Binding::Event(KeyCode::Modifier(modifier), modif)
391    }
392}
393
394#[macro_export]
395#[doc(hidden)]
396macro_rules! __bindings__ {
397    (match _ $match:tt) => {{
398        #[allow(clippy::vec_init_then_push)]
399        let bindings: Vec<_> = $crate::mode::bindings!(@bindings $match);
400
401        #[allow(clippy::vec_init_then_push)]
402        let descriptions = $crate::mode::bindings!(@descriptions $match);
403
404        #[allow(clippy::vec_init_then_push)]
405        let followups = $crate::mode::bindings!(@followups $match);
406
407        $crate::mode::Bindings {
408            title: None,
409            list: bindings
410                .into_iter()
411                .zip(descriptions)
412                .zip(followups)
413                .map(|((b, d), f)| (b, d, f))
414                .collect()
415        }
416    }};
417
418    (@bindings { $($patterns:tt)* }) => {{
419        let mut list = vec![Vec::new()];
420        $crate::mode::bindings!(@binding_entry list: $($patterns)*);
421        list
422    }};
423
424    (@binding_entry $list:ident:) => {};
425    (@binding_entry
426        $list:ident: $modif:ident$excl:tt($($tokens:tt)*) | $($rest:tt)*
427    ) => {
428        let last = $list.last_mut().unwrap();
429        $modif$excl(@bindings [] last, $($tokens)*);
430        $crate::mode::bindings!(@binding_entry $list: $($rest)*);
431    };
432    (@binding_entry
433        $list:ident: $modif:ident$excl:tt($($tokens:tt)*) => $result:expr $(, $($rest:tt)*)?
434    ) => {
435        let last = $list.last_mut().unwrap();
436        $modif$excl(@bindings [] last, $($tokens)*);
437        $list.push(Vec::new());
438        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
439    };
440    (@binding_entry
441        $list:ident: $modif:ident$excl:tt($($tokens:tt)*) => $result:tt $(,)? $($rest:tt)*
442    ) => {
443        let last = $list.last_mut().unwrap();
444        $modif$excl(@bindings last, [] $($tokens)*);
445        $list.push(Vec::new());
446        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
447    };
448    (@binding_entry $list:ident: _ | $($rest:tt)*) => {
449        $list.last_mut().unwrap().push($crate::mode::Binding::anything());
450        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
451    };
452    (@binding_entry $list:ident: _ => $result:expr, $($rest:tt)*) => {
453        $list.last_mut().unwrap().push($crate::mode::Binding::anything());
454        $list.push(Vec::new());
455        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
456    };
457    (@binding_entry $list:ident: _ => $result:tt $(,)? $($rest:tt)*) => {
458        $list.last_mut().unwrap().push($crate::mode::Binding::anything());
459        $list.push(Vec::new());
460        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
461    };
462    (@binding_entry $list:ident: $pattern:expr => $result:expr, $($rest:tt)*) => {
463        $list.last_mut().push($crate::mode::Binding::anything());
464        $list.push(Vec::new());
465        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
466    };
467    (@binding_entry $list:ident: $binding_pat:expr => $matcher:tt $(,)? $($rest:tt)*) => {
468        $list.last_mut().unwrap().push($binding_pat);
469        $list.push(Vec::new());
470        $crate::mode::bindings!(@binding_entry $list: $($($rest)*)?);
471    };
472
473    (@descriptions { $($patterns:tt)* }) => {{
474        let mut list = Vec::new();
475        $crate::mode::bindings!(@description_entry list: $($patterns)*);
476        list
477    }};
478
479    (@description_entry $list:ident:) => {};
480    (@description_entry
481        $list:ident:
482        $pattern:pat => ($text:expr, $($matcher:tt)+)
483        $(,$($rest:tt)*)?
484    ) => {
485        $list.push($text);
486        $crate::mode::bindings!(@description_entry $list: $($($rest)*)?);
487    };
488    (@description_entry $list:ident: $pattern:pat => $text:expr $(,$($rest:tt)*)?) => {
489        $list.push($text);
490        $crate::mode::bindings!(@description_entry $list: $($($rest)*)?);
491    };
492
493    (@followups { $($patterns:tt)+ }) => {{
494        let mut list = Vec::new();
495        $crate::mode::bindings!(@followup_entry list: $($patterns)+);
496        list
497    }};
498
499    (@followup_entry $list:ident:) => {};
500    (@followup_entry
501        $list:ident:
502        $pattern:pat => ($texts:expr, match _ $match:tt)
503        $(,$($rest:tt)*)?
504    ) => {
505        $list.push(Some($crate::mode::bindings! { match _ $match }));
506        $crate::mode::bindings!(@followup_entry $list: $($($rest)*)?);
507    };
508    (@followup_entry
509        $list:ident:
510        $pattern:pat => ($text:expr, $bindings:expr)
511        $(,$($rest:tt)*)?
512    ) => {
513        $list.push(Some($bindings));
514        $crate::mode::bindings!(@followup_entry $list: $($($rest)*)?);
515    };
516    (@followup_entry $list:ident: $pattern:pat => $text:expr $(,$($rest:tt)*)?) => {
517        $list.push(None);
518        $crate::mode::bindings!(@followup_entry $list: $($($rest)*)?);
519    };
520}