diff --git a/examples/key-display.rs b/examples/key-display.rs new file mode 100644 index 00000000..c5ea7b8d --- /dev/null +++ b/examples/key-display.rs @@ -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(()) +} diff --git a/src/event.rs b/src/event.rs index 34644676..3dd120d0 100644 --- a/src/event.rs +++ b/src/event.rs @@ -98,7 +98,7 @@ use crate::event::{ }; use crate::{csi, Command}; use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; -use std::fmt; +use std::fmt::{self, Display}; use std::time::Duration; 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 + /// . + /// + /// On Windows, the super key is displayed as "Windows" and the control key is displayed as + /// "Ctrl". See + /// . + /// + /// 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. #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] @@ -801,17 +850,37 @@ pub enum MediaKeyCode { 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`]). #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum ModifierKeyCode { /// Left Shift key. LeftShift, - /// Left Control key. + /// Left Control key. (Control on macOS, Ctrl on other platforms) LeftControl, - /// Left Alt key. + /// Left Alt key. (Option on macOS, Alt on other platforms) LeftAlt, - /// Left Super key. + /// Left Super key. (Command on macOS, Windows on Windows, Super on other platforms) LeftSuper, /// Left Hyper key. LeftHyper, @@ -819,11 +888,11 @@ pub enum ModifierKeyCode { LeftMeta, /// Right Shift key. RightShift, - /// Right Control key. + /// Right Control key. (Control on macOS, Ctrl on other platforms) RightControl, - /// Right Alt key. + /// Right Alt key. (Option on macOS, Alt on other platforms) RightAlt, - /// Right Super key. + /// Right Super key. (Command on macOS, Windows on Windows, Super on other platforms) RightSuper, /// Right Hyper key. RightHyper, @@ -835,11 +904,74 @@ pub enum ModifierKeyCode { 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 + /// . + /// + /// On Windows, the super key is displayed as "Windows" and the control key is displayed as + /// "Ctrl". See + /// . + /// + /// 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. #[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum KeyCode { - /// Backspace key. + /// Backspace key (Delete on macOS, Backspace on other platforms). Backspace, /// Enter key. Enter, @@ -863,7 +995,7 @@ pub enum KeyCode { Tab, /// Shift + Tab key. BackTab, - /// Delete key. + /// Delete key. (Fn+Delete on macOS, Delete on other platforms) Delete, /// Insert key. Insert, @@ -936,6 +1068,66 @@ pub enum KeyCode { 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 + /// . + /// + /// 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. /// /// Encapsulates publicly available `Event` with additional internal @@ -960,7 +1152,10 @@ mod tests { use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; - use super::{KeyCode, KeyEvent, KeyModifiers}; + use super::*; + use KeyCode::*; + use MediaKeyCode::*; + use ModifierKeyCode::*; #[test] fn test_equality() { @@ -991,4 +1186,104 @@ mod tests { assert_eq!(lowercase_d_with_shift_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"); + } }