Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

WriteMeans
tab, enter, esc, spacenamed keys
up down left right home end pageup pagedownnavigation keys
backspace delete insertediting keys
f1f12function keys
a, :, ?single characters
ctrl+sCtrl + S
alt+xAlt + X
shift+tabShift + Tab (normalized to BackTab)
ctrl+shift+pcombined modifiers
g hpress 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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
.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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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:

#![allow(unused)]
fn main() {
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).