Skip to content

Commit 501b745

Browse files
authored
refactor: add ToKeyMap, IntoKeyMap and FromKeyMap traits to allow conversion between keymap's node and backend's key (#28)
1 parent e48a2e3 commit 501b745

File tree

31 files changed

+2033
-393
lines changed

31 files changed

+2033
-393
lines changed

.github/workflows/ci.yml

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,53 @@ name: CI
22

33
on:
44
push:
5-
branches: [ "main" ]
5+
branches: ["main"]
66
pull_request:
7-
branches: [ "main" ]
7+
branches: ["main"]
88

99
env:
1010
CARGO_TERM_COLOR: always
1111

1212
jobs:
13-
build:
13+
test-backends:
1414
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
backend:
18+
- crossterm
19+
- termion
1520

1621
steps:
17-
- uses: actions/checkout@v3
18-
- name: Build
19-
run: cargo build --verbose
20-
- name: Run tests
21-
run: cargo test --workspace --verbose
22+
- uses: actions/checkout@v4
23+
- uses: dtolnay/rust-toolchain@stable
24+
- uses: Swatinem/rust-cache@v2
25+
26+
- name: Run tests
27+
run: cargo test --no-default-features --features "derive ${{ matrix.backend }}" --verbose
28+
29+
test-wasm-backend:
30+
# TODO: This takes too long to run. Might enable it again later.
31+
if: false
32+
runs-on: ubuntu-latest
33+
34+
steps:
35+
- uses: actions/checkout@v4
36+
- uses: dtolnay/rust-toolchain@stable
37+
- uses: Swatinem/rust-cache@v2
38+
39+
- name: Install wasm-pack
40+
run: cargo install wasm-pack
41+
42+
- name: Run wasm tests
43+
run: wasm-pack test --headless --chrome --features wasm --no-default-features
44+
45+
test-workspace:
46+
runs-on: ubuntu-latest
47+
48+
steps:
49+
- uses: actions/checkout@v4
50+
- uses: dtolnay/rust-toolchain@stable
51+
- uses: Swatinem/rust-cache@v2
52+
53+
- name: Run tests
54+
run: cargo test --workspace --verbose

.github/workflows/github-pages.yml

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# See https://book.leptos.dev/deployment/csr.html#github-pages
2+
name: Release to Github Pages
3+
4+
on:
5+
push:
6+
branches: [main]
7+
paths:
8+
- 'examples/wasm/**'
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: write # for committing to gh-pages branch.
13+
pages: write
14+
id-token: write
15+
16+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
17+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
18+
concurrency:
19+
group: "pages"
20+
cancel-in-progress: false
21+
22+
env:
23+
WASM_PATH: ./examples/wasm
24+
25+
jobs:
26+
Github-Pages-Release:
27+
defaults:
28+
run:
29+
working-directory: ${{ env.WASM_PATH }}
30+
31+
timeout-minutes: 10
32+
33+
environment:
34+
name: github-pages
35+
url: ${{ steps.deployment.outputs.page_url }}
36+
37+
runs-on: ubuntu-latest
38+
39+
steps:
40+
- uses: actions/checkout@v4 # repo checkout
41+
42+
# Install Rust Nightly Toolchain, with Clippy & Rustfmt
43+
- name: Install nightly Rust
44+
uses: dtolnay/rust-toolchain@nightly
45+
46+
- name: Add WASM target
47+
run: rustup target add wasm32-unknown-unknown
48+
49+
- name: Download and install Trunk binary
50+
run: wget -qO- https://github.com/trunk-rs/trunk/releases/download/v0.21.14/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf-
51+
52+
- name: Build with Trunk
53+
# The behavior of the --public-url argument has changed: it no longer includes a leading '/'.
54+
# We now need to specify it explicitly. See https://github.com/trunk-rs/trunk/issues/668
55+
run: ./trunk build --release --public-url "/${GITHUB_REPOSITORY#*/}"
56+
57+
# Deploy with Github Static Pages
58+
- name: Setup Pages
59+
uses: actions/configure-pages@v5
60+
with:
61+
enablement: true
62+
# token:
63+
64+
- name: Upload artifact
65+
uses: actions/upload-pages-artifact@v3
66+
with:
67+
# Upload dist dir
68+
path: "${{ env.WASM_PATH }}/dist"
69+
70+
- name: Deploy to GitHub Pages 🚀
71+
id: deployment
72+
uses: actions/deploy-pages@v4

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
/target
1+
**/target
2+
**/pkg
3+
**/dist
24
Cargo.lock
35
.DS_Store
46
.vscode

Cargo.toml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,28 @@ homepage = "https://github.com/rezigned/keymap-rs"
2323
repository = "https://github.com/rezigned/keymap-rs"
2424
documentation = "https://docs.rs/keymap"
2525
edition = "2021"
26-
keywords = ["terminal", "input", "event", "config", "keymap", "keybinding"]
26+
keywords = ["terminal", "input", "event", "config", "keymap", "keybinding", "wasm"]
2727
license = "MIT"
2828

2929
[dependencies]
3030
keymap_parser = { path = "./keymap_parser", version = "1.0.0-rc.2" }
3131
keymap_derive = { path = "./keymap_derive", version = "1.0.0-rc.2", optional = true }
3232
crossterm = { version = "0.29", optional = true }
3333
termion = { version = "4.0", optional = true }
34+
web-sys = { version = "0.3", features = ["KeyboardEvent", "KeyboardEventInit"], optional = true }
35+
wasm-bindgen = { version = "0.2", optional = true }
3436
serde = { version = "1.0", features = ["derive"] }
3537

3638
[features]
37-
default = ["crossterm", "derive"]
39+
default = ["derive"]
3840
derive = ["dep:keymap_derive"]
41+
crossterm = ["dep:crossterm"]
42+
termion = ["dep:termion"]
43+
wasm = ["dep:web-sys", "dep:wasm-bindgen"]
3944

4045
[dev-dependencies]
4146
toml = "0.8"
47+
wasm-bindgen-test = "0.3"
4248

4349
[[example]]
4450
name = "crossterm"
@@ -54,6 +60,11 @@ name = "crossterm_derived_config"
5460
path = "examples/crossterm/derived_config.rs"
5561
required-features = ["crossterm", "derive"]
5662

63+
[[example]]
64+
name = "crossterm_modes"
65+
path = "examples/crossterm/modes.rs"
66+
required-features = ["crossterm", "derive"]
67+
5768
[[example]]
5869
name = "termion"
5970
required-features = ["termion"]

README.md

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
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` (via `web_sys`), and others.
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.
44

55
---
66

@@ -15,7 +15,7 @@
1515
* `@alnum` – alphanumeric
1616
* `@any` – match any key
1717
* 🧬 **Derive-based config parser** via `keymap_derive`
18-
* 🌐 Backend-agnostic (works with `crossterm`, `termion`, `web_sys`, etc.)
18+
* 🌐 Backend-agnostic (works with `crossterm`, `termion`, `wasm`, etc.)
1919
* 🪶 Lightweight and extensible
2020

2121
---
@@ -24,21 +24,31 @@
2424

2525
Run the following command:
2626

27-
> \[!NOTE]
28-
> By default, this installs with `crossterm` as the default backend. You can enable a different backend by specifying the feature flag:
29-
>
30-
> ```sh
31-
> cargo add keymap --features termion # or web_sys, etc.
32-
> ```
33-
3427
```sh
35-
cargo add keymap
28+
cargo add keymap --feature {crossterm | termion | wasm}
3629
```
3730

3831
---
3932

4033
## 🚀 Example
4134

35+
### Parsing keys
36+
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'))
40+
41+
// Same as above
42+
assert_eq(parser::parse("ctrl-l"), KeyMap::new(Modifier::Ctrl, Key::Char('l'))
43+
```
44+
45+
Parse an input key string into the backend's key event.
46+
```rust
47+
assert_eq!(
48+
keymap::backend::crossterm::parse("ctrl-l"),
49+
crossterm::event::KeyEvent::new(KeyCode::Char('l'), KeyModifiers::CONTROL)
50+
)
51+
```
4252
### Using `keymap_derive`
4353

4454
Define your actions and key mappings:

examples/config_derive.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
#[cfg(feature = "derive")]
12
use keymap::{DerivedConfig, Item};
3+
4+
#[cfg(feature = "derive")]
25
use serde::Deserialize;
36

47
#[cfg(feature = "derive")]
@@ -37,11 +40,13 @@ Up = { keys = ["u", "g g"], description = "Fly!" }
3740
Quit = { keys = ["@digit"], description = "Quit!" }
3841
"#;
3942

43+
#[cfg(feature = "derive")]
4044
#[allow(unused)]
4145
pub(crate) fn derived_config() -> DerivedConfig<Action> {
4246
toml::from_str(DERIVED_CONFIG).unwrap()
4347
}
4448

49+
#[cfg(feature = "derive")]
4550
#[allow(unused)]
4651
pub(crate) fn print_config(items: &[(Action, Item)]) {
4752
println!("--- keymap ---");

examples/crossterm/derive.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crossterm::{
1010
terminal::{disable_raw_mode, enable_raw_mode},
1111
};
1212
use crossterm_utils::output;
13-
use keymap::{KeyMapConfig};
13+
use keymap::KeyMapConfig;
1414
use std::io;
1515

1616
fn main() -> io::Result<()> {

examples/crossterm/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crossterm::{
1212
terminal::{disable_raw_mode, enable_raw_mode},
1313
};
1414
use crossterm_utils::output;
15-
use keymap::KeyMap;
15+
use keymap::ToKeyMap;
1616

1717
fn main() -> io::Result<()> {
1818
enable_raw_mode()?;
@@ -24,7 +24,7 @@ fn main() -> io::Result<()> {
2424
let event = read()?;
2525

2626
if let Event::Key(key) = event {
27-
if let Some((_, action)) = config.0.get_key_value(&KeyMap::try_from(&key).unwrap()) {
27+
if let Some((_, action)) = config.0.get_key_value(&key.to_keymap().unwrap()) {
2828
match action {
2929
Action::Up => send("Up!")?,
3030
Action::Down => send("Down!")?,

examples/crossterm/modes.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
use std::collections::HashMap;
2+
3+
use keymap::{DerivedConfig, ToKeyMap};
4+
use serde::Deserialize;
5+
6+
#[derive(keymap::KeyMap, Deserialize, Debug, Hash, Eq, PartialEq)]
7+
enum HomeAction {
8+
#[key("esc")]
9+
Quit,
10+
#[key("e")]
11+
Edit,
12+
}
13+
14+
#[derive(keymap::KeyMap, Deserialize, Debug, Hash, Eq, PartialEq)]
15+
enum EditAction {
16+
#[key("esc")]
17+
ExitMode,
18+
}
19+
20+
#[derive(Deserialize, Debug)]
21+
#[serde(untagged)]
22+
enum Actions {
23+
Home(DerivedConfig<HomeAction>),
24+
Edit(DerivedConfig<EditAction>),
25+
}
26+
27+
#[allow(unused)]
28+
pub(crate) const DERIVED_CONFIG: &str = r#"
29+
[home]
30+
Quit = { keys = ["esc", "q"], description = "Quit the app" }
31+
Edit = { keys = ["e"], description = "Enter edit mode" }
32+
33+
[edit]
34+
ExitMode = { keys = ["esc", "q"], description = "Exit edit mode" }
35+
"#;
36+
37+
type Modes = HashMap<String, Actions>;
38+
39+
fn main() {
40+
let modes: Modes = toml::from_str(DERIVED_CONFIG).unwrap();
41+
let mode = "home";
42+
43+
match mode {
44+
"home" => {
45+
if let Some(Actions::Home(config)) = modes.get(mode) {
46+
// config.get(key)
47+
}
48+
}
49+
"edit" => {
50+
if let Some(Actions::Edit(config)) = modes.get(mode) {
51+
// config.get(key)
52+
}
53+
}
54+
_ => {}
55+
}
56+
}

examples/termion/main.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod termion_utils;
66
use std::io::{stdin, Write};
77

88
use config::{parse_config, Action};
9-
use keymap::KeyMap;
9+
use keymap::ToKeyMap;
1010
use termion::event::Event;
1111
use termion::input::TermRead;
1212
use termion_utils::{output, print, Result};
@@ -20,7 +20,7 @@ fn main() -> Result {
2020
if let Event::Key(key) = event? {
2121
let mut send = |s: &str| print(&mut stdout, s);
2222

23-
match bindings.0.get_key_value(&KeyMap::try_from(&key).unwrap()) {
23+
match bindings.0.get_key_value(&key.to_keymap().unwrap()) {
2424
Some((key, action)) => {
2525
if *action == Action::Quit {
2626
break;

0 commit comments

Comments
 (0)