duat_core/mode/
patterns.rs

1//! Macros for very easy [`Mode`] pattern matching
2//!
3//! This module adds the [`event!`], [`alt!`], [`ctrl!`] and
4//! [`shift!`] macros, which can be used to create very succinct
5//! patterns for matching, greatly improving on the signal to noise
6//! ratio, especially inside the [`Mode::send_key`] function.
7//!
8//! [`Mode`]: super::Mode
9//! [`Mode::send_key`]: super::Mode::send_key
10use crate::mode::KeyMod;
11
12/// Macro for shortening [`KeyEvent`]s in pattern matching
13///
14/// This macro just turns this:
15///
16/// ```rust
17/// # use duat_core::mode::{KeyCode, KeyEvent, event};
18/// # let key_event: KeyEvent = KeyCode::Char('c').into();
19/// match key_event {
20///     event!('c') => {
21///         //...
22///     }
23///     event!(KeyCode::Backspace | KeyCode::Tab) => {
24///         //...
25///     }
26///     _ => {
27///         //...
28///     }
29/// }
30/// ```
31///
32/// Into this:
33///
34/// ```rust
35/// # use duat_core::mode::{KeyCode, KeyEvent, KeyEventKind, KeyMod};
36/// # let key_event: KeyEvent = KeyCode::Char('c').into();
37/// match key_event {
38///     KeyEvent {
39///         code: KeyCode::Char('c'),
40///         modifiers: KeyMod::NONE,
41///         kind: KeyEventKind::Press | KeyEventKind::Repeat,
42///         ..
43///     } => {
44///         //...
45///     }
46///     KeyEvent {
47///         code: KeyCode::Backspace | KeyCode::Tab,
48///         modifiers: KeyMod::NONE,
49///         kind: KeyEventKind::Press | KeyEventKind::Repeat,
50///         ..
51///     } => {
52///         //...
53///     }
54///     _ => {
55///         //...
56///     }
57/// }
58/// ```
59///
60/// This is very useful for pattern matching inside of [`Mode`]s,
61/// allowing you to significantly cut down on the amount of
62/// boilerplate being written.
63///
64/// You should also check out [`alt!`], [`ctrl!`] and [`shift!`],
65/// which are like this macro, but also add their respective
66/// [`KeyMod`]s, and can be nested. This macro _cannot_ be nested with
67/// them however, for obvious reasons.
68///
69/// [`KeyEvent`]: super::KeyEvent
70/// [`Mode`]: super::Mode
71pub macro event {
72    ($($chars:literal)|+) => {
73        $crate::mode::KeyEvent {
74            code: $crate::mode::KeyCode::Char($($chars)|+),
75            modifiers: $crate::mode::KeyMod::NONE,
76            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
77        }
78    },
79    // Straight up useless tbh.
80    ($char:ident @ $chars:literal) => {
81        $crate::mode::KeyEvent {
82            code: $crate::mode::KeyCode::Char($char @ $chars),
83            modifiers: $crate::mode::KeyMod::NONE,
84            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
85        }
86    },
87    ($char:ident @ ($($chars:literal)|+)) => {
88        $crate::mode::KeyEvent {
89            code: $crate::mode::KeyCode::Char($char @ ($($chars)|+)),
90            modifiers: $crate::mode::KeyMod::NONE,
91            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
92        }
93    },
94    ($code:pat) => {
95        $crate::mode::KeyEvent {
96            code: $code,
97            modifiers: $crate::mode::KeyMod::NONE,
98            ..
99        }
100    }
101}
102
103/// Macro for pattern matching [`KeyEvent`]s with [`KeyMod::ALT`]
104///
105/// This macro essentially turns this:
106///
107/// ```rust
108/// # use duat_core::mode::{KeyCode, KeyEvent, alt};
109/// # let key_event: KeyEvent = KeyCode::Char('c').into();
110/// match key_event {
111///     alt!('c') => {
112///         //...
113///     }
114///     _ => {
115///         //...
116///     }
117/// }
118/// ```
119///
120/// Into this:
121///
122/// ```rust
123/// # use duat_core::mode::{KeyCode, KeyEvent, KeyMod, KeyEventKind};
124/// # let key_event: KeyEvent = KeyCode::Char('c').into();
125/// match key_event {
126///     KeyEvent {
127///         code: KeyCode::Char('c'),
128///         modifiers: KeyMod::ALT,
129///         kind: KeyEventKind::Press | KeyEventKind::Repeat,
130///         ..
131///     } => {
132///         //...
133///     }
134///     _ => {
135///         //...
136///     }
137/// }
138/// ```
139///
140/// This is very useful for [`Mode`]s, which require matching on a
141/// large number of different `KeyEvent` patterns in order to decide
142/// what to do.
143///
144/// You can also use this with more complex patterns:
145///
146/// ```rust
147/// # use duat_core::mode::{KeyCode, KeyEvent, alt, ctrl, shift};
148/// # let key_event: KeyEvent = KeyCode::Char('c').into();
149/// match key_event {
150///     ctrl!(alt!('a' | 'b' | 'c')) | shift!(alt!(KeyCode::F(3 | 5))) => {
151///         //...
152///     }
153///     _ => {
154///         //...
155///     }
156/// }
157/// ```
158///
159/// For the other two modifiers with this convenience, see [`ctrl!`]
160/// and [`shift!`]. There is also [`event!`], which has no modifiers,
161/// and it _cannot_ be nested with the other ones, for obvious
162/// reasons.
163///
164/// [`KeyEvent`]: super::KeyEvent
165/// [`Mode`]: super::Mode
166pub macro alt {
167    ($($chars:literal)|+) => {
168        $crate::mode::KeyEvent {
169            code: $crate::mode::KeyCode::Char($($chars)|+),
170            modifiers: $crate::mode::KeyMod::ALT,
171            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
172        }
173    },
174    // Straight up useless tbh.
175    ($char:ident @ $chars:literal) => {
176        $crate::mode::KeyEvent {
177            code: $crate::mode::KeyCode::Char($char @ $chars),
178            modifiers: $crate::mode::KeyMod::ALT,
179            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
180        }
181    },
182    ($char:ident @ ($($chars:literal)|+)) => {
183        $crate::mode::KeyEvent {
184            code: $crate::mode::KeyCode::Char($char @ ($($chars)|+)),
185            modifiers: $crate::mode::KeyMod::ALT,
186            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
187        }
188    },
189    // I use $excl here in order to make it look like part of the macro,
190    // so rust analyzer properly highlights it.
191    ($modif:ident$excl:tt($($tokens:tt)+)) => {
192        $crate::mode::KeyEvent {
193            code: $modif$excl(@code $($tokens)+),
194            modifiers: $modif$excl(@modif [ALT] $($tokens)+),
195            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
196        }
197    },
198    ($code:pat) => {
199        $crate::mode::KeyEvent {
200            code: $code,
201            modifiers: $crate::mode::KeyMod::ALT,
202            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
203        }
204    },
205
206    (@code $modif:ident$excl:tt($($tokens:tt)+)) => {
207        $modif$excl(@code $($tokens)+)
208    },
209    (@code $($chars:literal)|+) => {
210        $crate::mode::KeyCode::Char($($chars)|+)
211    },
212    (@code $char:ident @ $chars:literal) => {
213        $crate::mode::KeyCode::Char($char @ $chars)
214    },
215    (@code $char:ident @ ($($chars:literal)|+)) => {
216        $crate::mode::KeyCode::Char($char @ ($($chars)|+))
217    },
218    (@code $code:pat) => {
219        $code
220    },
221
222    (@modif [$($list:ident),+] $modif:ident$excl:tt($($tokens:tt)+)) => {
223        $modif$excl(@modif [ALT, $($list),+] $($tokens)+)
224    },
225    (@modif [$($list:ident),+] $($other:tt)+) => {
226        $crate::mode::join_modifiers![ALT, $($list),+]
227    },
228}
229
230/// Macro for pattern matching [`KeyEvent`]s with [`KeyMod::CONTROL`]
231///
232/// This macro essentially turns this:
233///
234/// ```rust
235/// # use duat_core::mode::{KeyCode, KeyEvent, ctrl};
236/// # let key_event: KeyEvent = KeyCode::Char('c').into();
237/// match key_event {
238///     ctrl!(KeyCode::Backspace) => {
239///         //...
240///     }
241///     _ => {
242///         //...
243///     }
244/// }
245/// ```
246///
247/// Into this:
248///
249/// ```rust
250/// # use duat_core::mode::{KeyCode, KeyEvent, KeyMod, KeyEventKind};
251/// # let key_event: KeyEvent = KeyCode::Char('c').into();
252/// match key_event {
253///     KeyEvent {
254///         code: KeyCode::Backspace,
255///         modifiers: KeyMod::CONTROL,
256///         kind: KeyEventKind::Press | KeyEventKind::Repeat,
257///         ..
258///     } => {
259///         //...
260///     }
261///     _ => {
262///         //...
263///     }
264/// }
265/// ```
266///
267/// This is very useful for [`Mode`]s, which require matching on a
268/// large number of different `KeyEvent` patterns in order to decide
269/// what to do.
270///
271/// You can also use this with more complex patterns:
272///
273/// ```rust
274/// # use duat_core::mode::{KeyCode, KeyEvent, alt, ctrl};
275/// # let key_event: KeyEvent = KeyCode::Char('c').into();
276/// match key_event {
277///     ctrl!(alt!(KeyCode::Char('a' | 'b' | 'c') | KeyCode::BackTab)) => {
278///         //...
279///     }
280///     _ => {
281///         //...
282///     }
283/// }
284/// ```
285///
286/// For the other two modifiers with this convenience, see [`alt!`]
287/// and [`shift!`]. There is also [`event!`], which has no modifiers,
288/// and it _cannot_ be nested with the other ones, for obvious
289/// reasons.
290///
291/// [`KeyEvent`]: super::KeyEvent
292/// [`Mode`]: super::Mode
293pub macro ctrl {
294    ($($chars:literal)|+) => {
295        $crate::mode::KeyEvent {
296            code: $crate::mode::KeyCode::Char($($chars)|+),
297            modifiers: $crate::mode::KeyMod::CONTROL,
298            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
299        }
300    },
301    // Straight up useless tbh.
302    ($char:ident @ $chars:literal) => {
303        $crate::mode::KeyEvent {
304            code: $crate::mode::KeyCode::Char($char @ $chars),
305            modifiers: $crate::mode::KeyMod::CONTROL,
306            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
307        }
308    },
309    ($char:ident @ ($($chars:literal)|+)) => {
310        $crate::mode::KeyEvent {
311            code: $crate::mode::KeyCode::Char($char @ ($($chars)|+)),
312            modifiers: $crate::mode::KeyMod::CONTROL,
313            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
314        }
315    },
316    // I use $excl here in order to make it look like part of the macro,
317    // so rust analyzer properly highlights it.
318    ($modif:ident$excl:tt($($tokens:tt)+)) => {
319        $crate::mode::KeyEvent {
320            code: $modif$excl(@code $($tokens)+),
321            modifiers: $modif$excl(@modif [CONTROL] $($tokens)+),
322            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
323        }
324    },
325    ($code:pat) => {
326        $crate::mode::KeyEvent {
327            code: $code,
328            modifiers: $crate::mode::KeyMod::CONTROL,
329            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
330        }
331    },
332
333    (@code $modif:ident$excl:tt($($tokens:tt)+)) => {
334        $modif$excl(@code $($tokens)+)
335    },
336    (@code $($chars:literal)|+) => {
337        $crate::mode::KeyCode::Char($($chars)|+)
338    },
339    (@code $char:ident @ $chars:literal) => {
340        $crate::mode::KeyCode::Char($char @ $chars)
341    },
342    (@code $char:ident @ ($($chars:literal)|+)) => {
343        $crate::mode::KeyCode::Char($char @ ($($chars)|+))
344    },
345    (@code $code:pat) => {
346        $code
347    },
348
349    (@modif [$($list:ident),+] $modif:ident$excl:tt($($tokens:tt)+)) => {
350        $modif$excl(@modif [CONTROL, $($list),+] $($tokens)+)
351    },
352    (@modif [$($list:ident),+] $($other:tt)+) => {
353        $crate::mode::join_modifiers![CONTROL, $($list),+]
354    },
355}
356
357/// Macro for pattern matching [`KeyEvent`]s with [`KeyMod::SHIFT`]
358///
359/// This macro essentially turns this:
360///
361/// ```rust
362/// # use duat_core::mode::{KeyCode, KeyEvent, shift};
363/// # let key_event: KeyEvent = KeyCode::Char('c').into();
364/// match key_event {
365///     shift!(KeyCode::Enter) => {
366///         //...
367///     }
368///     _ => {
369///         //...
370///     }
371/// }
372/// ```
373///
374/// Into this:
375///
376/// ```rust
377/// # use duat_core::mode::{KeyCode, KeyEvent, KeyMod, KeyEventKind};
378/// # let key_event: KeyEvent = KeyCode::Char('c').into();
379/// match key_event {
380///     KeyEvent {
381///         code: KeyCode::Enter,
382///         modifiers: KeyMod::SHIFT,
383///         kind: KeyEventKind::Press | KeyEventKind::Repeat,
384///         ..
385///     } => {
386///         //...
387///     }
388///     _ => {
389///         //...
390///     }
391/// }
392/// ```
393///
394/// This is very useful for [`Mode`]s, which require matching on a
395/// large number of different `KeyEvent` patterns in order to decide
396/// what to do.
397///
398/// You can also use this with more complex patterns:
399///
400/// ```rust
401/// # use duat_core::mode::{KeyCode, KeyEvent, ctrl, shift};
402/// # let key_event: KeyEvent = KeyCode::Char('c').into();
403/// match key_event {
404///     ctrl!(shift!(KeyCode::PageDown | KeyCode::PageUp)) => {
405///         //...
406///     }
407///     _ => {
408///         //...
409///     }
410/// }
411/// ```
412///
413/// For the other two modifiers with this convenience, see [`alt!`]
414/// and [`shift!`]. There is also [`event!`], which has no modifiers,
415/// and it _cannot_ be nested with the other ones, for obvious
416/// reasons.
417///
418/// # A note on [`shift!`] specifically
419///
420/// Unlike [`ctrl!`] and [`alt!`], the [`KeyMod::SHIFT`] modifier is
421/// not always emitted, since some keys, like `'B'` can automatically
422/// be inferred to be shifted, so the shift modifier is not included
423/// with the [`KeyEvent`]. Given that, for now, you should avoid using
424/// this macro with [`KeyCode::Char`], since it will probably never
425/// match.
426///
427/// In the future, I might try to rectify this, by detecting when a
428/// key is "supposed" to be shifted, and then manually sending a
429/// modified [`KeyEvent`], which includes the shift modifier, so
430/// pattern matching can be more reliable.
431///
432/// [`KeyEvent`]: super::KeyEvent
433/// [`KeyCode::Char`]: super::KeyCode::Char
434/// [`Mode`]: super::Mode
435pub macro shift {
436    ($($chars:literal)|+) => {
437        $crate::mode::KeyEvent {
438            code: $crate::mode::KeyCode::Char($($chars)|+),
439            modifiers: $crate::mode::KeyMod::SHIFT,
440            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
441        }
442    },
443    // Straight up useless tbh.
444    ($char:ident @ $chars:literal) => {
445        $crate::mode::KeyEvent {
446            code: $crate::mode::KeyCode::Char($char @ $chars),
447            modifiers: $crate::mode::KeyMod::SHIFT,
448            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
449        }
450    },
451    ($char:ident @ ($($chars:literal)|+)) => {
452        $crate::mode::KeyEvent {
453            code: $crate::mode::KeyCode::Char($char @ ($($chars)|+)),
454            modifiers: $crate::mode::KeyMod::SHIFT,
455            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
456        }
457    },
458    // I use $excl here in order to make it look like part of the macro,
459    // so rust analyzer properly highlights it.
460    ($modif:ident$excl:tt($($tokens:tt)+)) => {
461        $crate::mode::KeyEvent {
462            code: $modif$excl(@code $($tokens)+),
463            modifiers: $modif$excl(@modif [SHIFT] $($tokens)+),
464            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
465        }
466    },
467    ($code:pat) => {
468        $crate::mode::KeyEvent {
469            code: $code,
470            modifiers: $crate::mode::KeyMod::SHIFT,
471            kind: $crate::mode::KeyEventKind::Press | $crate::mode::KeyEventKind::Repeat, ..
472        }
473    },
474
475    (@code $modif:ident$excl:tt($($tokens:tt)+)) => {
476        $modif$excl(@code $($tokens)+)
477    },
478    (@code $($chars:literal)|+) => {
479        $crate::mode::KeyCode::Char($($chars)|+)
480    },
481    (@code $char:ident @ $chars:literal) => {
482        $crate::mode::KeyCode::Char($char @ $chars)
483    },
484    (@code $char:ident @ ($($chars:literal)|+)) => {
485        $crate::mode::KeyCode::Char($char @ ($($chars)|+))
486    },
487    (@code $code:pat) => {
488        $code
489    },
490
491    (@modif [$($list:ident),+] $modif:ident$excl:tt($($tokens:tt)+)) => {
492        $modif$excl(@modif [SHIFT, $($list),+] $($tokens)+)
493    },
494    (@modif [$($list:ident),+] $($other:tt)+) => {
495        $crate::mode::join_modifiers![SHIFT, $($list),+]
496    },
497}
498
499/// A simple macro to join [`KeyMod`]s into a `const` `KeyMod` that
500/// can be used in pattern matching
501pub macro join_modifiers {
502    [ALT, CONTROL] => { $crate::mode::ALT_CONTROL },
503    [CONTROL, ALT] => { $crate::mode::ALT_CONTROL },
504    [ALT, SHIFT] => { $crate::mode::ALT_SHIFT },
505    [SHIFT, ALT] => { $crate::mode::ALT_SHIFT },
506    [CONTROL, SHIFT] => { $crate::mode::CONTROL_SHIFT },
507    [SHIFT, CONTROL] => { $crate::mode::CONTROL_SHIFT },
508    [ALT, CONTROL, SHIFTl] => { $crate::mode::ALT_CONTROL_SHIFT },
509    [ALT, SHIFT, CONTROLl] => { $crate::mode::ALT_CONTROL_SHIFT },
510    [CONTROL, ALT, SHIFTl] => { $crate::mode::ALT_CONTROL_SHIFT },
511    [CONTROL, SHIFT, ALTl] => { $crate::mode::ALT_CONTROL_SHIFT },
512    [SHIFT, ALT, CONTROLl] => { $crate::mode::ALT_CONTROL_SHIFT },
513    [SHIFT, CONTROL, ALTl] => { $crate::mode::ALT_CONTROL_SHIFT },
514    [$($other:tt)+] => {
515        compile_error!("Don't nest identical modifier macros while forming patterns")
516    }
517}
518
519/// `const` [`KeyMod`] used in pattern matching
520#[doc(hidden)]
521pub const ALT_CONTROL: KeyMod = KeyMod::ALT.union(KeyMod::CONTROL);
522
523/// `const` [`KeyMod`] used in pattern matching
524#[doc(hidden)]
525pub const ALT_SHIFT: KeyMod = KeyMod::ALT.union(KeyMod::SHIFT);
526
527/// `const` [`KeyMod`] used in pattern matching
528#[doc(hidden)]
529pub const CONTROL_SHIFT: KeyMod = KeyMod::CONTROL.union(KeyMod::SHIFT);
530
531/// `const` [`KeyMod`] used in pattern matching
532#[doc(hidden)]
533pub const ALT_CONTROL_SHIFT: KeyMod = KeyMod::ALT.union(KeyMod::CONTROL).union(KeyMod::SHIFT);