Skip to main content

Crate keymap

Crate keymap 

Source
Expand description

ยงkeymap-rs

Crates.io Docs.rs CI License

keymap-rs is a lightweight and extensible key mapping library for Rust that simplifies input processing for terminal user interfaces (TUIs), WebAssembly (WASM) applications, and more. It parses keymaps from derive macros or configuration files and maps them to actions from various input backends, including crossterm, termion, and wasm.

ยง๐Ÿ“‹ Table of Contents


ยง๐Ÿ”ง Features

  • โœ… Declarative Key Mappings: Define keymaps via simple configuration files (e.g., TOML, YAML) or directly in your code using derive macros.
  • โŒจ๏ธ Key Patterns: Supports single keys (a), combinations (ctrl-b), and multi-key sequences (ctrl-b n).
  • ๐Ÿง  Key Groups: Use built-in pattern matching for common key groups (@upper, @lower, @alpha, @alnum, and @any).
  • ๐Ÿ“ธ Key Group Capturing: Capture specific keypress data (like the actual char from @any or @digit) directly into your action enum variants at runtime.
  • ๐Ÿท๏ธ Custom Symbols & Help: Define custom display symbols (e.g., ^B) and help text for key bindings.
  • ๐Ÿงฌ Compile-Time Safety: The keymap_derive macro validates key syntax at compile time, preventing runtime errors.
  • ๐ŸŒ Backend-Agnostic: Works with multiple backends, including crossterm, termion, and wasm.
  • ๐Ÿชถ Lightweight & Extensible: Designed to be minimal and easy to extend with new backends or features.

ยง๐Ÿ•น๏ธ Demo

See keymap-rs in action with the WASM example:

Nyan Jump! Tur - Turing Machine Language
keymap-rs WASM Demo

Runtime Reload (Rebind keys at runtime)
Runtime keymap reload demo


ยง๐Ÿ“ฆ Installation

Add keymap to your Cargo.toml, enabling the feature for your chosen backend:

cargo add keymap --feature {crossterm | termion | wasm}

ยง๐Ÿš€ Usage

ยง1. Deriving KeyMap

The easiest way to get started is with the keymap::KeyMap derive macro.

Define your actions:

use keymap::KeyMap;

/// Application actions.
#[derive(KeyMap, Debug, PartialEq, Eq)]
pub enum Action {
    /// Quit the application.
    #[key("q", "esc")]
    Quit,

    /// Move left.
    #[key("left", "h")]
    Left,

    /// Move right.
    #[key("right", "l")]
    Right,

    /// Jump with custom key, display symbol and help text.
    #[key("space", symbol = "โฃ", help = "jump over obstacles")]
    Jump,

    /// Key Group Capturing action (e.g. tracking which character was pressed).
    /// `char` will be captured from any matched key group macro (like `@any` or `@digit`) at runtime.
    #[key("@any")]
    Shoot(char),
}

Use the generated keymap:

The KeyMap derive macro generates an associated keymap_config() method, which returns a Config<Action>.

// Retrieve the config
let config = Action::keymap_config();

// `key` is a key code from the input backend, e.g., `crossterm::event::KeyCode`
// You can lookup the default pre-instantiated action reference:
match config.get(&key) {
    Some(action) => match action {
        Action::Quit => break,
        Action::Jump => println!(
            "Jump! Symbol: {:?}, Help: {:?}",
            action.keymap_item().symbol,
            action.keymap_item().help)
        ),
        _ => println!("Action: {action:?} - {}", action.keymap_item().description),
    }
    _ => {}
}

// Or use Key Group Capturing to extract the actual `char` from `@any` or `@digit`!
if let Some(Action::Shoot(c)) = config.get_bound(&key) {
    println!("Captured key: {c}");
}

Note: keymap_derive automatically generates custom Serialize and Deserialize implementations for the derived enum, making your variants with captured data serialize as simple tags (e.g. "Shoot") out of the box so that Map deserialization continues to work flawlessly.

ยง2. Using External Configuration

keymap-rs also supports loading keymaps from external files (e.g., config.toml). This is useful for user-configurable keybindings.

Example config.toml:

# Override or add new keybindings
Jump = { keys = ["j", "up"], description = "Jump with 'j' or up arrow!" }
Quit = { keys = ["@any"], description = "Quit on any key press." }

This configuration can be loaded in two ways:

ยงConfig<T>: Load from File Only

This deserializes only the keybindings from the configuration file, ignoring any #[key("...")] attributes on your enum.

// This config will only contain 'Jump' and 'Quit' from the TOML file.
let config: Config<Action> = toml::from_str(&data)?;

Resulting keybindings:

KeyAction
"j", "up"Jump
@anyQuit
ยงDerivedConfig<T>: Merge Derived and File Configs

This merges keybindings from the #[key("...")] attributes with those from the configuration file. Keys from the external file will override any conflicting keys defined in the enum.

// This config contains keys from both the derive macro and the TOML file.
let config: DerivedConfig<Action> = toml::from_str(&data)?;

Resulting keybindings:

KeyActionSource
"j", "up"JumpConfig file (overrides "space")
"h", "left"LeftDerive macro
"l", "right"RightDerive macro
@anyQuitConfig file (overrides "q", "esc")

Note: When using DerivedConfig<T>, keys from the config file take precedence over derive macro keys for the same action.

ยง3. Compile-Time Validation

The keymap_derive macro validates all key strings at compile time, providing immediate feedback on invalid syntax.

Invalid Key Example:

#[derive(keymap::KeyMap)]
enum Action {
    // "enter2" is not a valid key.
    #[key("enter2", "ctrl-b n")]
    Invalid,
}

Compiler Error:

This code will fail to compile with a clear error message:

error: Invalid key "enter2": Parse error at position 5: expect end of input, found: 2
 --> keymap_derive/tests/derive.rs:7:11
  |
7 |     #[key("enter2", "ctrl-b n")]
  |           ^^^^^^^^

ยง4. Direct Key Parsing

Key strings can also be parsed directly into a KeyMap or a backend-specific key event.

use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
use keymap::{backend::crossterm::parse, Key, KeyMap, Modifier};

// Parse into a generic KeyMap
assert_eq!(
    "ctrl-l".parse::<KeyMap>(),
    Ok(KeyMap::new(Some(Modifier::Ctrl), Key::Char('l')))
);

// Or use the backend-specific parser
assert_eq!(
    parse("ctrl-l").unwrap(),
    KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL)
);

ยง๐Ÿ“ Key Syntax Reference

TypeDescriptionExample
Single KeysIndividual characters, special keys, arrow keys, and function keys.a, enter, up, f1
Key CombinationsKeys pressed simultaneously with modifiers (Ctrl, Alt, Shift).ctrl-c, alt-f4, ctrl-alt-shift-f1
Key SequencesMultiple keys pressed in order.g g (press g twice), ctrl-b n (Ctrl+B, then N), ctrl-b c (tmux-style new window)
Key GroupsPredefined patterns matching sets of keys.@upper (A-Z), @alpha (A-Z, a-z), @any (any key)
Custom SymbolsCustom display symbols for key bindings (e.g., for UI display).symbol = "^B"
Help TextShort help descriptions for key bindings.help = "jump"

Examples in Configuration:

# Single keys
Quit = { keys = ["q", "esc"] }

# Key combinations
Save = { keys = ["ctrl-s"] }
ForceQuit = { keys = ["ctrl-alt-f4"] }

# Key sequences
ShowGitStatus = { keys = ["g s"] }
NewTmuxWindow = { keys = ["ctrl-b c"] }

# Key groups
AnyLetter = { keys = ["@alpha"] }
AnyKey = { keys = ["@any"] }

ยง๐Ÿ“– Examples

For complete, runnable examples, check out the /examples directory.


ยง๐Ÿ“œ License

This project is licensed under the MIT License.


ยง๐Ÿ™Œ Contributions

Contributions, issues, and feature requests are welcome! Feel free to open an issue or submit a pull request.

Re-exportsยง

pub use config::Config;
pub use config::DerivedConfig;
pub use config::Item;
pub use config::KeyMapConfig;

Modulesยง

backend
Backends
config
Key mapping configuration for customizable input bindings.
node
Defines the core types for representing and parsing key combinations with modifiers.
parser
Parser

Structsยง

Matcher
A pattern matcher that maps sequences of Nodes to values.

Enumsยง

Error
Represents errors that can occur during keymap parsing or conversion.

Traitsยง

FromKeyMap
A trait for converting a KeyMap into a backend-specific key event type.
IntoKeyMap
A trait for converting a backend-specific key event into a KeyMap.
KeyGroupValue
A trait for types that can be extracted from a matched key group node.
ToKeyMap
A trait for converting a backend-specific key type into a KeyMap.

Type Aliasesยง

KeyMap
A type alias for a parsed keymap node tree.