#[non_exhaustive]pub struct Keymap<A> { /* private fields */ }Expand description
A table mapping normalized KeyInputs to a caller-defined action A.
Keymap is state-free: it answers “what is bound to this input?” and nothing
more. Deciding whether to consume the action or pass the input through, and
tracking modes or multi-key sequences, is the caller’s concern (and the job
of later layers). A lookup miss is an absence (None), not an error — the
caller treats it as “pass through”.
The action type A is yours. The struct itself places no bound on A;
bounds appear only on the methods that need them.
Implementations§
Source§impl<A> Keymap<A>
impl<A> Keymap<A>
Sourcepub fn new() -> Self
pub fn new() -> Self
Creates an empty keymap.
Examples found in repository?
15fn main() {
16 let mut keymap = Keymap::new();
17 keymap.bind(KeyInput::new(Key::Char('q'), Modifiers::CTRL), Action::Quit);
18 keymap.bind(KeyInput::new(Key::Char('s'), Modifiers::CTRL), Action::Save);
19 keymap.bind(
20 KeyInput::new(Key::Char('1'), Modifiers::SUPER),
21 Action::SplitPane,
22 );
23
24 // In a real app these come from the terminal backend (see the `crossterm`
25 // feature's `TryFrom<KeyEvent>`); here we construct them directly.
26 let inputs = [
27 KeyInput::new(Key::Char('q'), Modifiers::CTRL),
28 KeyInput::new(Key::Char('1'), Modifiers::SUPER),
29 KeyInput::new(Key::Char('x'), Modifiers::NONE),
30 ];
31
32 for input in inputs {
33 match keymap.get(&input) {
34 Some(action) => println!("{input:>8} -> consume {action:?}"),
35 None => println!("{input:>8} -> pass through"),
36 }
37 }
38}More examples
15fn main() {
16 let mut keymap = Keymap::new();
17 keymap.bind(
18 KeyInput::new(Key::Char('q'), Modifiers::CTRL),
19 Command {
20 id: "quit",
21 description: "Quit the application",
22 },
23 );
24 keymap.bind(
25 KeyInput::new(Key::Char('s'), Modifiers::CTRL),
26 Command {
27 id: "save",
28 description: "Save the current file",
29 },
30 );
31 keymap.bind(
32 KeyInput::new(Key::F(1), Modifiers::NONE),
33 Command {
34 id: "help",
35 description: "Show the help overlay",
36 },
37 );
38
39 // Listing: collect and sort by the canonical key string.
40 let mut listing: Vec<(String, &Command)> = keymap
41 .iter()
42 .map(|(input, cmd)| (input.to_string(), cmd))
43 .collect();
44 listing.sort_by(|a, b| a.0.cmp(&b.0));
45
46 println!("all bindings:");
47 for (key, cmd) in &listing {
48 println!(" {key:>8} {:<6} — {}", cmd.id, cmd.description);
49 }
50
51 search(&listing, "sav");
52 search(&listing, "zzz");
53}37fn main() {
38 // Base layer: shared bindings live here once.
39 let mut base = Keymap::new();
40 base.bind(ctrl('s'), Action::Save);
41 base.bind(ctrl('q'), Action::Quit);
42
43 // Panel layer: only the overrides for panel context.
44 let mut panel = Keymap::new();
45 panel.bind(ctrl('s'), Action::SplitPanel);
46
47 // The app maps its context to an ordered layer stack. This `match` is the
48 // only place "context" exists — entirely on the application side.
49 let layers_for = |ctx: &Context| -> Vec<&Keymap<Action>> {
50 match ctx {
51 Context::Editor => vec![&base],
52 Context::Panel => vec![&panel, &base],
53 }
54 };
55
56 for ctx in [Context::Editor, Context::Panel] {
57 let name = match ctx {
58 Context::Editor => "editor",
59 Context::Panel => "panel",
60 };
61 let layers = layers_for(&ctx);
62 for key in [ctrl('s'), ctrl('q')] {
63 match resolve_layered(layers.iter().copied(), &key) {
64 Some(action) => println!("[{name:>6}] {key:>6} -> {action:?}"),
65 None => println!("[{name:>6}] {key:>6} -> pass through"),
66 }
67 }
68 }
69}57fn main() {
58 let mut map = Keymap::new();
59 map.bind(plain(':'), Action::EnterCommandMode);
60
61 // Stage 3's resolver: command name -> action. Same shape as
62 // `keymap_config`'s `FnMut(&str) -> Option<A>`, intentionally a plain
63 // closure rather than that crate's type.
64 let resolve = |name: &str| -> Option<Action> {
65 match name {
66 "w" => Some(Action::Write),
67 "q" => Some(Action::Quit),
68 "wq" => Some(Action::WriteQuit),
69 _ => None,
70 }
71 };
72
73 // A simulated keystroke stream: `:wq⏎`, then `:q⏎`, then an unknown `:zz⏎`.
74 let stream = [
75 plain(':'),
76 plain('w'),
77 plain('q'),
78 enter(),
79 plain(':'),
80 plain('q'),
81 enter(),
82 plain(':'),
83 plain('z'),
84 plain('z'),
85 enter(),
86 ];
87
88 let mut mode = Mode::Normal;
89 for key in stream {
90 match &mut mode {
91 Mode::Normal => {
92 if map.get(&key) == Some(&Action::EnterCommandMode) {
93 println!(": -> enter command mode");
94 mode = Mode::Command(String::new());
95 } else {
96 // Any other normal-mode key would run its own binding; this
97 // demo only cares about the `:` entry point.
98 }
99 }
100 Mode::Command(buf) => match key.key() {
101 Key::Char(c) => {
102 buf.push(c);
103 println!(" :{buf}");
104 }
105 Key::Enter => {
106 // Take the buffer out and drop back to Normal in one move.
107 let name = std::mem::take(buf);
108 match resolve(&name) {
109 Some(action) => println!(" :{name} -> fire {action:?}"),
110 None => println!(" :{name} -> unknown command, no-op"),
111 }
112 mode = Mode::Normal;
113 }
114 Key::Esc => {
115 println!(" esc -> abandon command line");
116 mode = Mode::Normal;
117 }
118 // `Key` is #[non_exhaustive]; other editing keys are ignored here.
119 _ => {}
120 },
121 }
122 }
123}Sourcepub fn bind(&mut self, input: KeyInput, action: A) -> Option<A>
pub fn bind(&mut self, input: KeyInput, action: A) -> Option<A>
Binds input to action, returning the action previously bound to that
input, if any.
Examples found in repository?
15fn main() {
16 let mut keymap = Keymap::new();
17 keymap.bind(KeyInput::new(Key::Char('q'), Modifiers::CTRL), Action::Quit);
18 keymap.bind(KeyInput::new(Key::Char('s'), Modifiers::CTRL), Action::Save);
19 keymap.bind(
20 KeyInput::new(Key::Char('1'), Modifiers::SUPER),
21 Action::SplitPane,
22 );
23
24 // In a real app these come from the terminal backend (see the `crossterm`
25 // feature's `TryFrom<KeyEvent>`); here we construct them directly.
26 let inputs = [
27 KeyInput::new(Key::Char('q'), Modifiers::CTRL),
28 KeyInput::new(Key::Char('1'), Modifiers::SUPER),
29 KeyInput::new(Key::Char('x'), Modifiers::NONE),
30 ];
31
32 for input in inputs {
33 match keymap.get(&input) {
34 Some(action) => println!("{input:>8} -> consume {action:?}"),
35 None => println!("{input:>8} -> pass through"),
36 }
37 }
38}More examples
15fn main() {
16 let mut keymap = Keymap::new();
17 keymap.bind(
18 KeyInput::new(Key::Char('q'), Modifiers::CTRL),
19 Command {
20 id: "quit",
21 description: "Quit the application",
22 },
23 );
24 keymap.bind(
25 KeyInput::new(Key::Char('s'), Modifiers::CTRL),
26 Command {
27 id: "save",
28 description: "Save the current file",
29 },
30 );
31 keymap.bind(
32 KeyInput::new(Key::F(1), Modifiers::NONE),
33 Command {
34 id: "help",
35 description: "Show the help overlay",
36 },
37 );
38
39 // Listing: collect and sort by the canonical key string.
40 let mut listing: Vec<(String, &Command)> = keymap
41 .iter()
42 .map(|(input, cmd)| (input.to_string(), cmd))
43 .collect();
44 listing.sort_by(|a, b| a.0.cmp(&b.0));
45
46 println!("all bindings:");
47 for (key, cmd) in &listing {
48 println!(" {key:>8} {:<6} — {}", cmd.id, cmd.description);
49 }
50
51 search(&listing, "sav");
52 search(&listing, "zzz");
53}37fn main() {
38 // Base layer: shared bindings live here once.
39 let mut base = Keymap::new();
40 base.bind(ctrl('s'), Action::Save);
41 base.bind(ctrl('q'), Action::Quit);
42
43 // Panel layer: only the overrides for panel context.
44 let mut panel = Keymap::new();
45 panel.bind(ctrl('s'), Action::SplitPanel);
46
47 // The app maps its context to an ordered layer stack. This `match` is the
48 // only place "context" exists — entirely on the application side.
49 let layers_for = |ctx: &Context| -> Vec<&Keymap<Action>> {
50 match ctx {
51 Context::Editor => vec![&base],
52 Context::Panel => vec![&panel, &base],
53 }
54 };
55
56 for ctx in [Context::Editor, Context::Panel] {
57 let name = match ctx {
58 Context::Editor => "editor",
59 Context::Panel => "panel",
60 };
61 let layers = layers_for(&ctx);
62 for key in [ctrl('s'), ctrl('q')] {
63 match resolve_layered(layers.iter().copied(), &key) {
64 Some(action) => println!("[{name:>6}] {key:>6} -> {action:?}"),
65 None => println!("[{name:>6}] {key:>6} -> pass through"),
66 }
67 }
68 }
69}57fn main() {
58 let mut map = Keymap::new();
59 map.bind(plain(':'), Action::EnterCommandMode);
60
61 // Stage 3's resolver: command name -> action. Same shape as
62 // `keymap_config`'s `FnMut(&str) -> Option<A>`, intentionally a plain
63 // closure rather than that crate's type.
64 let resolve = |name: &str| -> Option<Action> {
65 match name {
66 "w" => Some(Action::Write),
67 "q" => Some(Action::Quit),
68 "wq" => Some(Action::WriteQuit),
69 _ => None,
70 }
71 };
72
73 // A simulated keystroke stream: `:wq⏎`, then `:q⏎`, then an unknown `:zz⏎`.
74 let stream = [
75 plain(':'),
76 plain('w'),
77 plain('q'),
78 enter(),
79 plain(':'),
80 plain('q'),
81 enter(),
82 plain(':'),
83 plain('z'),
84 plain('z'),
85 enter(),
86 ];
87
88 let mut mode = Mode::Normal;
89 for key in stream {
90 match &mut mode {
91 Mode::Normal => {
92 if map.get(&key) == Some(&Action::EnterCommandMode) {
93 println!(": -> enter command mode");
94 mode = Mode::Command(String::new());
95 } else {
96 // Any other normal-mode key would run its own binding; this
97 // demo only cares about the `:` entry point.
98 }
99 }
100 Mode::Command(buf) => match key.key() {
101 Key::Char(c) => {
102 buf.push(c);
103 println!(" :{buf}");
104 }
105 Key::Enter => {
106 // Take the buffer out and drop back to Normal in one move.
107 let name = std::mem::take(buf);
108 match resolve(&name) {
109 Some(action) => println!(" :{name} -> fire {action:?}"),
110 None => println!(" :{name} -> unknown command, no-op"),
111 }
112 mode = Mode::Normal;
113 }
114 Key::Esc => {
115 println!(" esc -> abandon command line");
116 mode = Mode::Normal;
117 }
118 // `Key` is #[non_exhaustive]; other editing keys are ignored here.
119 _ => {}
120 },
121 }
122 }
123}Sourcepub fn unbind(&mut self, input: &KeyInput) -> Option<A>
pub fn unbind(&mut self, input: &KeyInput) -> Option<A>
Removes any binding for input, returning the action that was bound.
Sourcepub fn get(&self, input: &KeyInput) -> Option<&A>
pub fn get(&self, input: &KeyInput) -> Option<&A>
Returns the action bound to input, or None if the input is unbound.
Examples found in repository?
15fn main() {
16 let mut keymap = Keymap::new();
17 keymap.bind(KeyInput::new(Key::Char('q'), Modifiers::CTRL), Action::Quit);
18 keymap.bind(KeyInput::new(Key::Char('s'), Modifiers::CTRL), Action::Save);
19 keymap.bind(
20 KeyInput::new(Key::Char('1'), Modifiers::SUPER),
21 Action::SplitPane,
22 );
23
24 // In a real app these come from the terminal backend (see the `crossterm`
25 // feature's `TryFrom<KeyEvent>`); here we construct them directly.
26 let inputs = [
27 KeyInput::new(Key::Char('q'), Modifiers::CTRL),
28 KeyInput::new(Key::Char('1'), Modifiers::SUPER),
29 KeyInput::new(Key::Char('x'), Modifiers::NONE),
30 ];
31
32 for input in inputs {
33 match keymap.get(&input) {
34 Some(action) => println!("{input:>8} -> consume {action:?}"),
35 None => println!("{input:>8} -> pass through"),
36 }
37 }
38}More examples
57fn main() {
58 let mut map = Keymap::new();
59 map.bind(plain(':'), Action::EnterCommandMode);
60
61 // Stage 3's resolver: command name -> action. Same shape as
62 // `keymap_config`'s `FnMut(&str) -> Option<A>`, intentionally a plain
63 // closure rather than that crate's type.
64 let resolve = |name: &str| -> Option<Action> {
65 match name {
66 "w" => Some(Action::Write),
67 "q" => Some(Action::Quit),
68 "wq" => Some(Action::WriteQuit),
69 _ => None,
70 }
71 };
72
73 // A simulated keystroke stream: `:wq⏎`, then `:q⏎`, then an unknown `:zz⏎`.
74 let stream = [
75 plain(':'),
76 plain('w'),
77 plain('q'),
78 enter(),
79 plain(':'),
80 plain('q'),
81 enter(),
82 plain(':'),
83 plain('z'),
84 plain('z'),
85 enter(),
86 ];
87
88 let mut mode = Mode::Normal;
89 for key in stream {
90 match &mut mode {
91 Mode::Normal => {
92 if map.get(&key) == Some(&Action::EnterCommandMode) {
93 println!(": -> enter command mode");
94 mode = Mode::Command(String::new());
95 } else {
96 // Any other normal-mode key would run its own binding; this
97 // demo only cares about the `:` entry point.
98 }
99 }
100 Mode::Command(buf) => match key.key() {
101 Key::Char(c) => {
102 buf.push(c);
103 println!(" :{buf}");
104 }
105 Key::Enter => {
106 // Take the buffer out and drop back to Normal in one move.
107 let name = std::mem::take(buf);
108 match resolve(&name) {
109 Some(action) => println!(" :{name} -> fire {action:?}"),
110 None => println!(" :{name} -> unknown command, no-op"),
111 }
112 mode = Mode::Normal;
113 }
114 Key::Esc => {
115 println!(" esc -> abandon command line");
116 mode = Mode::Normal;
117 }
118 // `Key` is #[non_exhaustive]; other editing keys are ignored here.
119 _ => {}
120 },
121 }
122 }
123}Sourcepub fn iter(&self) -> impl Iterator<Item = (&KeyInput, &A)>
pub fn iter(&self) -> impl Iterator<Item = (&KeyInput, &A)>
Iterates over every (input, action) binding.
Order is unspecified. This is the data source for listing and search
(the discovery layer) built on top of keymap-core.
Examples found in repository?
15fn main() {
16 let mut keymap = Keymap::new();
17 keymap.bind(
18 KeyInput::new(Key::Char('q'), Modifiers::CTRL),
19 Command {
20 id: "quit",
21 description: "Quit the application",
22 },
23 );
24 keymap.bind(
25 KeyInput::new(Key::Char('s'), Modifiers::CTRL),
26 Command {
27 id: "save",
28 description: "Save the current file",
29 },
30 );
31 keymap.bind(
32 KeyInput::new(Key::F(1), Modifiers::NONE),
33 Command {
34 id: "help",
35 description: "Show the help overlay",
36 },
37 );
38
39 // Listing: collect and sort by the canonical key string.
40 let mut listing: Vec<(String, &Command)> = keymap
41 .iter()
42 .map(|(input, cmd)| (input.to_string(), cmd))
43 .collect();
44 listing.sort_by(|a, b| a.0.cmp(&b.0));
45
46 println!("all bindings:");
47 for (key, cmd) in &listing {
48 println!(" {key:>8} {:<6} — {}", cmd.id, cmd.description);
49 }
50
51 search(&listing, "sav");
52 search(&listing, "zzz");
53}