Implement Display for KeyCode and KeyModifiers (#862)

Partially addresses #792
* Implement Display for KeyCode and KeyModifiers
* Add demo for Display implementation
This commit is contained in:
Josh McKinney 2024-05-03 10:23:07 -07:00 committed by GitHub
parent 7efe19da28
commit 39ef1e42ba
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 354 additions and 10 deletions

49
examples/key-display.rs Normal file
View File

@ -0,0 +1,49 @@
//! Demonstrates the display format of key events.
//!
//! This example demonstrates the display format of key events, which is useful for displaying in
//! the help section of a terminal application.
//!
//! cargo run --example key-display
use std::io;
use crossterm::event::{KeyEventKind, KeyModifiers};
use crossterm::{
event::{read, Event, KeyCode},
terminal::{disable_raw_mode, enable_raw_mode},
};
const HELP: &str = r#"Key display
- Press any key to see its display format
- Use Esc to quit
"#;
fn main() -> io::Result<()> {
println!("{}", HELP);
enable_raw_mode()?;
if let Err(e) = print_events() {
println!("Error: {:?}\r", e);
}
disable_raw_mode()?;
Ok(())
}
fn print_events() -> io::Result<()> {
loop {
let event = read()?;
match event {
Event::Key(event) if event.kind == KeyEventKind::Press => {
print!("Key pressed: ");
if event.modifiers != KeyModifiers::NONE {
print!("{}+", event.modifiers);
}
println!("{}\r", event.code);
if event.code == KeyCode::Esc {
break;
}
}
_ => {}
}
}
Ok(())
}

View File

@ -98,7 +98,7 @@ use crate::event::{
}; };
use crate::{csi, Command}; use crate::{csi, Command};
use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; use parking_lot::{MappedMutexGuard, Mutex, MutexGuard};
use std::fmt; use std::fmt::{self, Display};
use std::time::Duration; use std::time::Duration;
use bitflags::bitflags; use bitflags::bitflags;
@ -612,6 +612,55 @@ bitflags! {
} }
} }
impl Display for KeyModifiers {
/// Formats the key modifiers using the given formatter.
///
/// The key modifiers are joined by a `+` character.
///
/// # Platform-specific Notes
///
/// On macOS, the control, alt, and super keys is displayed as "Control", "Option", and
/// "Command" respectively. See
/// <https://support.apple.com/guide/applestyleguide/welcome/1.0/web>.
///
/// On Windows, the super key is displayed as "Windows" and the control key is displayed as
/// "Ctrl". See
/// <https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/keys-keyboard-shortcuts>.
///
/// On other platforms, the super key is referred to as "Super" and the control key is
/// displayed as "Ctrl".
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut first = true;
for modifier in self.iter() {
if !first {
f.write_str("+")?;
first = false;
}
match modifier {
KeyModifiers::SHIFT => f.write_str("Shift")?,
#[cfg(unix)]
KeyModifiers::CONTROL => f.write_str("Control")?,
#[cfg(windows)]
KeyModifiers::CONTROL => f.write_str("Ctrl")?,
#[cfg(target_os = "macos")]
KeyModifiers::ALT => f.write_str("Option")?,
#[cfg(not(target_os = "macos"))]
KeyModifiers::ALT => f.write_str("Alt")?,
#[cfg(target_os = "macos")]
KeyModifiers::SUPER => f.write_str("Command")?,
#[cfg(target_os = "windows")]
KeyModifiers::SUPER => f.write_str("Windows")?,
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
KeyModifiers::SUPER => f.write_str("Super")?,
KeyModifiers::HYPER => f.write_str("Hyper")?,
KeyModifiers::META => f.write_str("Meta")?,
_ => unreachable!(),
}
}
Ok(())
}
}
/// Represents a keyboard event kind. /// Represents a keyboard event kind.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
@ -801,17 +850,37 @@ pub enum MediaKeyCode {
MuteVolume, MuteVolume,
} }
impl Display for MediaKeyCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MediaKeyCode::Play => write!(f, "Play"),
MediaKeyCode::Pause => write!(f, "Pause"),
MediaKeyCode::PlayPause => write!(f, "Play/Pause"),
MediaKeyCode::Reverse => write!(f, "Reverse"),
MediaKeyCode::Stop => write!(f, "Stop"),
MediaKeyCode::FastForward => write!(f, "Fast Forward"),
MediaKeyCode::Rewind => write!(f, "Rewind"),
MediaKeyCode::TrackNext => write!(f, "Next Track"),
MediaKeyCode::TrackPrevious => write!(f, "Previous Track"),
MediaKeyCode::Record => write!(f, "Record"),
MediaKeyCode::LowerVolume => write!(f, "Lower Volume"),
MediaKeyCode::RaiseVolume => write!(f, "Raise Volume"),
MediaKeyCode::MuteVolume => write!(f, "Mute Volume"),
}
}
}
/// Represents a modifier key (as part of [`KeyCode::Modifier`]). /// Represents a modifier key (as part of [`KeyCode::Modifier`]).
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum ModifierKeyCode { pub enum ModifierKeyCode {
/// Left Shift key. /// Left Shift key.
LeftShift, LeftShift,
/// Left Control key. /// Left Control key. (Control on macOS, Ctrl on other platforms)
LeftControl, LeftControl,
/// Left Alt key. /// Left Alt key. (Option on macOS, Alt on other platforms)
LeftAlt, LeftAlt,
/// Left Super key. /// Left Super key. (Command on macOS, Windows on Windows, Super on other platforms)
LeftSuper, LeftSuper,
/// Left Hyper key. /// Left Hyper key.
LeftHyper, LeftHyper,
@ -819,11 +888,11 @@ pub enum ModifierKeyCode {
LeftMeta, LeftMeta,
/// Right Shift key. /// Right Shift key.
RightShift, RightShift,
/// Right Control key. /// Right Control key. (Control on macOS, Ctrl on other platforms)
RightControl, RightControl,
/// Right Alt key. /// Right Alt key. (Option on macOS, Alt on other platforms)
RightAlt, RightAlt,
/// Right Super key. /// Right Super key. (Command on macOS, Windows on Windows, Super on other platforms)
RightSuper, RightSuper,
/// Right Hyper key. /// Right Hyper key.
RightHyper, RightHyper,
@ -835,11 +904,74 @@ pub enum ModifierKeyCode {
IsoLevel5Shift, IsoLevel5Shift,
} }
impl Display for ModifierKeyCode {
/// Formats the modifier key using the given formatter.
///
/// # Platform-specific Notes
///
/// On macOS, the control, alt, and super keys is displayed as "Control", "Option", and
/// "Command" respectively. See
/// <https://support.apple.com/guide/applestyleguide/welcome/1.0/web>.
///
/// On Windows, the super key is displayed as "Windows" and the control key is displayed as
/// "Ctrl". See
/// <https://learn.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/term-collections/keys-keyboard-shortcuts>.
///
/// On other platforms, the super key is referred to as "Super".
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(target_os = "macos")]
match self {
ModifierKeyCode::LeftShift => write!(f, "Left Shift"),
ModifierKeyCode::LeftHyper => write!(f, "Left Hyper"),
ModifierKeyCode::LeftMeta => write!(f, "Left Meta"),
ModifierKeyCode::RightShift => write!(f, "Right Shift"),
ModifierKeyCode::RightHyper => write!(f, "Right Hyper"),
ModifierKeyCode::RightMeta => write!(f, "Right Meta"),
ModifierKeyCode::IsoLevel3Shift => write!(f, "Iso Level 3 Shift"),
ModifierKeyCode::IsoLevel5Shift => write!(f, "Iso Level 5 Shift"),
#[cfg(target_os = "macos")]
ModifierKeyCode::LeftControl => write!(f, "Left Control"),
#[cfg(not(target_os = "macos"))]
ModifierKeyCode::LeftControl => write!(f, "Left Ctrl"),
#[cfg(target_os = "macos")]
ModifierKeyCode::LeftAlt => write!(f, "Left Option"),
#[cfg(not(target_os = "macos"))]
ModifierKeyCode::LeftAlt => write!(f, "Left Alt"),
#[cfg(target_os = "macos")]
ModifierKeyCode::LeftSuper => write!(f, "Left Command"),
#[cfg(target_os = "windows")]
ModifierKeyCode::LeftSuper => write!(f, "Left Windows"),
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
ModifierKeyCode::LeftSuper => write!(f, "Left Super"),
#[cfg(target_os = "macos")]
ModifierKeyCode::RightControl => write!(f, "Right Control"),
#[cfg(not(target_os = "macos"))]
ModifierKeyCode::RightControl => write!(f, "Right Ctrl"),
#[cfg(target_os = "macos")]
ModifierKeyCode::RightAlt => write!(f, "Right Option"),
#[cfg(not(target_os = "macos"))]
ModifierKeyCode::RightAlt => write!(f, "Right Alt"),
#[cfg(target_os = "macos")]
ModifierKeyCode::RightSuper => write!(f, "Right Command"),
#[cfg(target_os = "windows")]
ModifierKeyCode::RightSuper => write!(f, "Right Windows"),
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
ModifierKeyCode::RightSuper => write!(f, "Right Super"),
}
}
}
/// Represents a key. /// Represents a key.
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum KeyCode { pub enum KeyCode {
/// Backspace key. /// Backspace key (Delete on macOS, Backspace on other platforms).
Backspace, Backspace,
/// Enter key. /// Enter key.
Enter, Enter,
@ -863,7 +995,7 @@ pub enum KeyCode {
Tab, Tab,
/// Shift + Tab key. /// Shift + Tab key.
BackTab, BackTab,
/// Delete key. /// Delete key. (Fn+Delete on macOS, Delete on other platforms)
Delete, Delete,
/// Insert key. /// Insert key.
Insert, Insert,
@ -936,6 +1068,66 @@ pub enum KeyCode {
Modifier(ModifierKeyCode), Modifier(ModifierKeyCode),
} }
impl Display for KeyCode {
/// Formats the `KeyCode` using the given formatter.
///
/// # Platform-specific Notes
///
/// On macOS, the Backspace key is displayed as "Delete", the Delete key is displayed as "Fwd
/// Del", and the Enter key is displayed as "Return". See
/// <https://support.apple.com/guide/applestyleguide/welcome/1.0/web>.
///
/// On other platforms, the Backspace key is displayed as "Backspace", the Delete key is
/// displayed as "Del", and the Enter key is displayed as "Enter".
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
// On macOS, the Backspace key is called "Delete" and the Delete key is called "Fwd Del".
#[cfg(target_os = "macos")]
KeyCode::Backspace => write!(f, "Delete"),
#[cfg(target_os = "macos")]
KeyCode::Delete => write!(f, "Fwd Del"),
#[cfg(not(target_os = "macos"))]
KeyCode::Backspace => write!(f, "Backspace"),
#[cfg(not(target_os = "macos"))]
KeyCode::Delete => write!(f, "Del"),
#[cfg(target_os = "macos")]
KeyCode::Enter => write!(f, "Return"),
#[cfg(not(target_os = "macos"))]
KeyCode::Enter => write!(f, "Enter"),
KeyCode::Left => write!(f, "Left"),
KeyCode::Right => write!(f, "Right"),
KeyCode::Up => write!(f, "Up"),
KeyCode::Down => write!(f, "Down"),
KeyCode::Home => write!(f, "Home"),
KeyCode::End => write!(f, "End"),
KeyCode::PageUp => write!(f, "Page Up"),
KeyCode::PageDown => write!(f, "Page Down"),
KeyCode::Tab => write!(f, "Tab"),
KeyCode::BackTab => write!(f, "Back Tab"),
KeyCode::Insert => write!(f, "Insert"),
KeyCode::F(n) => write!(f, "F{}", n),
KeyCode::Char(c) => match c {
// special case for non-visible characters
' ' => write!(f, "Space"),
c => write!(f, "{}", c),
},
KeyCode::Null => write!(f, "Null"),
KeyCode::Esc => write!(f, "Esc"),
KeyCode::CapsLock => write!(f, "Caps Lock"),
KeyCode::ScrollLock => write!(f, "Scroll Lock"),
KeyCode::NumLock => write!(f, "Num Lock"),
KeyCode::PrintScreen => write!(f, "Print Screen"),
KeyCode::Pause => write!(f, "Pause"),
KeyCode::Menu => write!(f, "Menu"),
KeyCode::KeypadBegin => write!(f, "Begin"),
KeyCode::Media(media) => write!(f, "{}", media),
KeyCode::Modifier(modifier) => write!(f, "{}", modifier),
}
}
}
/// An internal event. /// An internal event.
/// ///
/// Encapsulates publicly available `Event` with additional internal /// Encapsulates publicly available `Event` with additional internal
@ -960,7 +1152,10 @@ mod tests {
use std::collections::hash_map::DefaultHasher; use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use super::{KeyCode, KeyEvent, KeyModifiers}; use super::*;
use KeyCode::*;
use MediaKeyCode::*;
use ModifierKeyCode::*;
#[test] #[test]
fn test_equality() { fn test_equality() {
@ -991,4 +1186,104 @@ mod tests {
assert_eq!(lowercase_d_with_shift_hash, uppercase_d_with_shift_hash); assert_eq!(lowercase_d_with_shift_hash, uppercase_d_with_shift_hash);
assert_eq!(uppercase_d_hash, uppercase_d_with_shift_hash); assert_eq!(uppercase_d_hash, uppercase_d_with_shift_hash);
} }
#[test]
fn keycode_display() {
#[cfg(target_os = "macos")]
{
assert_eq!(format!("{}", Backspace), "Delete");
assert_eq!(format!("{}", Delete), "Fwd Del");
assert_eq!(format!("{}", Enter), "Return");
}
#[cfg(not(target_os = "macos"))]
{
assert_eq!(format!("{}", Backspace), "Backspace");
assert_eq!(format!("{}", Delete), "Del");
assert_eq!(format!("{}", Enter), "Enter");
}
assert_eq!(format!("{}", Left), "Left");
assert_eq!(format!("{}", Right), "Right");
assert_eq!(format!("{}", Up), "Up");
assert_eq!(format!("{}", Down), "Down");
assert_eq!(format!("{}", Home), "Home");
assert_eq!(format!("{}", End), "End");
assert_eq!(format!("{}", PageUp), "Page Up");
assert_eq!(format!("{}", PageDown), "Page Down");
assert_eq!(format!("{}", Tab), "Tab");
assert_eq!(format!("{}", BackTab), "Back Tab");
assert_eq!(format!("{}", Insert), "Insert");
assert_eq!(format!("{}", F(1)), "F1");
assert_eq!(format!("{}", Char('a')), "a");
assert_eq!(format!("{}", Null), "Null");
assert_eq!(format!("{}", Esc), "Esc");
assert_eq!(format!("{}", CapsLock), "Caps Lock");
assert_eq!(format!("{}", ScrollLock), "Scroll Lock");
assert_eq!(format!("{}", NumLock), "Num Lock");
assert_eq!(format!("{}", PrintScreen), "Print Screen");
assert_eq!(format!("{}", KeyCode::Pause), "Pause");
assert_eq!(format!("{}", Menu), "Menu");
assert_eq!(format!("{}", KeypadBegin), "Begin");
}
#[test]
fn media_keycode_display() {
assert_eq!(format!("{}", Media(Play)), "Play");
assert_eq!(format!("{}", Media(MediaKeyCode::Pause)), "Pause");
assert_eq!(format!("{}", Media(PlayPause)), "Play/Pause");
assert_eq!(format!("{}", Media(Reverse)), "Reverse");
assert_eq!(format!("{}", Media(Stop)), "Stop");
assert_eq!(format!("{}", Media(FastForward)), "Fast Forward");
assert_eq!(format!("{}", Media(Rewind)), "Rewind");
assert_eq!(format!("{}", Media(TrackNext)), "Next Track");
assert_eq!(format!("{}", Media(TrackPrevious)), "Previous Track");
assert_eq!(format!("{}", Media(Record)), "Record");
assert_eq!(format!("{}", Media(LowerVolume)), "Lower Volume");
assert_eq!(format!("{}", Media(RaiseVolume)), "Raise Volume");
assert_eq!(format!("{}", Media(MuteVolume)), "Mute Volume");
}
#[test]
fn modifier_keycode_display() {
assert_eq!(format!("{}", Modifier(LeftShift)), "Left Shift");
assert_eq!(format!("{}", Modifier(LeftHyper)), "Left Hyper");
assert_eq!(format!("{}", Modifier(LeftMeta)), "Left Meta");
assert_eq!(format!("{}", Modifier(RightShift)), "Right Shift");
assert_eq!(format!("{}", Modifier(RightHyper)), "Right Hyper");
assert_eq!(format!("{}", Modifier(RightMeta)), "Right Meta");
assert_eq!(format!("{}", Modifier(IsoLevel3Shift)), "Iso Level 3 Shift");
assert_eq!(format!("{}", Modifier(IsoLevel5Shift)), "Iso Level 5 Shift");
}
#[cfg(target_os = "macos")]
#[test]
fn modifier_keycode_display_macos() {
assert_eq!(format!("{}", Modifier(LeftControl)), "Left Control");
assert_eq!(format!("{}", Modifier(LeftAlt)), "Left Option");
assert_eq!(format!("{}", Modifier(LeftSuper)), "Left Command");
assert_eq!(format!("{}", Modifier(RightControl)), "Right Control");
assert_eq!(format!("{}", Modifier(RightAlt)), "Right Option");
assert_eq!(format!("{}", Modifier(RightSuper)), "Right Command");
}
#[cfg(target_os = "windows")]
#[test]
fn modifier_keycode_display_windows() {
assert_eq!(format!("{}", Modifier(LeftControl)), "Left Ctrl");
assert_eq!(format!("{}", Modifier(LeftAlt)), "Left Alt");
assert_eq!(format!("{}", Modifier(LeftSuper)), "Left Windows");
assert_eq!(format!("{}", Modifier(RightControl)), "Right Ctrl");
assert_eq!(format!("{}", Modifier(RightAlt)), "Right Alt");
assert_eq!(format!("{}", Modifier(RightSuper)), "Right Windows");
}
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
#[test]
fn modifier_keycode_display_other() {
assert_eq!(format!("{}", Modifier(LeftControl)), "Left Ctrl");
assert_eq!(format!("{}", Modifier(LeftAlt)), "Left Alt");
assert_eq!(format!("{}", Modifier(LeftSuper)), "Left Super");
assert_eq!(format!("{}", Modifier(RightControl)), "Right Ctrl");
assert_eq!(format!("{}", Modifier(RightAlt)), "Right Alt");
assert_eq!(format!("{}", Modifier(RightSuper)), "Right Super");
}
} }