Skip to main content

Keymap

Struct Keymap 

Source
#[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>

Source

pub fn new() -> Self

Creates an empty keymap.

Examples found in repository?
examples/basic_lookup.rs (line 16)
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
Hide additional examples
examples/discovery.rs (line 16)
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}
examples/modal_keymap.rs (line 39)
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}
examples/ex_command.rs (line 58)
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}
Source

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?
examples/basic_lookup.rs (line 17)
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
Hide additional examples
examples/discovery.rs (lines 17-23)
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}
examples/modal_keymap.rs (line 40)
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}
examples/ex_command.rs (line 59)
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}
Source

pub fn unbind(&mut self, input: &KeyInput) -> Option<A>

Removes any binding for input, returning the action that was bound.

Source

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?
examples/basic_lookup.rs (line 33)
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
Hide additional examples
examples/ex_command.rs (line 92)
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}
Source

pub fn contains(&self, input: &KeyInput) -> bool

Returns true if input has a binding.

Source

pub fn len(&self) -> usize

The number of bindings in the table.

Source

pub fn is_empty(&self) -> bool

Returns true if the table has no bindings.

Source

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?
examples/discovery.rs (line 41)
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}

Trait Implementations§

Source§

impl<A: Clone> Clone for Keymap<A>

Source§

fn clone(&self) -> Keymap<A>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<A: Debug> Debug for Keymap<A>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<A> Default for Keymap<A>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<A> Freeze for Keymap<A>

§

impl<A> RefUnwindSafe for Keymap<A>
where A: RefUnwindSafe,

§

impl<A> Send for Keymap<A>
where A: Send,

§

impl<A> Sync for Keymap<A>
where A: Sync,

§

impl<A> Unpin for Keymap<A>
where A: Unpin,

§

impl<A> UnsafeUnpin for Keymap<A>

§

impl<A> UnwindSafe for Keymap<A>
where A: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.