Implement the deferred I1b readline shortcuts in the command input field (ADR-0049, closing issue #29): Esc clear a partly-typed command (only when no completion memo) Ctrl-A cursor to line start (Home alias) Ctrl-E cursor to line end (End alias) Ctrl-W delete the previous word (readline-style, UTF-8 safe) Ctrl-K kill to end of line Ctrl-U kill to start of line Esc precedence is preserved: a live Tab-completion memo still wins (Esc undoes the completion first, ADR-0022); Esc clears only when no memo is alive. While a sidebar panel is focused (Ctrl-O), Esc exits navigation mode upstream and never clears the input draft. Cursor-only keys leave history navigation intact like Home/End; buffer-mutating keys end it like Backspace. New helpers clear_input / delete_prev_word / kill_to_end / kill_to_start in src/app.rs. 22 new Tier-1 tests (2458 pass / 0 fail / 0 skip, clippy clean). ADR-0049 amends ADR-0046's OOS list; requirements.md I1b marked done.
5.6 KiB
ADR-0049: Input-field readline keymap — Esc-clear + Ctrl-A/E/W/K/U (I1b)
Status
Accepted + implemented 2026-06-12 (issue #29). Closes Gitea #29
("Command input keystroke support") and the deferred I1b readline
requirement in requirements.md. Every fork below was escalated to the
user and user-chosen before any code was written; implemented test-first
(22 new Tier-1 tests in src/app.rs, all green; clippy nursery clean).
This ADR amends ADR-0046, which explicitly listed "readline
shortcuts (I1b)" in its out-of-scope set: that item is now in scope and
decided here. It is orthogonal to ADR-0003's input-mode model (simple
vs advanced, the : sigil) — these are editing keys within the input
field, not mode or sigil changes — and it extends the single-line cursor
editing already shipped under requirement I1a (Left/Right/Home/End/
Backspace/Delete, app.rs).
Context
The input field already supported in-line cursor editing (I1a): Left/ Right by char (UTF-8 aware), Home/End to the extremes, Backspace/Delete. Two gaps remained, raised in issue #29:
- No way to clear a partly-typed command in one keystroke — a user who started typing the wrong thing had to hold Backspace.
- No readline cursor/kill shortcuts (Ctrl-A/Ctrl-E and friends) for keyboards without Home/End and for muscle-memory in a command-driven workflow. This is requirement I1b, deferred by ADR-0046.
Esc was free in the input field except that a live Tab-completion
memo consumes it first (to undo the completion in one keystroke,
ADR-0022). Ctrl-A/E/W/K/U were unbound. The existing chords are Ctrl-C
(quit), Ctrl-O (nav focus cycle, ADR-0046), and Ctrl-] (demo caption
toggle, ADR-0047) — none collide with a/e/w/k/u.
Decision
Bind the following in the input field (non-modal, non-navigation,
both input modes), in App::handle_key:
| Key | Action |
|---|---|
Esc |
Clear the input (empty buffer, cursor→0, scroll→0) |
Ctrl-A |
Cursor to line start (alias of Home) |
Ctrl-E |
Cursor to line end (alias of End) |
Ctrl-W |
Delete the word before the cursor |
Ctrl-K |
Kill from the cursor to end of line |
Ctrl-U |
Kill from start of line to the cursor |
Behavioural rules:
- Esc precedence. A live completion memo still wins: the first Esc undoes the completion (ADR-0022), and Esc only clears when no memo is alive. This is a natural progression — Esc once to back out the completion, Esc again to clear.
- Esc does not clear while navigating the sidebar. When a sidebar
panel is focused (Ctrl-O, ADR-0046 DC3),
handle_keyroutes every key to the navigation handler before the input-field keymap, where Esc exits navigation mode (nav_exit). Entering nav mode never touched the input buffer, so Esc-to-close-the-panel returns focus to the input with the partly-typed command intact — it cannot reach the clear binding. Locked by a regression test. - Single Esc clears (user-chosen over double-Esc). Discoverable and fast; the trade-off (an accidental Esc wipes an unsubmitted line) was accepted. A submitted line is always recoverable from history; only unsubmitted draft text is lost.
- Cursor-only keys don't touch history navigation. Ctrl-A/Ctrl-E, like Home/End, move the cursor without ending history recall.
- Buffer-mutating keys end history navigation. Esc-clear and
Ctrl-W/K/U call
cancel_history_navigation(the cleared/edited line is the new draft), matching Backspace/Delete. - Ctrl-W is readline-style and UTF-8 safe. It eats any run of trailing whitespace, then the preceding run of non-whitespace; word boundaries are found on char boundaries so multi-byte words delete cleanly. It only ever deletes back to the cursor (a mid-line Ctrl-W leaves the suffix intact).
Helpers added: clear_input, delete_prev_word, kill_to_end,
kill_to_start (src/app.rs), mirroring the existing cursor_left /
delete_before_cursor style.
Forks (all user-chosen)
- Esc semantics: single-Esc-clears, not double-Esc — discoverable over accident-proof.
- Scope: the full I1b set (Esc-clear + Ctrl-A/E/W/K/U), not just the issue's literal Ctrl-A/E + Esc — closes the whole I1b requirement in one pass rather than leaving Ctrl-W/K/U for a follow-up.
- Documentation: a new ADR (this one), recording the input-field keymap convention and amending ADR-0046's OOS list — over folding it into ADR-0046 or shipping it I1a-style with no ADR.
Consequences
- I1b is complete;
requirements.mdI1b moves to[x]. - The new keys are not yet advertised on screen. Surfacing per-focus keybindings in the bottom status line is issue #27's domain (a separate, in-design UX change); this ADR makes the keys work, #27 will make them discoverable.
- Demo-mode badges (ADR-0047) are not extended to the new Ctrl-
chords here. Esc already badges as
[ESC]; Ctrl-A/E/W/K/U are glyph-less and would be invisible in an asciinema cast. Whether to add[CTRL-A]…[CTRL-U]badges is left to ADR-0047's scope and flagged as a follow-up — it is a cast-polish concern, not a #29 requirement.
Out of scope
- On-screen keybinding hints for the input field (issue #27).
- Demo badges for the new chords (ADR-0047 follow-up; flagged above).
- Multi-line input (I1) and its Ctrl-Enter submit — unrelated, still deferred.
- Word-wise cursor motion (Alt-B/Alt-F) and transpose/yank — not requested; not part of I1b.