Skip to content

Commit a6f2e61

Browse files
committed
Add is_ and as_ methods to the event enums
Often application code only cares about a small subset of possible events. These methods make it simpler to write code which checks whether an event is a particular event type or converts events into the specific type (returning an Option). This can help simplify some nested match blocks. E.g.: ```rust match event { Event::Key(key) if key.kind == KeyEventKind::Press => { ... } } ``` becomes: ```rust if let Some(key) = event.as_key_press() { ... } ``` Similar flexible methods are aded across all the event enums: - Event::is_focus_gained() - Event::is_focus_lost() - Event::is_key() - Event::is_mouse() - Event::is_paste() - Event::is_resize() - Event::is_key_press() - Event::as_key_press() -> Option<&KeyEvent> - MouseEventKind::is_*() - MouseButton::is_*() - KeyEventKind::is_*() - KeyEvent::is_press() - KeyEvent::is_release() - KeyEvent::is_repeat() - KeyCode::is_*() - KeyCode::is_function_key(n) - KeyCode::is_char(c) - KeyCode::as_char() -> Option<char> - KeyCode::is_media_key(media) - KeyCode::is_modifier(modifier)
1 parent fc8f977 commit a6f2e61

File tree

2 files changed

+248
-5
lines changed

2 files changed

+248
-5
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ use-dev-tty = ["filedescriptor", "rustix/process"]
4646

4747
[dependencies]
4848
bitflags = { version = "2.3" }
49+
derive_more = { version = "1.0.0", features = ["is_variant"] }
4950
document-features = "0.2.10"
5051
futures-core = { version = "0.3", optional = true, default-features = false }
5152
parking_lot = "0.12"

src/event.rs

Lines changed: 247 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub(crate) mod stream;
126126
pub(crate) mod sys;
127127
pub(crate) mod timeout;
128128

129+
use derive_more::derive::IsVariant;
129130
#[cfg(feature = "event-stream")]
130131
pub use stream::EventStream;
131132

@@ -543,7 +544,7 @@ impl Command for PopKeyboardEnhancementFlags {
543544
/// Represents an event.
544545
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
545546
#[cfg_attr(not(feature = "bracketed-paste"), derive(Copy))]
546-
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
547+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash, IsVariant)]
547548
pub enum Event {
548549
/// The terminal gained focus
549550
FocusGained,
@@ -562,6 +563,63 @@ pub enum Event {
562563
Resize(u16, u16),
563564
}
564565

566+
impl Event {
567+
/// Returns `true` if the event is a key press event.
568+
///
569+
/// This is useful for waiting for any key press event, regardless of the key that was pressed.
570+
///
571+
/// Returns `false` for key release and repeat events (as well as for non-key events).
572+
///
573+
/// # Examples
574+
///
575+
/// The following code runs a loop that processes events until a key press event is encountered:
576+
///
577+
/// ```no_run
578+
/// use crossterm::event;
579+
///
580+
/// while !event::read()?.is_key_press() {
581+
/// // ...
582+
/// }
583+
/// ```
584+
#[inline]
585+
pub fn is_key_press(&self) -> bool {
586+
matches!(
587+
self,
588+
Event::Key(KeyEvent {
589+
kind: KeyEventKind::Press,
590+
..
591+
})
592+
)
593+
}
594+
595+
/// Returns an Option containing the KeyEvent if the event is a key press event.
596+
///
597+
/// This is a convenience method that makes apps that only care about key press events, and not
598+
/// key release or repeat events (or non-key events), easier to write.
599+
///
600+
/// Returns `None` for key release and repeat events (as well as for non-key events).
601+
///
602+
/// # Examples
603+
///
604+
/// The following code runs a loop that only processes key press events:
605+
///
606+
/// ```no_run
607+
/// use crossterm::event;
608+
///
609+
/// while let Ok(event) = event::read() {
610+
/// if let Some(key) = event.as_key_press() {
611+
/// // ...
612+
/// }
613+
/// }
614+
#[inline]
615+
pub fn as_key_press(&self) -> Option<&KeyEvent> {
616+
match self {
617+
Event::Key(event) if self.is_key_press() => Some(event),
618+
_ => None,
619+
}
620+
}
621+
}
622+
565623
/// Represents a mouse event.
566624
///
567625
/// # Platform-specific Notes
@@ -600,7 +658,7 @@ pub struct MouseEvent {
600658
/// `MouseEventKind::Up` and `MouseEventKind::Drag` events. `MouseButton::Left`
601659
/// is returned if we don't know which button was used.
602660
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
603-
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
661+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, IsVariant)]
604662
pub enum MouseEventKind {
605663
/// Pressed mouse button. Contains the button that was pressed.
606664
Down(MouseButton),
@@ -622,7 +680,7 @@ pub enum MouseEventKind {
622680

623681
/// Represents a mouse button.
624682
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
625-
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
683+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, IsVariant)]
626684
pub enum MouseButton {
627685
/// Left mouse button.
628686
Left,
@@ -702,7 +760,7 @@ impl Display for KeyModifiers {
702760

703761
/// Represents a keyboard event kind.
704762
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
705-
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
763+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, IsVariant)]
706764
pub enum KeyEventKind {
707765
Press,
708766
Repeat,
@@ -806,6 +864,21 @@ impl KeyEvent {
806864
}
807865
self
808866
}
867+
868+
/// Returns whether the key event is a press event.
869+
pub fn is_press(&self) -> bool {
870+
self.kind.is_press()
871+
}
872+
873+
/// Returns whether the key event is a release event.
874+
pub fn is_release(&self) -> bool {
875+
self.kind.is_release()
876+
}
877+
878+
/// Returns whether the key event is a repeat event.
879+
pub fn is_repeat(&self) -> bool {
880+
self.kind.is_repeat()
881+
}
809882
}
810883

811884
impl From<KeyCode> for KeyEvent {
@@ -1006,7 +1079,7 @@ impl Display for ModifierKeyCode {
10061079
}
10071080

10081081
/// Represents a key.
1009-
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
1082+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, IsVariant)]
10101083
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
10111084
pub enum KeyCode {
10121085
/// Backspace key (Delete on macOS, Backspace on other platforms).
@@ -1040,10 +1113,12 @@ pub enum KeyCode {
10401113
/// F key.
10411114
///
10421115
/// `KeyCode::F(1)` represents F1 key, etc.
1116+
#[is_variant(ignore)]
10431117
F(u8),
10441118
/// A character.
10451119
///
10461120
/// `KeyCode::Char('c')` represents `c` character, etc.
1121+
#[is_variant(ignore)]
10471122
Char(char),
10481123
/// Null.
10491124
Null,
@@ -1096,16 +1171,100 @@ pub enum KeyCode {
10961171
/// **Note:** these keys can only be read if
10971172
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] has been enabled with
10981173
/// [`PushKeyboardEnhancementFlags`].
1174+
#[is_variant(ignore)]
10991175
Media(MediaKeyCode),
11001176
/// A modifier key.
11011177
///
11021178
/// **Note:** these keys can only be read if **both**
11031179
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] and
11041180
/// [`KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES`] have been enabled with
11051181
/// [`PushKeyboardEnhancementFlags`].
1182+
#[is_variant(ignore)]
11061183
Modifier(ModifierKeyCode),
11071184
}
11081185

1186+
impl KeyCode {
1187+
/// Returns `true` if the key code is the given function key.
1188+
///
1189+
/// # Examples
1190+
///
1191+
/// ```
1192+
/// # use crossterm::event::KeyCode;
1193+
/// assert!(KeyCode::F(1).is_function(1));
1194+
/// assert!(!KeyCode::F(1).is_function(2));
1195+
/// ```
1196+
pub fn is_function_key(&self, n: u8) -> bool {
1197+
matches!(self, KeyCode::F(m) if *m == n)
1198+
}
1199+
1200+
/// Returns `true` if the key code is the given character.
1201+
///
1202+
/// # Examples
1203+
///
1204+
/// ```
1205+
/// # use crossterm::event::KeyCode;
1206+
/// assert!(KeyCode::Char('a').is_char('a'));
1207+
/// assert!(!KeyCode::Char('a').is_char('b'));
1208+
/// assert!(!KeyCode::F(1).is_char('a'));
1209+
/// ```
1210+
pub fn is_char(&self, c: char) -> bool {
1211+
matches!(self, KeyCode::Char(m) if *m == c)
1212+
}
1213+
1214+
/// Returns the character if the key code is a character key.
1215+
///
1216+
/// Returns `None` if the key code is not a character key.
1217+
///
1218+
/// # Examples
1219+
///
1220+
/// ```
1221+
/// # use crossterm::event::KeyCode;
1222+
/// assert_eq!(KeyCode::Char('a').as_char(), Some('a'));
1223+
/// assert_eq!(KeyCode::F(1).as_char(), None);
1224+
/// ```
1225+
pub fn as_char(&self) -> Option<char> {
1226+
match self {
1227+
KeyCode::Char(c) => Some(*c),
1228+
_ => None,
1229+
}
1230+
}
1231+
1232+
/// Returns `true` if the key code is the given media key.
1233+
///
1234+
/// **Note:** this method requires
1235+
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] to be enabled with
1236+
/// [`PushKeyboardEnhancementFlags`].
1237+
///
1238+
/// # Examples
1239+
///
1240+
/// ```
1241+
/// # use crossterm::event::{KeyCode, MediaKeyCode};
1242+
/// assert!(KeyCode::Media(MediaKeyCode::Play).is_media(MediaKeyCode::Play));
1243+
/// assert!(!KeyCode::Media(MediaKeyCode::Play).is_media(MediaKeyCode::Pause));
1244+
/// ```
1245+
pub fn is_media_key(&self, media: MediaKeyCode) -> bool {
1246+
matches!(self, KeyCode::Media(m) if *m == media)
1247+
}
1248+
1249+
/// Returns `true` if the key code is the given modifier key.
1250+
///
1251+
/// **Note:** this method requires both
1252+
/// [`KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES`] and
1253+
/// [`KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES`] to be enabled with
1254+
/// [`PushKeyboardEnhancementFlags`].
1255+
///
1256+
/// # Examples
1257+
///
1258+
/// ```
1259+
/// # use crossterm::event::{KeyCode, ModifierKeyCode};
1260+
/// assert!(KeyCode::Modifier(ModifierKeyCode::LeftShift).is_modifier(ModifierKeyCode::LeftShift));
1261+
/// assert!(!KeyCode::Modifier(ModifierKeyCode::LeftShift).is_modifier(ModifierKeyCode::RightShift));
1262+
/// ```
1263+
pub fn is_modifier(&self, modifier: ModifierKeyCode) -> bool {
1264+
matches!(self, KeyCode::Modifier(m) if *m == modifier)
1265+
}
1266+
}
1267+
11091268
impl Display for KeyCode {
11101269
/// Formats the `KeyCode` using the given formatter.
11111270
///
@@ -1324,4 +1483,87 @@ mod tests {
13241483
assert_eq!(format!("{}", Modifier(RightAlt)), "Right Alt");
13251484
assert_eq!(format!("{}", Modifier(RightSuper)), "Right Super");
13261485
}
1486+
1487+
#[test]
1488+
fn test_event_is() {
1489+
let event = Event::FocusGained;
1490+
assert!(event.is_focus_gained());
1491+
assert!(!event.is_key());
1492+
1493+
let event = Event::FocusLost;
1494+
assert!(event.is_focus_lost());
1495+
assert!(!event.is_key());
1496+
1497+
let event = Event::Resize(1, 1);
1498+
assert!(event.is_resize());
1499+
assert!(!event.is_key());
1500+
1501+
let event = Event::Key(KeyCode::Esc.into());
1502+
assert!(event.is_key());
1503+
assert!(!event.is_focus_gained());
1504+
1505+
let event = Event::Mouse(MouseEvent {
1506+
kind: MouseEventKind::Down(MouseButton::Left),
1507+
column: 1,
1508+
row: 1,
1509+
modifiers: KeyModifiers::empty(),
1510+
});
1511+
assert!(event.is_mouse());
1512+
assert!(!event.is_key());
1513+
}
1514+
1515+
const ESC_PRESSED: KeyEvent =
1516+
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Press);
1517+
const ESC_RELEASED: KeyEvent =
1518+
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Release);
1519+
const ESC_REPEAT: KeyEvent =
1520+
KeyEvent::new_with_kind(KeyCode::Esc, KeyModifiers::empty(), KeyEventKind::Repeat);
1521+
1522+
#[test]
1523+
fn test_event_is_key_press() {
1524+
let event = Event::Key(ESC_PRESSED);
1525+
assert!(event.is_key_press());
1526+
1527+
let event = Event::Key(ESC_RELEASED);
1528+
assert!(!event.is_key_press());
1529+
1530+
let event = Event::Key(ESC_REPEAT);
1531+
assert!(!event.is_key_press());
1532+
1533+
let event = Event::FocusGained;
1534+
assert!(!event.is_key_press());
1535+
}
1536+
1537+
#[test]
1538+
fn test_event_as_key_press() {
1539+
let event = Event::Key(ESC_PRESSED);
1540+
assert_eq!(event.as_key_press(), Some(&ESC_PRESSED));
1541+
1542+
let event = Event::Key(ESC_RELEASED);
1543+
assert_eq!(event.as_key_press(), None);
1544+
1545+
let event = Event::Key(ESC_REPEAT);
1546+
assert_eq!(event.as_key_press(), None);
1547+
1548+
let event = Event::FocusGained;
1549+
assert_eq!(event.as_key_press(), None);
1550+
}
1551+
1552+
#[test]
1553+
fn test_key_event_is() {
1554+
let event = ESC_PRESSED;
1555+
assert!(event.is_press());
1556+
assert!(!event.is_release());
1557+
assert!(!event.is_repeat());
1558+
1559+
let event = ESC_RELEASED;
1560+
assert!(!event.is_press());
1561+
assert!(event.is_release());
1562+
assert!(!event.is_repeat());
1563+
1564+
let event = ESC_REPEAT;
1565+
assert!(!event.is_press());
1566+
assert!(!event.is_release());
1567+
assert!(event.is_repeat());
1568+
}
13271569
}

0 commit comments

Comments
 (0)