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

Commands

Commands are the typed kind: :quit, :n, go to notes. You register a name with aliases, the user types a string, and the runtime resolves it (by prefix) to one of your actions.

The crate gives you the resolver. It does not give you a command palette — the text box, the cursor, the open/close state are ordinary app state you own and render. The full example builds a complete :-style palette this way in about 30 lines.

Registering commands

On the builder, .command(name, aliases, action):

#![allow(unused)]
fn main() {
TuiPages::builder(View::Home)
    .command("Go to Home",  ["h", "home"],   Action::GotoHome)
    .command("Go to Notes", ["n", "notes"],  Action::GotoNotes)
    .command("Quit",        ["q", "quit"],   Action::Quit)
}

The first argument is a display name (shown in hints); the rest are the aliases the user can type. Matching is by prefix, so not resolves to notes.

Submitting input

When the user confirms their typed string, hand it to submit_command. It resolves and runs the action exactly like a key binding would — including applying any effects and setting quit_requested:

#![allow(unused)]
fn main() {
let quit = tui.submit_command(&input, state)?.quit_requested;
}

The returned TuiPagesOutput::status tells you what happened:

#![allow(unused)]
fn main() {
pub enum TuiPagesStatus<A> {
    ActionHandled,                       // resolved and ran
    CommandIncomplete(Vec<CommandHint>), // a prefix of several commands
    CommandUnknown,                      // no match
    CommandEmpty,                        // empty input
    // ...
}
}

A palette is just app state

The runtime owns no palette. You own a flag and a string, open it on a key, feed keystrokes to your string, and call submit_command on Enter. This is the whole palette loop from the full example:

#![allow(unused)]
fn main() {
// State you own:
//   palette_open: bool,
//   palette_input: String,

if state.palette_open {
    match key.code {
        KeyCode::Enter => {
            let input = state.palette_input.clone();
            let quit = tui.submit_command(&input, state)?.quit_requested;
            close_palette(tui, state);
            if quit { return Ok(()); }
        }
        KeyCode::Esc       => close_palette(tui, state),
        KeyCode::Backspace => { state.palette_input.pop(); }
        KeyCode::Char(c)   => state.palette_input.push(c),
        _ => {}
    }
    continue; // palette swallows the key; don't fall through to handle_key
}
}

Opening it is an action that sets the flag and (optionally) opens an overlay focus target so your renderer knows to draw the bar:

#![allow(unused)]
fn main() {
Action::OpenPalette => {
    state.palette_open = true;
    state.palette_input.clear();
    ActionOutcome::effect(TuiEffect::Focus(FocusIntent::Open(
        FocusTarget::Overlay(Overlay::CommandBar),
    )))
}
}

Closing it clears your state and the overlay:

#![allow(unused)]
fn main() {
fn close_palette(tui: &mut App, state: &mut State) {
    state.palette_open = false;
    state.palette_input.clear();
    tui.focus.clear_overlay();
}
}

Live hints while typing

To show completions as the user types, query the resolver directly. tui.commands is the public CommandResolver:

#![allow(unused)]
fn main() {
match tui.commands.process(&state.palette_input) {
    CommandResponse::Incomplete(hints) => show(hints), // Vec<CommandHint>
    CommandResponse::Execute(_)        => {}           // a full match exists
    CommandResponse::Unknown           => show_error(),
    CommandResponse::Empty             => {}
}
}

Each CommandHint is { alias: String, action_name: String } — the alias to complete to, and its display name.

The resolution timeout (for multi-step command states) defaults to 1000ms and is configurable with .command_timeout_ms(ms) on the builder.