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);