#[non_exhaustive]pub struct KeyInput { /* private fields */ }Expand description
A normalized key press: a Key plus the Modifiers held with it.
This is the unit a Keymap is keyed on, so its equality is
the equality used for binding lookup.
§Normalization
For character-producing keys, the terminal has already resolved which glyph
the keypress produces (A, !, あ, …). That resolution depends on the
keyboard layout, which this crate cannot reconstruct from the glyph alone, so
keymap-core does not re-case or re-map it.
Modifiers::SHIFT is cleared for a character key only on a bare press
(when Shift is the sole modifier), because there it is redundant with the
resolved glyph: Char('A') with Shift held normalizes to Char('A'), and
"shift+a" parses to the same KeyInput as "a". When Ctrl, Alt, or
Super is also held, terminals send the base glyph plus modifier bits
(e.g. Ctrl+Shift+s arrives as Char('s') + CTRL + SHIFT), so Shift is
the only signal distinguishing the chord from Ctrl+s and is kept. This
is what makes cmd+shift+s usable as a binding distinct from cmd+s.
Ctrl, Alt, and Super are always preserved; for non-character keys
(e.g. Tab, arrows) Shift is kept as a modifier.
Two consequences left to a later capability-aware layer (not handled here):
if a terminal reports the base-layout key (Char('a') + Shift) instead of
the shifted glyph, a bare press normalizes to Char('a') and won’t match a
Char('A') binding; and symbol chords (shift+1 → ! vs 1+Shift) cannot
be reconciled across terminals without layout knowledge.
Constructing a KeyInput directly via KeyInput::new does not apply this
normalization — it is applied at backend boundaries (e.g. the crossterm
conversion). Pass already-normalized values to new.
Implementations§
Source§impl KeyInput
impl KeyInput
Sourcepub const fn new(key: Key, mods: Modifiers) -> Self
pub const fn new(key: Key, mods: Modifiers) -> Self
Builds a KeyInput from an already-normalized key and modifier set.
Examples found in repository?
More examples
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}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}Sourcepub fn normalized(key: Key, mods: Modifiers) -> Self
pub fn normalized(key: Key, mods: Modifiers) -> Self
Builds a KeyInput, applying the shared [normalize] rule first.
This is the constructor every boundary that turns raw key parts into a
KeyInput should use — the string grammar, the crossterm adapter, and
the keymap-term byte decoder all funnel through it. Routing them through
one normalizing constructor is what guarantees a binding parsed from text
and an event produced at runtime land on the same KeyInput, so lookup
can’t silently miss. Prefer this over new unless the
parts are already known to be normalized.
Sourcepub const fn key(&self) -> Key
pub const fn key(&self) -> Key
The key that was pressed.
Examples found in repository?
52fn main() {
53 let mut map = Keymap::new();
54 map.bind(plain(':'), Action::EnterCommandMode);
55
56 // Stage 3's resolver: command name -> action. Same shape as
57 // `keymap_config`'s `FnMut(&str) -> Option<A>`, intentionally a plain
58 // closure rather than that crate's type.
59 let resolve = |name: &str| -> Option<Action> {
60 match name {
61 "w" => Some(Action::Write),
62 "q" => Some(Action::Quit),
63 "wq" => Some(Action::WriteQuit),
64 _ => None,
65 }
66 };
67
68 // A simulated keystroke stream: `:wq⏎`, then `:q⏎`, then an unknown `:zz⏎`.
69 let stream = [
70 plain(':'),
71 plain('w'),
72 plain('q'),
73 enter(),
74 plain(':'),
75 plain('q'),
76 enter(),
77 plain(':'),
78 plain('z'),
79 plain('z'),
80 enter(),
81 ];
82
83 let mut mode = Mode::Normal;
84 for key in stream {
85 match &mut mode {
86 Mode::Normal => {
87 if map.get(&key) == Some(&Action::EnterCommandMode) {
88 println!(": -> enter command mode");
89 mode = Mode::Command(String::new());
90 } else {
91 // Any other normal-mode key would run its own binding; this
92 // demo only cares about the `:` entry point.
93 }
94 }
95 Mode::Command(buf) => match key.key() {
96 Key::Char(c) => {
97 buf.push(c);
98 println!(" :{buf}");
99 }
100 Key::Enter => {
101 // Take the buffer out and drop back to Normal in one move.
102 let name = std::mem::take(buf);
103 match resolve(&name) {
104 Some(action) => println!(" :{name} -> fire {action:?}"),
105 None => println!(" :{name} -> unknown command, no-op"),
106 }
107 mode = Mode::Normal;
108 }
109 Key::Esc => {
110 println!(" esc -> abandon command line");
111 mode = Mode::Normal;
112 }
113 // `Key` is #[non_exhaustive]; other editing keys are ignored here.
114 _ => {}
115 },
116 }
117 }
118}Source§impl KeyInput
impl KeyInput
Sourcepub fn legacy_form(&self) -> LegacyForm
pub fn legacy_form(&self) -> LegacyForm
Reports how this chord fares under the legacy 7-bit C0 terminal encoding
(see LegacyForm and the module docs).
This is a pure, terminal-independent structural fact, not a reachability
verdict. Only the cases that are certain regardless of terminal are
reported as CollapsesTo /
Unrepresentable; everything else is
Representable, which means “has a legacy C0
encoding”, not “guaranteed to arrive on every terminal”.