Skip to content

Commit 9a33aea

Browse files
authored
docs: improve and restructure README.md (#29)
* docs: improve and restructure README.md * chore: add wasm preview image
1 parent 501b745 commit 9a33aea

File tree

5 files changed

+138
-86
lines changed

5 files changed

+138
-86
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ name: CI
33
on:
44
push:
55
branches: ["main"]
6+
paths-ignore:
7+
- '**.md'
68
pull_request:
79
branches: ["main"]
10+
paths-ignore:
11+
- '**.md'
812

913
env:
1014
CARGO_TERM_COLOR: always

README.md

Lines changed: 123 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,152 +1,164 @@
11
# keymap-rs
22

3-
**keymap-rs** is a lightweight and extensible key mapping library for Rust applications. It supports parsing key mappings from configuration files and mapping them to actions based on input events from backends like [`crossterm`](https://crates.io/crates/crossterm), [`termion`](https://docs.rs/termion/latest/termion/), `wasm`, and others.
3+
[![Crates.io](https://img.shields.io/crates/v/keymap.svg)](https://crates.io/crates/keymap)
4+
[![Docs.rs](https://docs.rs/keymap/badge.svg)](https://docs.rs/keymap)
5+
[![CI](https://github.com/rezigned/keymap-rs/actions/workflows/ci.yml/badge.svg)](https://github.com/rezigned/keymap-rs/actions/workflows/ci.yml)
6+
[![License](https://img.shields.io/crates/l/keymap.svg)](https://github.com/rezigned/keymap-rs/blob/main/LICENSE)
7+
8+
**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`](https://crates.io/crates/crossterm), [`termion`](https://docs.rs/termion/latest/termion/), and [`wasm`](https://webassembly.org/).
49

510
---
611

7-
## 🔧 Features (v1.0.0)
12+
## 🔧 Features
813

9-
* ✅ Declarative key mappings via configuration (e.g., YAML, JSON, etc.)
10-
* ⌨️ Supports single keys (e.g. `a`, `enter`, `ctrl-b`, etc.) and key **sequences** (e.g. `ctrl-b n`)
11-
* 🧠 Supports **key groups**:
12-
* `@upper` – uppercase letters
13-
* `@lower` – lowercase letters
14-
* `@alpha` – all alphabetic characters
15-
* `@alnum` – alphanumeric
16-
* `@any` – match any key
17-
* 🧬 **Derive-based config parser** via `keymap_derive`
18-
* 🌐 Backend-agnostic (works with `crossterm`, `termion`, `wasm`, etc.)
19-
* 🪶 Lightweight and extensible
14+
***Declarative Key Mappings**: Define keymaps via simple configuration (e.g., TOML, YAML) or directly in your code using derive macros.
15+
* ⌨️ **Key Patterns**: Supports single keys (`a`), combinations (`ctrl-b`), and multi-key sequences (`ctrl-b n`).
16+
* 🧠 **Key Groups**: Use built-in pattern matching for common key groups:
17+
* `@upper` – Uppercase letters
18+
* `@lower` – Lowercase letters
19+
* `@alpha` – All alphabetic characters
20+
* `@alnum` – Alphanumeric characters
21+
* `@any` – Match any key
22+
* 🧬 **Compile-Time Safety**: The `keymap_derive` macro validates key syntax at compile time, preventing runtime errors.
23+
* 🌐 **Backend Agnostic**: Works with multiple backends, including `crossterm`, `termion`, and `wasm`.
24+
* 🪶 **Lightweight & Extensible**: Designed to be minimal and easy to extend with new backends or features.
25+
26+
---
27+
28+
## 🕹️ Demo
29+
30+
See `keymap-rs` in action with the [WASM example](https://rezigned.com/keymap-rs/):
31+
32+
<p align="center">
33+
<img src="./examples/wasm/public/preview.png" alt="keymap-rs WASM Demo" width="700">
34+
</p>
2035

2136
---
2237

2338
## 📦 Installation
2439

25-
Run the following command:
40+
Add `keymap` to your `Cargo.toml`, enabling the feature for your chosen backend:
2641

2742
```sh
2843
cargo add keymap --feature {crossterm | termion | wasm}
2944
```
3045

3146
---
3247

33-
## 🚀 Example
48+
## 🚀 Usage
3449

35-
### Parsing keys
50+
### 1. Deriving Keymaps
3651

37-
Parse an input key string into a `KeyMap`
38-
```rust
39-
assert_eq("ctrl-l".parse::<KeyMap>(), KeyMap::new(Modifier::Ctrl, Key::Char('l'))
52+
The easiest way to get started is with the `keymap::KeyMap` derive macro.
4053

41-
// Same as above
42-
assert_eq(parser::parse("ctrl-l"), KeyMap::new(Modifier::Ctrl, Key::Char('l'))
43-
```
54+
**Define your actions:**
4455

45-
Parse an input key string into the backend's key event.
4656
```rust
47-
assert_eq!(
48-
keymap::backend::crossterm::parse("ctrl-l"),
49-
crossterm::event::KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL)
50-
)
51-
```
52-
### Using `keymap_derive`
53-
54-
Define your actions and key mappings:
57+
use keymap::KeyMap;
5558

56-
```rust
57-
/// Game actions
58-
#[derive(keymap::KeyMap, Debug)]
59+
/// Application actions.
60+
#[derive(KeyMap, Debug, PartialEq, Eq)]
5961
pub enum Action {
60-
/// Rage quit the game
62+
/// Quit the application.
6163
#[key("q", "esc")]
6264
Quit,
6365

64-
/// Step left (dodge the trap!)
65-
#[key("left")]
66+
/// Move left.
67+
#[key("left", "h")]
6668
Left,
6769

68-
/// Step right (grab the treasure!)
69-
#[key("right")]
70+
/// Move right.
71+
#[key("right", "l")]
7072
Right,
7173

72-
/// Jump over obstacles (or just for fun)
74+
/// Jump.
7375
#[key("space")]
7476
Jump,
7577
}
7678
```
7779

78-
Use the config:
80+
**Use the generated keymap:**
81+
82+
The `KeyMap` derive macro generates an associated `keymap_config()` method that returns a `Config<Action>`.
7983

8084
```rust
85+
// Retrieve the config
8186
let config = Action::keymap_config();
8287

83-
if let Event::Key(key) = event::read()? {
84-
match config.get(&key) {
85-
Some(action) => match action {
86-
Action::Quit => break,
87-
Action::Jump => println!("Jump Jump!"),
88-
_ => println!("{:?} - {}", action, action.keymap_item().description),
89-
},
90-
None => println!("Unknown key {:?}", key),
88+
// `key` is a key code from the input backend, e.g., `crossterm::event::KeyCode`
89+
match config.get(&key) {
90+
Some(action) => match action {
91+
Action::Quit => break,
92+
Action::Jump => println!("Jump!"),
93+
_ => println!("Action: {action:?} - {}", action.keymap_item().description),
9194
}
95+
_ => {}
9296
}
9397
```
9498

95-
### Using external configuration (e.g. `toml`, `yaml`, etc.)
99+
### 2. Using External Configuration
96100

97-
Define a config:
101+
You can also load keymaps from external files (e.g., `config.toml`). This is useful for user-configurable keybindings.
102+
103+
**Example `config.toml`:**
98104

99105
```toml
100-
Jump = { keys = ["j", "up"], description = "Jump with 'j'!" }
101-
Quit = { keys = ["@any"], description = "Quit!" }
106+
# Override or add new keybindings
107+
Jump = { keys = ["j", "up"], description = "Jump with 'j' or up arrow!" }
108+
Quit = { keys = ["@any"], description = "Quit on any key press." }
102109
```
103110

104-
#### Deserialize with `Config<T>`
111+
You have two ways to load this configuration:
112+
113+
#### `Config<T>`: Load from File Only
105114

106-
> [!NOTE]
107-
> The table below shows all keys that are deserialized only from the configuration file. Keys defined via `#[key("..")]` are **not** included.
108-
>
109-
> | Key | Action |
110-
> | ------------- | ------ |
111-
> | `"j"`, `"up"` | Jump |
112-
> | `@any` | Quit |
115+
This deserializes **only** the keybindings from the configuration file, ignoring any `#[key("...")]` attributes on your enum.
113116

114117
```rust
115-
let config: Config<Action> = toml::from_str("./config.toml").unwrap();
118+
// This config will only contain 'Jump' and 'Quit' from the TOML file.
119+
let config: Config<Action> = toml::from_str(config_str).unwrap();
116120
```
117121

118-
#### Deserialize with `DerivedConfig<T>`
122+
| Key | Action |
123+
| ------------- | ------ |
124+
| `"j"`, `"up"` | Jump |
125+
| `@any` | Quit |
119126

120-
> [!NOTE]
121-
> The table below shows all keys when using both the configuration file **and** the keys defined via `#[key("..")]`. The sets are merged.
122-
>
123-
> | Key | Action |
124-
> | ------------- | ------ |
125-
> | `"j"`, `"up"` | Jump |
126-
> | `"left"` | Left |
127-
> | `"right"` | Right |
128-
> | `@any` | Quit |
127+
#### `DerivedConfig<T>`: Merge Derived and File Configs
128+
129+
This **merges** the keybindings from the `#[key("...")]` attributes with the ones from the configuration file. Keys from the external file will override any conflicting keys defined in the enum.
129130

130131
```rust
131-
let config: DerivedConfig<Action> = toml::from_str("./config.toml").unwrap();
132+
// This config contains keys from both the derive macro and the TOML file.
133+
let config: DerivedConfig<Action> = toml::from_str(config_str).unwrap();
132134
```
133135

134-
---
135-
### 🛠️ Bonus: Compile-time Validation
136+
| Key | Action |
137+
| ------------------------ | ------ |
138+
| `"j"`, `"up"` | Jump |
139+
| `"h"`, `"left"` | Left |
140+
| `"l"`, `"right"` | Right |
141+
| `@any` | Quit |
142+
| *`"q"`, `"esc"`, `"space"` are ignored* |
136143

137-
One powerful advantage of using the `#[key(".."))]` attribute macro from `keymap_derive` is that invalid key definitions are caught at **compile time**, ensuring early feedback and safety.
144+
### 3. Compile-Time Validation
138145

139-
#### Example: Invalid Key
146+
The `keymap_derive` macro validates all key strings at **compile time**, so you get immediate feedback on invalid syntax.
147+
148+
**Invalid Key Example:**
140149

141150
```rust
142151
#[derive(keymap::KeyMap)]
143152
enum Action {
153+
// "enter2" is not a valid key.
144154
#[key("enter2", "ctrl-b n")]
145155
Invalid,
146156
}
147157
```
148158

149-
#### Compile Error
159+
**Compiler Error:**
160+
161+
This code will fail to compile with a clear error message:
150162

151163
```
152164
error: Invalid key "enter2": Parse error at position 5: expect end of input, found: 2
@@ -156,7 +168,37 @@ error: Invalid key "enter2": Parse error at position 5: expect end of input, fou
156168
| ^^^^^^^^
157169
```
158170

159-
This prevents runtime surprises and provides clear diagnostics during development.
171+
### 4. Direct Key Parsing
172+
173+
You can also parse key strings directly into a `KeyMap` or a backend-specific key event.
174+
175+
```rust
176+
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
177+
use keymap::{backend::crossterm::parse, Key, KeyMap, Modifier};
178+
179+
// Parse into a generic KeyMap
180+
assert_eq!(
181+
"ctrl-l".parse::<KeyMap>(),
182+
Ok(KeyMap::new(Some(Modifier::Ctrl), Key::Char('l')))
183+
);
184+
185+
// Or use the backend-specific parser
186+
assert_eq!(
187+
parse("ctrl-l").unwrap(),
188+
KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL)
189+
);
190+
```
191+
192+
---
193+
194+
## 📖 Examples
195+
196+
For complete, runnable examples, check out the [`/examples`](https://github.com/rezigned/keymap-rs/tree/main/examples) directory, which includes demos for:
197+
- `crossterm`
198+
- `termion`
199+
- `wasm`
200+
201+
---
160202

161203
## 📜 License
162204

@@ -166,4 +208,5 @@ This project is licensed under the [MIT License](https://github.com/rezigned/key
166208

167209
## 🙌 Contributions
168210

169-
Contributions, issues, and feature requests are welcome. Have an idea for a new backend, pattern rule, or integration? Open a PR!
211+
Contributions, issues, and feature requests are welcome! Feel free to open an issue or submit a pull request.
212+

examples/config_derive.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use keymap::Config;
12
#[cfg(feature = "derive")]
23
use keymap::{DerivedConfig, Item};
34

@@ -46,6 +47,11 @@ pub(crate) fn derived_config() -> DerivedConfig<Action> {
4647
toml::from_str(DERIVED_CONFIG).unwrap()
4748
}
4849

50+
#[allow(unused)]
51+
pub(crate) fn config() -> Config<Action> {
52+
toml::from_str(DERIVED_CONFIG).unwrap()
53+
}
54+
4955
#[cfg(feature = "derive")]
5056
#[allow(unused)]
5157
pub(crate) fn print_config(items: &[(Action, Item)]) {

examples/wasm/public/preview.png

23.7 KB
Loading

src/config.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,12 @@ use crate::{keymap::ToKeyMap, matcher::Matcher, KeyMap};
7070
/// }
7171
/// ```
7272
pub trait KeyMapConfig<T> {
73-
/// Returns the default key-to-item mappings.
73+
/// Returns the default keymap configuration.
7474
///
75-
/// This method should return a vector of `(T, Item)` pairs representing
76-
/// the default associations between keys and their corresponding items.
77-
/// These defaults will be incorporated into a [`Config<T>`]
78-
/// and can be overridden by user-supplied configuration when deserializing.
75+
/// This method returns a [`Config<T>`] containing the default mappings
76+
/// between keys and their associated items. These defaults are used as
77+
/// the base configuration and can be overridden by user-supplied settings
78+
/// during deserialization.
7979
fn keymap_config() -> Config<T>;
8080

8181
/// Returns the [`Item`] associated with this particular variant.
@@ -411,7 +411,6 @@ impl Item {
411411
///
412412
/// When deserializing, Serde expects a map whose keys are of type `T` and
413413
/// whose values are `Item`. For example, with `T = String`:
414-
415414
/// ```toml
416415
/// Create = { keys = ["c"], description = "Create a new item" }
417416
/// Delete = { keys = ["d", "d e"], description = "Delete an item" }

0 commit comments

Comments
 (0)