keyboard-codes 0.3.0

Cross-platform keyboard key code mapping and conversion
Documentation
use crate::error::KeyParseError;
use crate::mapping::standard::parse_key_ignore_case;
use crate::parser::{normalize_key_name, parse_modifier_with_aliases};
use crate::types::{Key, KeyCodeMapper, Modifier, Platform};

/// Unified keyboard input type that can represent both keys and modifiers
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum KeyboardInput {
    /// Regular key
    Key(Key),
    /// Modifier key
    Modifier(Modifier),
}

impl KeyboardInput {
    /// Get the string representation of the keyboard input
    pub fn as_str(&self) -> &'static str {
        match self {
            KeyboardInput::Key(key) => key.as_str(),
            KeyboardInput::Modifier(modifier) => modifier.as_str(),
        }
    }

    /// Convert to platform-specific code
    pub fn to_code(&self, platform: Platform) -> usize {
        match self {
            KeyboardInput::Key(key) => KeyCodeMapper::to_code(key, platform),
            KeyboardInput::Modifier(modifier) => KeyCodeMapper::to_code(modifier, platform),
        }
    }

    /// Parse from platform-specific code
    pub fn from_code(code: usize, platform: Platform) -> Option<Self> {
        // Try to parse as Key first, then as Modifier
        Key::from_code(code, platform)
            .map(KeyboardInput::Key)
            .or_else(|| Modifier::from_code(code, platform).map(KeyboardInput::Modifier))
    }

    /// Check if this is a regular key
    pub fn is_key(&self) -> bool {
        matches!(self, KeyboardInput::Key(_))
    }

    /// Check if this is a modifier key
    pub fn is_modifier(&self) -> bool {
        matches!(self, KeyboardInput::Modifier(_))
    }

    /// Get the inner key if this is a Key variant
    pub fn as_key(&self) -> Option<Key> {
        match self {
            KeyboardInput::Key(key) => Some(*key),
            _ => None,
        }
    }

    /// Get the inner modifier if this is a Modifier variant
    pub fn as_modifier(&self) -> Option<Modifier> {
        match self {
            KeyboardInput::Modifier(modifier) => Some(*modifier),
            _ => None,
        }
    }

    /// Parse with alias and case-insensitive support
    pub fn parse_with_aliases(input: &str) -> Result<Self, KeyParseError> {
        if input.is_empty() {
            return Err(KeyParseError::UnknownKey("empty string".to_string()));
        }

        // First try to parse as modifier with aliases
        if let Ok(modifier) = parse_modifier_with_aliases(input) {
            return Ok(KeyboardInput::Modifier(modifier));
        }

        // Then try to parse as key with aliases
        let normalized_key = normalize_key_name(input);
        if let Ok(key) = parse_key_ignore_case(normalized_key) {
            return Ok(KeyboardInput::Key(key));
        }

        Err(KeyParseError::UnknownKey(input.to_string()))
    }
}

impl std::fmt::Display for KeyboardInput {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.as_str())
    }
}

impl std::str::FromStr for KeyboardInput {
    type Err = KeyParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // First try case-sensitive parsing for exact matches
        if let Ok(key) = s.parse::<Key>() {
            return Ok(KeyboardInput::Key(key));
        }

        if let Ok(modifier) = s.parse::<Modifier>() {
            return Ok(KeyboardInput::Modifier(modifier));
        }

        // Fall back to alias-aware parsing
        Self::parse_with_aliases(s)
    }
}

// Implement KeyCodeMapper for KeyboardInput
impl KeyCodeMapper for KeyboardInput {
    fn to_code(&self, platform: Platform) -> usize {
        self.to_code(platform)
    }

    fn from_code(code: usize, platform: Platform) -> Option<Self> {
        Self::from_code(code, platform)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_keyboard_input_creation() {
        let key_input = KeyboardInput::Key(Key::A);
        let modifier_input = KeyboardInput::Modifier(Modifier::Control);

        assert!(key_input.is_key());
        assert!(modifier_input.is_modifier());
        assert_eq!(key_input.as_key(), Some(Key::A));
        assert_eq!(modifier_input.as_modifier(), Some(Modifier::Control));
    }

    #[test]
    fn test_keyboard_input_from_str() {
        assert_eq!(
            "A".parse::<KeyboardInput>().unwrap(),
            KeyboardInput::Key(Key::A)
        );
        assert_eq!(
            "Control".parse::<KeyboardInput>().unwrap(),
            KeyboardInput::Modifier(Modifier::Control)
        );
        assert_eq!(
            "ctrl".parse::<KeyboardInput>().unwrap(),
            KeyboardInput::Modifier(Modifier::Control)
        );
        assert_eq!(
            "esc".parse::<KeyboardInput>().unwrap(),
            KeyboardInput::Key(Key::Escape)
        );
    }

    #[test]
    fn test_keyboard_input_to_code() {
        let key_input = KeyboardInput::Key(Key::Enter);
        let modifier_input = KeyboardInput::Modifier(Modifier::Shift);

        assert_eq!(key_input.to_code(Platform::Windows), 0x0D);
        assert_eq!(modifier_input.to_code(Platform::Windows), 0x10);
    }

    #[test]
    fn test_keyboard_input_from_code() {
        assert_eq!(
            KeyboardInput::from_code(0x41, Platform::Windows),
            Some(KeyboardInput::Key(Key::A))
        );
        assert_eq!(
            KeyboardInput::from_code(0x10, Platform::Windows),
            Some(KeyboardInput::Modifier(Modifier::Shift))
        );
    }

    #[test]
    fn test_keyboard_input_display() {
        assert_eq!(KeyboardInput::Key(Key::Enter).to_string(), "Enter");
        assert_eq!(
            KeyboardInput::Modifier(Modifier::Control).to_string(),
            "Control"
        );
    }
}