minus 5.6.1

An asynchronous data feedable terminal paging library for Rust
Documentation
#![allow(dead_code)]

use super::{Token, MODIFIERS};
use std::collections::HashMap;

use crossterm::event::{KeyCode, KeyEvent, KeyEventState, KeyModifiers};
use once_cell::sync::Lazy;

static SPECIAL_KEYS: Lazy<HashMap<&str, KeyCode>> = Lazy::new(|| {
    let mut map = HashMap::new();

    map.insert("enter", KeyCode::Enter);
    map.insert("tab", KeyCode::Tab);
    map.insert("backtab", KeyCode::BackTab);
    map.insert("backspace", KeyCode::Backspace);
    map.insert("up", KeyCode::Up);
    map.insert("down", KeyCode::Down);
    map.insert("right", KeyCode::Right);
    map.insert("left", KeyCode::Left);
    map.insert("pageup", KeyCode::PageUp);
    map.insert("pagedown", KeyCode::PageDown);
    map.insert("home", KeyCode::Home);
    map.insert("end", KeyCode::End);
    map.insert("insert", KeyCode::Insert);
    map.insert("delete", KeyCode::Delete);
    map.insert("esc", KeyCode::Esc);
    map.insert("f1", KeyCode::F(1));
    map.insert("f2", KeyCode::F(2));
    map.insert("f3", KeyCode::F(3));
    map.insert("f4", KeyCode::F(4));
    map.insert("f5", KeyCode::F(5));
    map.insert("f6", KeyCode::F(6));
    map.insert("f7", KeyCode::F(7));
    map.insert("f8", KeyCode::F(8));
    map.insert("f9", KeyCode::F(9));
    map.insert("f10", KeyCode::F(10));
    map.insert("f11", KeyCode::F(11));
    map.insert("f12", KeyCode::F(12));
    map.insert("dash", KeyCode::Char('-'));
    map.insert("space", KeyCode::Char(' '));

    map
});

struct KeySeq {
    code: Option<KeyCode>,
    modifiers: KeyModifiers,
}

impl Default for KeySeq {
    fn default() -> Self {
        Self {
            code: None,
            modifiers: KeyModifiers::NONE,
        }
    }
}

pub fn parse_key_event(text: &str) -> KeyEvent {
    let token_list = super::parse_tokens(text);

    KeySeq::gen_keyevent_from_tokenlist(&token_list, text)
}

impl KeySeq {
    fn gen_keyevent_from_tokenlist(token_list: &[Token], text: &str) -> KeyEvent {
        let mut ks = Self::default();

        let mut token_iter = token_list.iter().peekable();

        while let Some(token) = token_iter.peek() {
            match token {
                Token::Separator => {
                    token_iter.next();
                    assert!(
                        !(token_iter.peek() == Some(&&Token::Separator)),
                        "'{}': Multiple separators found consecutively",
                        text
                    );
                }
                Token::SingleChar(c) => {
                    token_iter.next();
                    if let Some(m) = MODIFIERS.get(c) {
                        if token_iter.next() == Some(&Token::Separator) {
                            assert!(
                                !ks.modifiers.contains(*m),
                                "'{}': Multiple instances of same modifier given",
                                text
                            );
                            ks.modifiers.insert(*m);
                        } else if ks.code.is_none() {
                            ks.code = Some(KeyCode::Char(*c));
                        } else {
                            panic!("'{}' Invalid key input sequence given", text);
                        }
                    } else if ks.code.is_none() {
                        ks.code = Some(KeyCode::Char(*c));
                    } else {
                        panic!("'{}': Invalid key input sequence given", text);
                    }
                }
                Token::MultipleChar(c) => {
                    let c = c.to_ascii_lowercase().to_string();
                    SPECIAL_KEYS.get(c.as_str()).map_or_else(
                        || panic!("'{}': Invalid key input sequence given", text),
                        |key| {
                            if ks.code.is_none() {
                                ks.code = Some(*key);
                            } else {
                                panic!("'{}': Invalid key input sequence given", text);
                            }
                        },
                    );
                    token_iter.next();
                }
            }
        }
        KeyEvent {
            code: ks.code.unwrap_or(KeyCode::Null),
            modifiers: ks.modifiers,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    }
}

#[cfg(test)]
#[test]
#[allow(clippy::too_many_lines)]
fn test_parse_key_event() {
    assert_eq!(
        parse_key_event("up"),
        KeyEvent {
            code: KeyCode::Up,
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("k"),
        KeyEvent {
            code: KeyCode::Char('k'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("j"),
        KeyEvent {
            code: KeyCode::Char('j'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("down"),
        KeyEvent {
            code: KeyCode::Down,
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("down"),
        KeyEvent {
            code: KeyCode::Down,
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("enter"),
        KeyEvent {
            code: KeyCode::Enter,
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("c-u"),
        KeyEvent {
            code: KeyCode::Char('u'),
            modifiers: KeyModifiers::CONTROL,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("c-d"),
        KeyEvent {
            code: KeyCode::Char('d'),
            modifiers: KeyModifiers::CONTROL,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("g"),
        KeyEvent {
            code: KeyCode::Char('g'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("s-g"),
        KeyEvent {
            code: KeyCode::Char('g'),
            modifiers: KeyModifiers::SHIFT,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("G"),
        KeyEvent {
            code: KeyCode::Char('G'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("pageup"),
        KeyEvent {
            code: KeyCode::PageUp,
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("pagedown"),
        KeyEvent {
            code: KeyCode::PageDown,
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("c-l"),
        KeyEvent {
            code: KeyCode::Char('l'),
            modifiers: KeyModifiers::CONTROL,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("q"),
        KeyEvent {
            code: KeyCode::Char('q'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("c-c"),
        KeyEvent {
            code: KeyCode::Char('c'),
            modifiers: KeyModifiers::CONTROL,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("/"),
        KeyEvent {
            code: KeyCode::Char('/'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("?"),
        KeyEvent {
            code: KeyCode::Char('?'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("n"),
        KeyEvent {
            code: KeyCode::Char('n'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("p"),
        KeyEvent {
            code: KeyCode::Char('p'),
            modifiers: KeyModifiers::NONE,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
    assert_eq!(
        parse_key_event("c-s-h"),
        KeyEvent {
            code: KeyCode::Char('h'),
            modifiers: KeyModifiers::CONTROL | KeyModifiers::SHIFT,
            kind: crossterm::event::KeyEventKind::Press,
            state: KeyEventState::NONE,
        }
    );
}