Skip to content

Commit 2010892

Browse files
committed
update the demo in readme to more practical example
1 parent 67f48e8 commit 2010892

File tree

7 files changed

+190
-73
lines changed

7 files changed

+190
-73
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ required-features = ["arbitrary"]
3535

3636
[[example]]
3737
name = "crossterm"
38-
required-features = ["crossterm"]
38+
required-features = ["crossterm", "serde"]
3939

4040
[[example]]
4141
name = "termwiz"

README.md

Lines changed: 86 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -28,49 +28,101 @@ library can be buggy and have arbitrary breaking changes.**
2828
cargo add keybinds
2929
```
3030

31-
## Minimal usage
31+
## Usage
3232

33-
This crate is platform-agnostic. Create `KeybindDispatcher` instance and define key bindings by `bind` method.
34-
Pass each key input to the `dispatch` method call. It returns a dispatched action. See the [API documentation][api-doc]
35-
for more details.
33+
This code demonstrates the usage by parsing and dispatching key bindings for moving the cursor inside terminal
34+
using the `serde` and `crossterm` optional features. You can run this code as the [example](./examples/crossterm.rs)
35+
as well. See the [API documentation][api-doc] for more details.
3636

3737
```rust
38-
use keybinds::{KeybindDispatcher, KeyInput, Key, Mods};
38+
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
39+
use crossterm::{cursor, event, execute};
40+
use keybinds::{KeybindDispatcher, Keybinds};
41+
use serde::Deserialize;
42+
use std::io;
3943

4044
// Actions dispatched by key bindings
41-
#[derive(PartialEq, Eq, Debug)]
45+
#[derive(Deserialize)]
4246
enum Action {
43-
SayHello,
44-
OpenFile,
45-
ExitApp,
47+
Exit,
48+
Up,
49+
Down,
50+
Left,
51+
Right,
52+
Top,
53+
Bottom,
54+
Home,
55+
End,
4656
}
4757

48-
// Create a dispatcher to dispatch actions for upcoming key inputs
49-
let mut dispatcher = KeybindDispatcher::default();
50-
51-
// Register key bindings to dispatch the actions
52-
53-
// Key sequence "h" → "e" → "l" → "l" → "o"
54-
dispatcher.bind("h e l l o", Action::SayHello).unwrap();
55-
// Key combination "Ctrl + Alt + Enter"
56-
dispatcher.bind("Ctrl+Alt+Enter", Action::OpenFile).unwrap();
57-
// Sequence of key combinations
58-
dispatcher.bind("Ctrl+x Ctrl+c", Action::ExitApp).unwrap();
59-
60-
// Dispatch `SayHello` action
61-
assert_eq!(dispatcher.dispatch(KeyInput::from('h')), None);
62-
assert_eq!(dispatcher.dispatch(KeyInput::from('e')), None);
63-
assert_eq!(dispatcher.dispatch(KeyInput::from('l')), None);
64-
assert_eq!(dispatcher.dispatch(KeyInput::from('l')), None);
65-
assert_eq!(dispatcher.dispatch(KeyInput::from('o')), Some(&Action::SayHello));
66-
67-
// Dispatch `OpenFile` action
68-
let action = dispatcher.dispatch(KeyInput::new(Key::Enter, Mods::CTRL | Mods::ALT));
69-
assert_eq!(action, Some(&Action::OpenFile));
58+
// Configuration of your app
59+
#[derive(Deserialize)]
60+
struct Config {
61+
keyboard: Keybinds<Action>,
62+
}
7063

71-
// Dispatch `ExitApp` action
72-
assert_eq!(dispatcher.dispatch(KeyInput::new('x', Mods::CTRL)), None);
73-
assert_eq!(dispatcher.dispatch(KeyInput::new('c', Mods::CTRL)), Some(&Action::ExitApp));
64+
const CONFIG_FILE: &str = r#"
65+
[keyboard]
66+
"Esc" = "Exit"
67+
68+
# Standard bindings
69+
"Up" = "Up"
70+
"Down" = "Down"
71+
"Left" = "Left"
72+
"Right" = "Right"
73+
"PageUp" = "Top"
74+
"PageDown" = "Bottom"
75+
"Home" = "Home"
76+
"End" = "End"
77+
78+
# Emacs-like bindings
79+
"Ctrl+p" = "Up"
80+
"Ctrl+n" = "Down"
81+
"Ctrl+b" = "Left"
82+
"Ctrl+f" = "Right"
83+
"Alt+<" = "Top"
84+
"Alt+>" = "Bottom"
85+
"Ctrl+a" = "Home"
86+
"Ctrl+e" = "End"
87+
88+
# Vim-like bindings
89+
"k" = "Up"
90+
"j" = "Down"
91+
"h" = "Left"
92+
"l" = "Right"
93+
"g g" = "Top"
94+
"G" = "Bottom"
95+
"^" = "Home"
96+
"$" = "End"
97+
"#;
98+
99+
fn main() -> io::Result<()> {
100+
// Parse the configuration from the file content
101+
let config: Config = toml::from_str(CONFIG_FILE).unwrap();
102+
103+
// Create the key binding dispatcher to handle key input events
104+
let mut dispatcher = KeybindDispatcher::new(config.keyboard);
105+
106+
enable_raw_mode()?;
107+
let mut stdout = io::stdout();
108+
while let Ok(event) = event::read() {
109+
// If the event triggered some action, dispatch it
110+
if let Some(action) = dispatcher.dispatch(&event) {
111+
match action {
112+
Action::Exit => break,
113+
Action::Up => execute!(stdout, cursor::MoveUp(1))?,
114+
Action::Down => execute!(stdout, cursor::MoveDown(1))?,
115+
Action::Left => execute!(stdout, cursor::MoveLeft(1))?,
116+
Action::Right => execute!(stdout, cursor::MoveRight(1))?,
117+
Action::Top => execute!(stdout, cursor::MoveUp(9999))?,
118+
Action::Bottom => execute!(stdout, cursor::MoveDown(9999))?,
119+
Action::Home => execute!(stdout, cursor::MoveLeft(9999))?,
120+
Action::End => execute!(stdout, cursor::MoveRight(9999))?,
121+
}
122+
}
123+
}
124+
disable_raw_mode()
125+
}
74126
```
75127

76128
## Examples

examples/crossterm.rs

Lines changed: 73 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,88 @@
1-
use crossterm::event::{read, Event};
21
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
3-
use keybinds::{KeyInput, KeybindDispatcher};
2+
use crossterm::{cursor, event, execute};
3+
use keybinds::{KeybindDispatcher, Keybinds};
4+
use serde::Deserialize;
45
use std::io;
56

67
// Actions dispatched by key bindings
7-
#[derive(PartialEq, Eq, Debug)]
8+
#[derive(Deserialize)]
89
enum Action {
9-
SayHi,
10-
MoveLeft,
11-
Paste,
12-
ExitApp,
10+
Exit,
11+
Up,
12+
Down,
13+
Left,
14+
Right,
15+
Top,
16+
Bottom,
17+
Home,
18+
End,
1319
}
1420

21+
// Configuration of your app
22+
#[derive(Deserialize)]
23+
struct Config {
24+
keyboard: Keybinds<Action>,
25+
}
26+
27+
const CONFIG_FILE: &str = r#"
28+
[keyboard]
29+
"Esc" = "Exit"
30+
31+
# Standard bindings
32+
"Up" = "Up"
33+
"Down" = "Down"
34+
"Left" = "Left"
35+
"Right" = "Right"
36+
"PageUp" = "Top"
37+
"PageDown" = "Bottom"
38+
"Home" = "Home"
39+
"End" = "End"
40+
41+
# Emacs-like bindings
42+
"Ctrl+p" = "Up"
43+
"Ctrl+n" = "Down"
44+
"Ctrl+b" = "Left"
45+
"Ctrl+f" = "Right"
46+
"Alt+<" = "Top"
47+
"Alt+>" = "Bottom"
48+
"Ctrl+a" = "Home"
49+
"Ctrl+e" = "End"
50+
51+
# Vim-like bindings
52+
"k" = "Up"
53+
"j" = "Down"
54+
"h" = "Left"
55+
"l" = "Right"
56+
"g g" = "Top"
57+
"G" = "Bottom"
58+
"^" = "Home"
59+
"$" = "End"
60+
"#;
61+
1562
fn main() -> io::Result<()> {
16-
// Create a dispatcher to dispatch actions for upcoming key inputs
17-
let mut dispatcher = KeybindDispatcher::default();
63+
// Parse the configuration from the file content
64+
let config: Config = toml::from_str(CONFIG_FILE).unwrap();
1865

19-
// Key bindings to dispatch the actions
20-
dispatcher.bind("h i", Action::SayHi).unwrap();
21-
dispatcher.bind("Left", Action::MoveLeft).unwrap();
22-
dispatcher.bind("Ctrl+p", Action::Paste).unwrap();
23-
dispatcher.bind("Ctrl+x Ctrl+c", Action::ExitApp).unwrap();
66+
// Create the key binding dispatcher to handle key input events
67+
let mut dispatcher = KeybindDispatcher::new(config.keyboard);
2468

25-
println!("Type Ctrl+X → Ctrl+C to exit");
2669
enable_raw_mode()?;
27-
28-
while let Ok(event) = read() {
29-
if let Event::Key(event) = event {
30-
// Can convert crossterm's `KeyEvent` into `KeyInput`
31-
println!("Key input `{:?}`\r", KeyInput::from(event));
32-
33-
// `KeybindDispatcher::dispatch` accepts crossterm's `KeyEvent`
34-
if let Some(action) = dispatcher.dispatch(event) {
35-
println!("Triggered action `{action:?}`\r");
36-
if action == &Action::ExitApp {
37-
break;
38-
}
70+
let mut stdout = io::stdout();
71+
while let Ok(event) = event::read() {
72+
// If the event triggered some action, dispatch it
73+
if let Some(action) = dispatcher.dispatch(&event) {
74+
match action {
75+
Action::Exit => break,
76+
Action::Up => execute!(stdout, cursor::MoveUp(1))?,
77+
Action::Down => execute!(stdout, cursor::MoveDown(1))?,
78+
Action::Left => execute!(stdout, cursor::MoveLeft(1))?,
79+
Action::Right => execute!(stdout, cursor::MoveRight(1))?,
80+
Action::Top => execute!(stdout, cursor::MoveUp(9999))?,
81+
Action::Bottom => execute!(stdout, cursor::MoveDown(9999))?,
82+
Action::Home => execute!(stdout, cursor::MoveLeft(9999))?,
83+
Action::End => execute!(stdout, cursor::MoveRight(9999))?,
3984
}
4085
}
4186
}
42-
43-
disable_raw_mode()?;
44-
Ok(())
87+
disable_raw_mode()
4588
}

src/arbitrary.rs

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,21 @@ use arbitrary::{Arbitrary, Result, Unstructured};
3636
impl Arbitrary<'_> for Mods {
3737
fn arbitrary(u: &mut Unstructured<'_>) -> Result<Self> {
3838
let mut mods = Self::NONE;
39-
mods.set(Mods::CTRL, u.arbitrary()?);
40-
mods.set(Mods::CMD, u.arbitrary()?);
41-
mods.set(Mods::ALT, u.arbitrary()?);
42-
mods.set(Mods::WIN, u.arbitrary()?);
43-
mods.set(Mods::SHIFT, u.arbitrary()?);
39+
if u.arbitrary()? {
40+
mods |= Mods::CTRL;
41+
}
42+
if u.arbitrary()? {
43+
mods |= Mods::CMD;
44+
}
45+
if u.arbitrary()? {
46+
mods |= Mods::ALT;
47+
}
48+
if u.arbitrary()? {
49+
mods |= Mods::WIN;
50+
}
51+
if u.arbitrary()? {
52+
mods |= Mods::SHIFT;
53+
}
4454
Ok(mods)
4555
}
4656
}

src/keybind.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ impl<A> From<Vec<Keybind<A>>> for Keybinds<A> {
7070
}
7171
}
7272

73+
impl<A> From<Keybinds<A>> for Vec<Keybind<A>> {
74+
fn from(binds: Keybinds<A>) -> Self {
75+
binds.0
76+
}
77+
}
78+
7379
pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(1);
7480

7581
pub struct KeybindDispatcher<A> {

src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
//! - [winit][]
1313
//! - Support structral fuzzing using [arbitrary][] optionally.
1414
//!
15+
//! # Installation
16+
//!
17+
//! ```sh
18+
//! cargo add keybinds
19+
//! ```
20+
//!
1521
//! # Minimal example
1622
//!
1723
//! ```

src/serde.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
//! assert_eq!(&generated, configuration);
3838
//! ```
3939
use crate::{Key, KeyInput, KeySeq, Keybind, Keybinds, Mods};
40-
use serde::de::{Deserialize, Deserializer, MapAccess, Visitor};
40+
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
4141
use serde::ser::{Serialize, SerializeMap, Serializer};
4242
use std::fmt;
4343

@@ -52,7 +52,7 @@ impl<'de> Deserialize<'de> for KeyInput {
5252
formatter.write_str("key sequence for a key bind")
5353
}
5454

55-
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
55+
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
5656
v.parse().map_err(E::custom)
5757
}
5858
}
@@ -72,7 +72,7 @@ impl<'de> Deserialize<'de> for KeySeq {
7272
formatter.write_str("key sequence for a key bind")
7373
}
7474

75-
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
75+
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
7676
v.parse().map_err(E::custom)
7777
}
7878
}

0 commit comments

Comments
 (0)