rune.input
Read and modify the input buffer from scripts — pickers, binds, and completion are all built on these. For the interactive side (default keys, the multiline draft editor, history navigation, and tab completion), see Input & History.
Quick reference
Section titled “Quick reference”rune.input.get() -- current input textrune.input.set(text) -- replace the input textrune.input.get_cursor() -- zero-based UTF-8 byte offsetrune.input.set_cursor(pos) -- move to a UTF-8 byte offsetrune.input.open_editor(initial?) -- edit in $EDITOR; returns edited_text, okrune.input.word_left() -- move cursor to the previous word boundaryrune.input.word_right() -- move cursor to the next word boundaryrune.input.delete_word() -- delete the word before the cursorget/set operate on the whole buffer; the word operations combine
them with cursor moves. The default word-editing keys use the built-in
input actions instead, which edit the input directly. The "input_changed"
hook event fires whenever the buffer changes,
including typing, history or completion, rune.input.set, and the draft left
after submission. Script edits notify observers before rune.input.set returns;
setting the same text again does not notify. Observers may edit the draft again,
so these callbacks can nest under the script watchdog. CRLF and bare CR in
script edits become LF, matching the editor’s newline convention. Applying a
script edit in the UI does not fire a second notification.
Cursor positions are zero-based UTF-8 byte offsets, using the same byte units
as Lua 5.1 string operations. set_cursor clamps positions to the input and
snaps an offset inside a multibyte sequence to the preceding UTF-8 code point
boundary.
Setting text containing a newline, tab, or terminal control byte activates the
visible draft editor and initially selects Verbatim. An explicit mode choice made
with Alt+V or restored from history persists through text changes. Replacing
the draft with one non-empty plain line preserves its mode; setting it to ""
clears the draft and resets to Command. See
Multiline draft editor
for its submission semantics and limits.
Input bindings
Section titled “Input bindings”rune.bind accepts either a Lua callback or a named
internal action:
-- Arbitrary Lua behaviorrune.bind("f1", function() rune.send("look") end)
-- Internal action, resolved in the current input contextrune.bind("enter", "input.submit")rune.bind({"ctrl+j", "shift+enter", "ctrl+enter"}, "input.newline")| Internal action | Behavior | Default keys |
|---|---|---|
input.submit |
Submit the draft using its current Command/Verbatim mode | Enter |
input.newline |
Insert a newline, opening the draft editor if necessary | Ctrl+J, Shift+Enter, Ctrl+Enter |
input.toggle_mode |
Switch Command/Verbatim interpretation | Alt+V |
input.open_editor |
Edit the current draft in $EDITOR; apply a successful result without submitting |
Ctrl+E |
input.cancel |
Cancel according to the current input context, as below | Esc |
input.delete_word |
Delete from the start of the previous word to the cursor | Ctrl+W, Alt+Backspace |
input.word_left |
Move the cursor to the start of the previous word | Alt+Left, Ctrl+Left |
input.word_right |
Move the cursor to the start of the next word | Alt+Right, Ctrl+Right |
| Cancel context | Behavior |
|---|---|
| Normal input | Clear the draft |
| Multiline draft editor | First press requests confirmation; a second cancel press discards the draft |
| Inline or modal picker | Close without accepting; preserve the draft |
| Scrollback search | Cancel search and restore the previous view |
The word actions apply in normal input, the inline picker, and the draft editor. They run in the input itself rather than calling Lua, so a held key repeats at the terminal’s rate even while scripts are busy.
Any intervening non-cancel key or binding update dismisses multiline discard confirmation. Rebinding cancel changes it across all these contexts. Ctrl+C also remains an overlay interrupt; outside overlays it keeps its existing Lua binding. Modal pickers and search capture other actions. Printable bindings retain the input contexts’ typing protection; prefer non-printable keys for input actions.
A new key adds an alias; an existing key replaces its assignment. To move an action, bind the new key and explicitly unbind the old one:
rune.bind("f2", "input.open_editor")rune.unbind("ctrl+e")rune.bind("ctrl+g", "input.cancel")rune.unbind("esc")Arrays create independent bindings and return an array of handles. A single key returns one handle. Removing one alias leaves the others intact. Disabling a binding or its group prevents its action. Defaults are ordinary registrations; unbinding them does not reveal a hidden default.
Multiline hints use the earliest registered active binding for each action; search uses the same cancel binding for its hint. If no active binding remains, its hint disappears. Shift+Enter and Ctrl+Enter require distinct terminal key reporting; Ctrl+J is the portable newline alternative.
Callbacks execute through Session and Lua. Internal actions are resolved by the TUI; submission proceeds to Session for processing, and opening the external editor asks Session to suspend the terminal and apply the edited result. No Lua callback or Lua watchdog is involved in a named input action. Action strings are identifiers, not commands to send or Lua expressions; unknown names are errors.
The input.open_editor binding edits the current draft automatically. The Lua
function below instead returns text to its caller, which decides how to use it.
rune.input.open_editor
Section titled “rune.input.open_editor”rune.input.open_editor(initial?) -> edited_text, okinitial(string, optional) — text to seed the editor buffer with.
Opens $EDITOR (falling back to vi/notepad) on a temp file; the
client suspends until the editor exits. On success, CRLF and bare CR are
normalized to LF and exactly one final LF (the conventional text-file
terminator) is removed. All other authored whitespace — indentation, tabs,
trailing spaces, and additional blank lines — is preserved. The function
returns that text and true, including "", true for an intentionally empty
file. It returns "", false when the editor could not run or exited with an
error.
The default ctrl+e bind is a thin wrapper. A multiline result enters the
draft editor, respecting an explicit mode choice and preserving newlines:
rune.bind("ctrl+e", function() local text, ok = rune.input.open_editor(rune.input.get()) if ok then rune.input.set(text) endend)rune.history
Section titled “rune.history”rune.history.get() -- submitted text, oldest firstrune.history.add(cmd) -- append a normal command entryHistory survives /reload. After processing a submission, Rune stores the
surviving lines together with their original Command or Verbatim mode. Consumed
and invalid lines are omitted. An empty final history string is not added.
Input hooks, echo hooks, and command handlers do not see the current submission
in history while it is running. Explicit add calls remain visible immediately.
Arrow navigation and ctrl+r restore the block and its stored mode, so even a
one-line Verbatim entry returns to the draft editor. Consecutive entries are
deduplicated only when both their text and mode match.
get() returns the text-only view and does not expose the stored mode.
add(cmd) adds a normal command entry for scripts that want a synthetic
command (one sent by an alias, say) to be recallable. Because it creates a
normal command entry, cmd may contain several newline-separated commands
and tabs. Invalid UTF-8 and terminal controls are rejected. Verbatim history entries come only
from submitted verbatim input.
Related: Input & History guide · rune.bind · rune.ui.picker