# Input Pipeline
The input pipeline turns a `KeyEvent` into one of: an action to run, plain text
for a field, a "waiting for the next key" state, or a cancellation. You rarely
touch it directly — `handle_key` runs it for you — but you do need to know the
binding syntax and how text input and chord sequences work.
## Binding syntax
Bindings are plain strings. Modifiers are joined with `+`; a multi-key sequence
is space-separated.
| `tab`, `enter`, `esc`, `space` | named keys |
| `up` `down` `left` `right` `home` `end` `pageup` `pagedown` | navigation keys |
| `backspace` `delete` `insert` | editing keys |
| `f1` … `f12` | function keys |
| `a`, `:`, `?` | single characters |
| `ctrl+s` | Ctrl + S |
| `alt+x` | Alt + X |
| `shift+tab` | Shift + Tab (normalized to BackTab) |
| `ctrl+shift+p` | combined modifiers |
| `g h` | press `g`, then `h` (a sequence) |
Modifier names are case-insensitive and have short forms: `ctrl`/`control`/`c`,
`alt`/`meta`/`m`, `shift`/`s`. So `ctrl+s` and `c+s` are the same binding.
> This is the **string** format — `ctrl+`, not the `C-` you may have seen
> elsewhere. The examples all use this form.
## Binding keys to actions
On the builder, `.bind(mode, binding, action)` registers a key in a mode:
```rust
TuiPages::builder(View::Home)
.bind(modes::GENERAL, "tab", Action::FocusNext)
.bind(modes::GENERAL, "enter", Action::Select)
.bind(modes::GENERAL, "g h", Action::GotoHome) // sequence
.bind(modes::GLOBAL, "ctrl+c", Action::Quit) // works everywhere
```
A key only fires if its mode is in the current page's `PageSpec::modes`.
`GLOBAL` is always active.
## Multi-key sequences
Bind a space-separated sequence and the runtime waits for the rest after the
first key:
```rust
.bind(modes::GENERAL, "g h", Action::GotoHome)
.bind(modes::GENERAL, "g n", Action::GotoNotes)
.bind(modes::GENERAL, "ctrl+x ctrl+c", Action::ForceQuit)
```
While waiting, `handle_key` returns `TuiPagesStatus::Waiting(hints)` — those
hints are the candidate continuations, which you can show in a status bar. If
the next key doesn't match, or the timeout (`input_timeout_ms`, default 1000ms)
elapses, the sequence cancels.
## Text input
By default every key is looked up as a binding. When a page sets
`accepts_text_input(true)`, plain character keys that aren't bound instead flow
through as text:
```rust
PageSpec::new()
.accepts_text_input(true)
.modes(vec![modes::INSERT])
```
Now `handle_key` returns `TuiPagesStatus::TextHandled` for ordinary typing, and
your handler's `handle_text(chord, ctx, state)` gets the `KeyChord`. Bound keys
(like `esc` to leave the field) still fire as actions.
## What `handle_key` returns
`handle_key` returns a `TuiPagesOutput` whose `status` is one of:
```rust
pub enum TuiPagesStatus<A> {
ActionHandled, // a binding matched; your handler ran
TextHandled, // text flowed to handle_text
Waiting(Vec<InputHint<A>>), // mid-sequence; show the hints
Cancelled, // sequence expired or didn't match
CommandIncomplete(_), // from submit_command — see Commands
CommandUnknown,
CommandEmpty,
}
```
and a `quit_requested: bool` set when an effect asked to quit. Most loops only
check `quit_requested` and stash `Waiting` hints for display:
```rust
let output = tui.handle_key(key, state)?;
match output.status {
TuiPagesStatus::Waiting(hints) => waiting = hints,
_ => waiting.clear(),
}
if output.quit_requested {
return Ok(());
}
```
## Parsing bindings yourself
If you load keybindings from a config file, the parse functions are public:
```rust
use tui_pages::{parse_binding, try_parse_binding, parse_key, try_parse_key};
// Lenient: drops tokens it can't parse. Good for trusted, in-code strings.
let chords: Vec<KeyChord> = parse_binding("ctrl+x z");
// Strict: reports the first bad token. Use for user-editable config.
match try_parse_binding("ctrl+shft+x") {
Ok(chords) => { /* ... */ }
Err(e) => eprintln!("bad binding: {e}"), // UnknownKey("ctrl+shft+x")
}
```
`parse_binding` returns a *sequence* (`Vec<KeyChord>`); `parse_key` parses a
single chord. To build a chord from a live event, use
`KeyChord::from_event(&key_event)`.