oo-ide 0.0.4

∞ is a terminal IDE focused on low distraction, high usability.
Documentation
use logos::Logos as _;

use crate::prelude::*;

use input::{Key, KeyEvent, Modifiers};
use shortcuts::lexer::Token;

pub fn parse_key_combo(input: &str) -> Result<KeyEvent> {
    let mut modifiers = Modifiers::empty();
    let mut key: Option<Key> = None;

    let lexer = Token::lexer(input);

    for token in lexer {
        match token.map_err(|_| anyhow!("Failed to parse {:?}", input))? {
            Token::Ctrl => modifiers.insert(Modifiers::CTRL),
            Token::Shift => modifiers.insert(Modifiers::SHIFT),
            Token::Alt => modifiers.insert(Modifiers::ALT),

            Token::Char(c) => key = Some(Key::Char(c.to_ascii_lowercase())),

            Token::FKey(n) => key = Some(Key::F(n)),

            Token::ArrowUp => key = Some(Key::ArrowUp),
            Token::ArrowDown => key = Some(Key::ArrowDown),
            Token::ArrowLeft => key = Some(Key::ArrowLeft),
            Token::ArrowRight => key = Some(Key::ArrowRight),

            Token::Enter => key = Some(Key::Enter),
            Token::Escape => key = Some(Key::Escape),
            Token::Tab => key = Some(Key::Tab),
            Token::Backspace => key = Some(Key::Backspace),
            Token::Delete => key = Some(Key::Delete),

            Token::Plus => {}
        }
    }

    Ok(KeyEvent {
        modifiers,
        key: key.ok_or_else(|| anyhow!("Missing key in shortcut '{input}'."))?,
    })
}

#[cfg(test)]
mod tests {
    use crate::{
        input::{Key, Modifiers},
        shortcuts::parser::parse_key_combo,
    };

    // --- Single keys ---
    #[test]
    fn test_parse_single_char() {
        let result = parse_key_combo("a").unwrap();
        assert_eq!(result.key, Key::Char('a'));
        assert!(result.modifiers.is_empty());
    }

    #[test]
    fn test_parse_upper_char_becomes_lower() {
        let result = parse_key_combo("A").unwrap();
        assert_eq!(result.key, Key::Char('a'));
    }

    #[test]
    fn test_parse_digit() {
        let result = parse_key_combo("5").unwrap();
        assert_eq!(result.key, Key::Char('5'));
    }

    #[test]
    fn test_parse_fkey() {
        let result = parse_key_combo("F12").unwrap();
        assert_eq!(result.key, Key::F(12));
    }

    #[test]
    fn test_parse_arrow_up() {
        let result = parse_key_combo("ArrowUp").unwrap();
        assert_eq!(result.key, Key::ArrowUp);
    }

    #[test]
    fn test_parse_arrow_down() {
        let result = parse_key_combo("ArrowDown").unwrap();
        assert_eq!(result.key, Key::ArrowDown);
    }

    #[test]
    fn test_parse_arrow_left() {
        let result = parse_key_combo("ArrowLeft").unwrap();
        assert_eq!(result.key, Key::ArrowLeft);
    }

    #[test]
    fn test_parse_arrow_right() {
        let result = parse_key_combo("ArrowRight").unwrap();
        assert_eq!(result.key, Key::ArrowRight);
    }

    #[test]
    fn test_parse_enter() {
        let result = parse_key_combo("Enter").unwrap();
        assert_eq!(result.key, Key::Enter);
    }

    #[test]
    fn test_parse_escape() {
        let result = parse_key_combo("Escape").unwrap();
        assert_eq!(result.key, Key::Escape);
    }

    #[test]
    fn test_parse_tab() {
        let result = parse_key_combo("Tab").unwrap();
        assert_eq!(result.key, Key::Tab);
    }

    #[test]
    fn test_parse_backspace() {
        let result = parse_key_combo("Backspace").unwrap();
        assert_eq!(result.key, Key::Backspace);
    }

    #[test]
    fn test_parse_delete() {
        let result = parse_key_combo("Delete").unwrap();
        assert_eq!(result.key, Key::Delete);
    }

    // --- Single modifiers ---
    #[test]
    fn test_parse_ctrl_a() {
        let result = parse_key_combo("Ctrl+a").unwrap();
        assert_eq!(result.key, Key::Char('a'));
        assert!(result.modifiers.contains(Modifiers::CTRL));
        assert!(!result.modifiers.contains(Modifiers::ALT));
        assert!(!result.modifiers.contains(Modifiers::SHIFT));
    }

    #[test]
    fn test_parse_shift_enter() {
        let result = parse_key_combo("Shift+Enter").unwrap();
        assert_eq!(result.key, Key::Enter);
        assert!(result.modifiers.contains(Modifiers::SHIFT));
    }

    #[test]
    fn test_parse_alt_f4() {
        let result = parse_key_combo("Alt+F4").unwrap();
        assert_eq!(result.key, Key::F(4));
        assert!(result.modifiers.contains(Modifiers::ALT));
    }

    // --- Multiple modifiers ---
    #[test]
    fn test_parse_ctrl_shift_c() {
        let result = parse_key_combo("Ctrl+Shift+c").unwrap();
        assert_eq!(result.key, Key::Char('c'));
        assert!(result.modifiers.contains(Modifiers::CTRL));
        assert!(result.modifiers.contains(Modifiers::SHIFT));
        assert!(!result.modifiers.contains(Modifiers::ALT));
    }

    #[test]
    fn test_parse_ctrl_alt_delete() {
        let result = parse_key_combo("Ctrl+Alt+Delete").unwrap();
        assert_eq!(result.key, Key::Delete);
        assert!(result.modifiers.contains(Modifiers::CTRL));
        assert!(result.modifiers.contains(Modifiers::ALT));
        assert!(!result.modifiers.contains(Modifiers::SHIFT));
    }

    #[test]
    fn test_parse_all_modifiers() {
        let result = parse_key_combo("Ctrl+Shift+Alt+x").unwrap();
        assert_eq!(result.key, Key::Char('x'));
        assert!(result.modifiers.contains(Modifiers::CTRL));
        assert!(result.modifiers.contains(Modifiers::SHIFT));
        assert!(result.modifiers.contains(Modifiers::ALT));
    }

    // --- Error cases ---
    #[test]
    fn test_parse_missing_key() {
        let result = parse_key_combo("Ctrl+");
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_only_modifiers() {
        let result = parse_key_combo("Ctrl+Shift+");
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_empty_string() {
        let result = parse_key_combo("");
        assert!(result.is_err());
    }

    #[test]
    fn test_parse_invalid_sequence() {
        // Only modifiers without a key should fail
        let result = parse_key_combo("Ctrl+Alt+Shift+");
        assert!(result.is_err());
    }
}