keymaps 1.2.0

A rust crate which provides a standardized encoding for key codes
Documentation
// keymaps - A rust crate which provides a standardized encoding for key codes
//
// Copyright (C) 2025 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// This file is part of the keymaps crate.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/lgpl-3.0.txt>.

use tokenizer::Mode;

use crate::{
    error,
    key_repr::{Key, KeyModifiers, KeyValue, MediaKeyCode},
};

#[cfg(feature = "modifier-keys")]
use crate::key_repr::ModifierKeyCode;
#[cfg(feature = "mouse-keys")]
use crate::key_repr::MouseKeyValue;

use self::tokenizer::{Token, Tokenizer};

pub(crate) mod tokenizer;

pub(crate) struct Parser<'a> {
    ts: Tokenizer<'a>,
}

impl<'a> Parser<'a> {
    pub(crate) fn new(input: &'a str) -> Self {
        Self {
            ts: Tokenizer::new(input),
        }
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.ts.is_empty()
    }

    /// Parse a key build up of both Modifiers and a value, for example: `<C-A>`.
    pub(crate) fn parse_full_key(&mut self, dash_index: usize) -> Result<Key, error::KeyParse> {
        self.ts.expect(Token::AngularBracketOpen)?;

        let modifiers = self.parse_modifiers(dash_index)?;

        assert_eq!(self.ts.index(), dash_index);
        self.ts.expect(Token::Dash)?;

        let value = self.parse_value()?;

        self.ts.expect(Token::AngularBracketClose)?;
        Ok(Key { value, modifiers })
    }

    pub(crate) fn next_key(&mut self) -> Result<Key, error::KeyParse> {
        let value;
        let modifiers;

        if self.ts.peek(Token::AngularBracketOpen)? {
            if let Some(dash_index) = self.ts.find(Token::Dash)? {
                if let Some(abc_index) = self.ts.find(Token::AngularBracketClose)? {
                    if abc_index < dash_index {
                        // The dash comes after the current key is finished (it could be in the
                        // next key)
                        value = self.parse_special_value()?;
                        modifiers = KeyModifiers::default();
                    } else {
                        return self.parse_full_key(dash_index);
                    }
                } else {
                    return self.parse_full_key(dash_index);
                }
            } else {
                value = self.parse_special_value()?;
                modifiers = KeyModifiers::default();
            }
        } else {
            value = self.parse_normal_value()?;
            modifiers = KeyModifiers::default();
        }

        Ok(Key { value, modifiers })
    }

    fn parse_modifiers(&mut self, dash_index: usize) -> Result<KeyModifiers, error::KeyParse> {
        enum Mod {
            Alt,
            Ctrl,
            Meta,
            Shift,
        }

        let tokens: String = self
            .ts
            .take_while(|ch| ch != Token::Dash)?
            .into_iter()
            .map(Token::to_char)
            .collect();
        assert_eq!(self.ts.index(), dash_index);

        let modifiers: Vec<Mod> = if tokens.contains('+') {
            // Only long modifiers can be separated by pluses. Thus parse them.
            tokens
                .split('+')
                .map(|modifier| match modifier {
                    "Alt" => Ok(Mod::Alt),
                    "Ctrl" => Ok(Mod::Ctrl),
                    "Meta" | "Super" => Ok(Mod::Meta),
                    "Shift" => Ok(Mod::Shift),
                    other => Err(error::KeyParse::UnknownLongMod(other.to_owned())),
                })
                .collect::<Result<Vec<_>, error::KeyParse>>()
        } else {
            // Both a single long modifier and one or multiple short modifiers could be inputted.
            // Try to decide

            if tokens.contains(|ch: char| ch.is_lowercase()) {
                // A short modifier contains only uppercase letters.
                // Thus, this must be a single long modifier.

                match tokens.as_str() {
                    "Alt" => Ok(vec![Mod::Alt]),
                    "Ctrl" => Ok(vec![Mod::Ctrl]),
                    "Meta" | "Super" => Ok(vec![Mod::Meta]),
                    "Shift" => Ok(vec![Mod::Shift]),
                    other => Err(error::KeyParse::UnknownLongMod(other.to_owned())),
                }
            } else {
                tokens
                    .chars()
                    .map(|ch| match ch {
                        'A' => Ok(Mod::Alt),
                        'C' => Ok(Mod::Ctrl),
                        'M' => Ok(Mod::Meta),
                        'S' => Ok(Mod::Shift),
                        other => Err(error::KeyParse::UnknownShortMod(other)),
                    })
                    .collect()
            }
        }?;

        let mut output = KeyModifiers::default();
        macro_rules! set_mod {
            ($mod:ident) => {
                if output.$mod {
                    return Err(error::KeyParse::ModifierAlreadySet(
                        stringify!($mod).to_owned(),
                    ));
                } else {
                    output.$mod = true;
                }
            };
        }

        for modi in modifiers {
            match modi {
                Mod::Alt => set_mod!(alt),
                Mod::Ctrl => set_mod!(ctrl),
                Mod::Meta => set_mod!(meta),
                Mod::Shift => set_mod!(shift),
            }
        }

        Ok(output)
    }

    fn parse_value(&mut self) -> Result<KeyValue, error::KeyParse> {
        if self.ts.peek(Token::AngularBracketOpen)? {
            self.parse_special_value()
        } else {
            self.parse_normal_value()
        }
    }

    fn parse_special_value(&mut self) -> Result<KeyValue, error::KeyParse> {
        self.ts.expect(Token::AngularBracketOpen)?;

        let tokens: String = self
            .ts
            .take_while(|ch| ch != Token::AngularBracketClose)?
            .into_iter()
            .map(Token::to_char)
            .collect();

        let value = match tokens.as_str() {
            "BACKSPACE" => KeyValue::Backspace,
            "ENTER" => KeyValue::Enter,
            "LEFT" => KeyValue::Left,
            "RIGHT" => KeyValue::Right,
            "UP" => KeyValue::Up,
            "DOWN" => KeyValue::Down,
            "HOME" => KeyValue::Home,
            "END" => KeyValue::End,
            "PAGEUP" => KeyValue::PageUp,
            "PAGEDOWN" => KeyValue::PageDown,
            "TAB" => KeyValue::Tab,
            "BACKTAB" => KeyValue::BackTab,
            "DELETE" => KeyValue::Delete,
            "INSERT" => KeyValue::Insert,
            "ESC" => KeyValue::Esc,
            "CAPSLOCK" => KeyValue::CapsLock,
            "SCROLLlOCK" => KeyValue::ScrollLock,
            "NUMLOCK" => KeyValue::NumLock,
            "PRINTSCREEN" => KeyValue::PrintScreen,
            "PAUSE" => KeyValue::Pause,
            "MENU" => KeyValue::Menu,
            "KEYPADBEGIN" => KeyValue::KeypadBegin,
            "DASH" => KeyValue::Char('-'),
            "ABO" | "ANGULAR_BRACKET_OPEN" => KeyValue::Char('<'),
            "ABC" | "ANGULAR_BRACKET_CLOSE" => KeyValue::Char('>'),
            f_char if f_char.starts_with('F') => {
                let digits: u8 = f_char
                    .strip_prefix("F")
                    .expect("This starts with F, we checked")
                    .parse()
                    .map_err(error::KeyParse::CantParseFNumber)?;
                KeyValue::F(digits)
            }
            #[cfg(feature = "mouse-keys")]
            "MOUSE_RIGHT" => KeyValue::MouseKey(MouseKeyValue::Right),
            #[cfg(feature = "mouse-keys")]
            "MOUSE_LEFT" => KeyValue::MouseKey(MouseKeyValue::Left),
            #[cfg(feature = "mouse-keys")]
            "MOUSE_MIDDLE" => KeyValue::MouseKey(MouseKeyValue::Middle),

            #[cfg(feature = "modifier-keys")]
            "LEFT_ALT" => KeyValue::ModifierKey(ModifierKeyCode::LeftAlt),
            #[cfg(feature = "modifier-keys")]
            "RIGHT_ALT" => KeyValue::ModifierKey(ModifierKeyCode::RightAlt),
            #[cfg(feature = "modifier-keys")]
            "LEFT_CTRL" => KeyValue::ModifierKey(ModifierKeyCode::LeftCtrl),
            #[cfg(feature = "modifier-keys")]
            "RIGHT_CTRL" => KeyValue::ModifierKey(ModifierKeyCode::RightCtrl),
            #[cfg(feature = "modifier-keys")]
            "LEFT_META" | "LEFT_SUPER" => KeyValue::ModifierKey(ModifierKeyCode::LeftMeta),
            #[cfg(feature = "modifier-keys")]
            "RIGHT_META" | "RIGHT_SUPER" => KeyValue::ModifierKey(ModifierKeyCode::RightMeta),
            #[cfg(feature = "modifier-keys")]
            "LEFT_SHIFT" => KeyValue::ModifierKey(ModifierKeyCode::LeftShift),
            #[cfg(feature = "modifier-keys")]
            "RIGHT_SHIFT" => KeyValue::ModifierKey(ModifierKeyCode::RightShift),

            "MEDIA_PLAY" => KeyValue::Media(MediaKeyCode::Play),
            "MEDIA_PAUSE" => KeyValue::Media(MediaKeyCode::Pause),
            "MEDIA_PLAYPAUSE" => KeyValue::Media(MediaKeyCode::PlayPause),
            "MEDIA_REVERSE" => KeyValue::Media(MediaKeyCode::Reverse),
            "MEDIA_STOP" => KeyValue::Media(MediaKeyCode::Stop),
            "MEDIA_FASTFORWARD" => KeyValue::Media(MediaKeyCode::FastForward),
            "MEDIA_REWIND" => KeyValue::Media(MediaKeyCode::Rewind),
            "MEDIA_TRACKNEXT" => KeyValue::Media(MediaKeyCode::TrackNext),
            "MEDIA_TRACKPREVIOUS" => KeyValue::Media(MediaKeyCode::TrackPrevious),
            "MEDIA_RECORD" => KeyValue::Media(MediaKeyCode::Record),
            "MEDIA_LOWERVOLUME" => KeyValue::Media(MediaKeyCode::LowerVolume),
            "MEDIA_RAISEVOLUME" => KeyValue::Media(MediaKeyCode::RaiseVolume),
            "MEDIA_MUTEVOLUME" => KeyValue::Media(MediaKeyCode::MuteVolume),

            other => return Err(error::KeyParse::UnexpectedSpecialKey(other.to_owned())),
        };

        self.ts.expect(Token::AngularBracketClose)?;
        Ok(value)
    }
    fn parse_normal_value(&mut self) -> Result<KeyValue, error::KeyParse> {
        self.ts.mode = Mode::NormalValue;
        let output = KeyValue::Char(self.ts.expect_char()?);
        self.ts.mode = Mode::Default;

        Ok(output)
    }
}