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.