Reference

Key bindings

TUI input

The session UI reads raw keystrokes and negotiates richer terminal keyboard protocols at startup. These are the bindings the current TUI recognizes.

Editing & cursor

The input box is multi-line and accepts full UTF-8 text. It grows to fit the draft rather than scrolling internally.

KeyAction
any printable keyInsert text at the cursor.
← / →Move one codepoint.
Alt+B / Alt+F
Ctrl+← / Ctrl+→
Move by word. Terminals vary in which encoding they send; panto accepts the common ones.
Ctrl+A / Ctrl+EJump to the start / end of the current logical line.
Home / EndJump to the start / end of the whole buffer.
↑ / ↓At the buffer top or bottom, recall the previous or next history entry. Otherwise move one logical line up or down, preserving the byte column where possible.
Backspace / DeleteDelete the previous / next codepoint.
Alt+Backspace / Ctrl+BackspaceDelete the previous word.
Ctrl+U / Ctrl+WDelete to the start of the current logical line / delete the previous word.
pasteInsert the pasted bytes literally; pasted newlines do not auto-submit.

Ctrl+A and Ctrl+E use newline-delimited logical lines, while Home and End use the whole buffer. Words are separated only by ASCII space, tab, and newline. Input history is in memory, recorded on submit, and skips empty lines and consecutive duplicates.

Deletion is plain: there is no kill ring, yank buffer, or undo/redo. There is no vi mode, insert/normal mode, or user-remappable keymap.

Submit & newline

KeyAction
EnterWhen idle, submit the current input. During a live turn, ordinary text is queued as steering text and injected at the next tool boundary; slash commands remain queued for the next run, while ! lines execute at the tool boundary.
Ctrl+EnterDuring a live turn, ordinary text is queued as a follow-up turn to run after the current turn completes; slash commands and shell lines use their command/shell paths instead of becoming follow-up turns.
Shift+EnterInsert a newline. This is recognized from Kitty keyboard protocol, xterm modifyOtherKeys, or a bare \n byte.

Queued lines are not echoed when queued. Ordinary text is echoed only when injected or executed later; slash commands and shell lines are dispatched through their own paths. The footer reports separate counts, such as esc interrupt · 1 steering · 2 follow-up.

A terminal or user key mapping that emits a bare \n is treated as Shift+Enter on every terminal, whether or not Kitty negotiation succeeded. A stock Terminal.app-style mapping emits \r for both keys, so Shift+Enter remains indistinguishable from Enter and submits.

Selectors & control

Control bindings

KeyAction
Ctrl+TOpen the model selector. Inside the /tree browser, set its filter to “no tools”.
Ctrl+ROpen the reasoning selector.
Ctrl+OCollapse / expand all tool-output cards. Inside /tree, select the default filter.
Ctrl+YOpen the theme picker. /theme opens the same picker.
Ctrl+GOpen the current draft in [tui] editor, then $VISUAL, then $EDITOR, then vi. It is swallowed while a turn is running.
EscWith a selector open, cancel it. During a live turn, interrupt the turn without requesting exit. Otherwise, clear the input. A lone Esc interrupts immediately during a turn; while a shell command runs, it cancels that command.
Ctrl+C / Ctrl+DWhen idle, exit cleanly. During a live turn, request cancellation and exit after the turn unwinds.
TabComplete a command or path. It is handled by the app, not inserted into the input box.

An open selector is modal and consumes every key except Ctrl+C and Ctrl+D. The initial tool-card state can be set with [tui] tools_collapsed. When idle, a lone Esc waits 30 ms to distinguish it from the start of an escape sequence; during a live turn it is committed immediately.

Selectors

Selectors are filterable: type to narrow the list, use ↑/↓ or Ctrl+N/Ctrl+P to move, and press Enter to accept. Navigation wraps at both ends. Ctrl+H deletes one filter character, Ctrl+W deletes its trailing word, and Ctrl+U clears the filter. Esc cancels. Modified printable filter input is ignored, but modified arrows still navigate and modified Backspace still edits the filter.

Matches are case-insensitive and ranked as label prefix, label substring, label subsequence, detail substring, then detail subsequence. The command picker searches command names only. Completion-style command and path pickers also accept with Tab.

Command and path completion

Typing / into an empty input opens the command-completion popup. Tab completes a command when the first token begins at buffer position 0 with / and contains no second slash. Otherwise it completes a path from the directory named by the token (the current directory when the token has no /): panto inserts the longest common prefix and opens a picker only when more than one candidate remains. Directories receive a trailing /; dotfiles appear only when the typed prefix starts with .. No matches are a silent no-op.

In the command popup, Space accepts the highlighted command (or leaves unmatched text as typed) and returns to the input so arguments can be added. Backspace with an empty filter removes the /; Esc keeps the / and typed filter in the input. A paste extends the popup filter.

Session tree and sessions

Use /tree to open the session-history browser. It shows persisted messages in depth-first tree order, with the active branch first at each fork; system-prompt entries are hidden.

KeyTree filter
Ctrl+TNo tools.
Ctrl+UUser messages only.
Ctrl+AAll messages.
Ctrl+O / Shift+Ctrl+OSelect the default filter; both bindings do the same thing.
Ctrl+LShow named rows only.
Ctrl+FName the selected row; Enter saves and Esc cancels.

The active tree filter appears beside the search prompt. /theme and Ctrl+Y open the theme picker: highlighted themes preview live, Enter commits and remembers the choice, and Esc rolls back to the opening theme. The command picker includes Lua-registered commands and the theme picker includes extension-registered themes. Pressing an already active filter key returns to the default filter. Ctrl+C and Ctrl+D pass through every open selector to the quit path. Accepting a row branches the session from that message; if it is a user message, its text is recovered into the input for re-editing. Use /resume without an argument to open the session picker, where accepting a session rebuilds the session world.

Shell input

A line beginning with !cmd runs the command through /bin/sh -c at the tool boundary, stages its command and output for the next prompt, and does not start a turn. !!cmd does the same and immediately starts a turn. A leading space sends a literal !; bare ! reports [empty shell command]. The input box uses a shell colour while its buffer starts with !. While a shell command runs, a lone Esc or Ctrl+C cancels it; cursor-key escape sequences do not.

Scrolling

There are no scrolling key bindings. panto never uses the alternate screen: output stays inline in the primary terminal buffer and older content moves into the terminal's native scrollback. Page Up and Page Down are decoded but ignored. The input box grows instead of having an internal scroll window.

Selectors and remembered state

Choosing a model or reasoning level changes the live session and is remembered in $XDG_DATA_HOME/panto/state.toml (or ~/.local/share/panto/state.toml). Nothing is written back to config.toml or models.toml.

Terminal protocol

panto enables bracketed paste and negotiates keyboard protocol support when raw mode starts.

Shift + Enter

Kitty-capable terminals report Shift+Enter through Kitty keyboard protocol. xterm and tmux can use modifyOtherKeys. A bare \n is also decoded as Shift+Enter unconditionally; a terminal that sends \r for both keys cannot distinguish them.

TerminalShift + Enter
Kitty, Ghostty, foot, and similar Kitty-capable terminalsSupported via Kitty keyboard protocol.
xterm, tmux, and similar terminals with extended keysSupported via modifyOtherKeys.
A terminal or user mapping that sends bare \nRecognized as Shift+Enter.
Stock Terminal.app-style mappingIndistinguishable from Enter, so it submits.

Startup negotiation

After entering raw mode, panto writes \x1b[?2004h to enable bracketed paste, pushes Kitty flags 1 and 4 with \x1b[>5u, queries the active flags with \x1b[?u, and sends \x1b[c as a Primary Device Attributes sentinel. A non-zero Kitty reply activates Kitty keyboard protocol. The DA reply resolves the handshake: if no Kitty reply arrived, panto enables xterm modifyOtherKeys mode 2 with \x1b[>4;2m; no startup timeout is needed.

The Kitty push deliberately omits flag 2 (key release events) and flag 8 (report all keys). Release events can be decoded if sent, but they are never requested and are ignored by the input box. On teardown, panto restores the cursor, disables the fallback if used, pops Kitty state with \x1b[<u, and disables bracketed paste with \x1b[?2004l.

Bracketed paste

Bracketed paste means multi-line pastes are inserted literally instead of being reinterpreted as a sequence of keypresses. Partial pastes are retained until the closing marker arrives. If a terminal ignores bracketed-paste mode, pasted bytes are handled as ordinary keypresses, so an embedded newline can submit.

Terminal behavior

Mouse tracking and terminal focus events are not supported. Resize uses a libuv SIGWINCH handler, applies during a live turn when dimensions change, and forces a full redraw that clears scrollback. Output is framed with DEC 2026 synchronized-output sequences when supported (assumed unless TERM is empty or dumb).

Raw mode disables terminal signals, including ISIG, so Ctrl+C, Ctrl+Z, and Ctrl+\ are read as bytes. panto restores terminal state on SIGINT, SIGTERM, and SIGHUP, and on a Zig panic before the trace is printed. The TUI requires both stdin and stdout to be ttys; it exits with an error when either standard stream is not a terminal.

/help renders tui_app.key_bindings along with the registered slash commands. Built-ins include direct /model <provider:alias> and /reasoning <level>, plus /new, /status, and /quit. The visible keybind bar uses separate static per-mode tables that are manually kept in sync.