Search code examples
serializationrustdeserializationtomlcrossterm

How can I deserialize chars in a TOML config file into crossterm::event::KeyCode?


I have a .toml config file containing some key bindings expressed as chars, I need to deserialize that file to a struct where each field is a crossterm::event::KeyCode. I'm using toml crate to parse the string. What I thought is that maybe there is a way to deserialize the string parsing keys as char and then mapping them to KeyCode.

config.toml:

key_0 = 'x'

key_bindings.rs:

use crossterm::event::KeyCode;
use serde::Deserialize;

#[derive(Deserialize, Debug)]
pub struct KeyBindings {
    pub key_0: KeyCode,
}

How can I deserialize the config.toml file to the KeyBindings struct?


Solution

  • To my knowledge, there's neither a crate to do this parsing from a string to a crossterm key, nor even a normalized format.

    You'll also have to deal with specifics of both Crossterm and your application. It especially depends on how you want to use the key. For example, if you want to parameterize a mapping from an event to an action, you may want to transform the key a little so that it matches what Crossterm may produce.

    You might use a function like this one:

    /// parse a string as a keyboard key definition.
    ///
    /// About the case:
    /// The char we receive as code from crossterm is usually lowercase
    /// but uppercase when it was typed with shift (i.e. we receive
    /// "g" for a lowercase, and "shift-G" for an uppercase)
    pub fn parse_key(raw: &str) -> Result<KeyEvent, ConfError> {
        fn bad_key(raw: &str) -> Result<KeyEvent, ConfError> {
            Err(ConfError::InvalidKey {
                raw: raw.to_owned(),
            })
        }
        let tokens: Vec<&str> = raw.split('-').collect();
        let last = tokens[tokens.len() - 1].to_ascii_lowercase();
        let mut code = match last.as_ref() {
            "esc" => Esc,
            "enter" => Enter,
            "left" => Left,
            "right" => Right,
            "up" => Up,
            "down" => Down,
            "home" => Home,
            "end" => End,
            "pageup" => PageUp,
            "pagedown" => PageDown,
            "backtab" => BackTab,
            "backspace" => Backspace,
            "del" => Delete,
            "delete" => Delete,
            "insert" => Insert,
            "ins" => Insert,
            "f1" => F(1),
            "f2" => F(2),
            "f3" => F(3),
            "f4" => F(4),
            "f5" => F(5),
            "f6" => F(6),
            "f7" => F(7),
            "f8" => F(8),
            "f9" => F(9),
            "f10" => F(10),
            "f11" => F(11),
            "f12" => F(12),
            "space" => Char(' '),
            "tab" => Tab,
            c if c.len() == 1 => Char(c.chars().next().unwrap()),
            _ => {
                return bad_key(raw);
            }
        };
        let mut modifiers = KeyModifiers::empty();
        if code == BackTab {
            // Crossterm always sends the shift key with  backtab
            modifiers.insert(KeyModifiers::SHIFT);
        }
        for token in tokens.iter().take(tokens.len() - 1) {
            match token.to_ascii_lowercase().as_ref() {
                "ctrl" => {
                    modifiers.insert(KeyModifiers::CONTROL);
                }
                "alt" => {
                    modifiers.insert(KeyModifiers::ALT);
                }
                "shift" => {
                    modifiers.insert(KeyModifiers::SHIFT);
                    if let Char(c) = code {
                        if c.is_ascii_lowercase() {
                            code = Char(c.to_ascii_uppercase());
                        }
                    }
                }
                _ => {
                    return bad_key(raw);
                }
            }
        }
        Ok(KeyEvent { code, modifiers })
    }
    

    source: keys.rs in broot