Skip to content

test(input): hold the shared key encoder against ghostty's - #183

Draft
Ayman Bagabas (aymanbagabas) wants to merge 6 commits into
aymanbagabas/key-encodingfrom
aymanbagabas/key-encoder-oracle
Draft

test(input): hold the shared key encoder against ghostty's#183
Ayman Bagabas (aymanbagabas) wants to merge 6 commits into
aymanbagabas/key-encodingfrom
aymanbagabas/key-encoder-oracle

Conversation

@aymanbagabas

@aymanbagabas Ayman Bagabas (aymanbagabas) commented Aug 26, 2026

Copy link
Copy Markdown
Member

Differential-tests tui-test's shared key encoder against ghostty's, which is a reference implementation maintained by people who work on nothing else.

Stacked: #180 -> #182 -> this. The oracle needs encode_key from #182 and ghostty's keyboard_mode() from #180 — without the latter it compared ghostty in Kitty mode against the shared encoder in legacy mode, which produced 329 phantom disagreements. Review the two beneath first; this PR's own change is a single test file.

Why

Three of the four backends have no key encoder of their own and always fall back to keys.rs. A bug there is a bug on alacritty, rio, and xterm.js at once — and nothing was checking keys.rs against anything but its own expectations. Ghostty ships an encoder, so it can be held up as an oracle.

It has already paid for itself

Four real bugs, all in the PRs beneath this one, all found by this test and none by the hand-written cases:

symptom cause
key press A sent a tokens were lowercased wholesale, right for a named key and wrong for a character
key press Space sent nothing ghostty encodes a text-bearing key with no utf8 as empty, and only single-character tokens set it
key press Alt+a sent a set_options_from_terminal resets macos_option_as_alt to False, so Alt composed instead of prefixing ESC
Shift+a sent CSI 97;2u instead of A consumed_mods was never set, so ghostty reported a Shift that had been spent producing the text
kitty_keyboard = false still encoded Kitty set_options_from_terminal reads flags off the live terminal, going around the profile

All five would have shipped. That is the argument for the test.

The last one is the interesting one: it only exists when #180 and #182 are both present, so neither branch could surface it on its own, and it is why #182 is now stacked on #180 rather than parallel to it.

What it compares

495 events: every token whose encoding both encoders claim to define, across five terminal states — legacy, application cursor keys (CSI ?1h), and three Kitty mode combinations (>1u, >3u, >15u).

Two spellings normalized

Both are spellings, not meanings, and the spec makes both legal:

  • Event type. With REPORT_EVENT_TYPES on, ghostty writes the press event as :1; kitty and xterm omit it because press is the default. CSI 1;5:1A and CSI 1;5A decode identically.
  • Default parameters. Ghostty writes CSI 1;1A where kitty and xterm write CSI A.

Normalizing keeps those modes in the matrix rather than excluding them. Both normalizers are deliberately narrow — the event-type one only strips a :1 immediately before the final byte, so the :65 alternate-key sub-parameter in CSI 97:65;6u is untouched.

What is excluded, and why

Combinations the legacy scheme cannot express. Ghostty upgrades them to CSI-u or xterm's modifyOtherKeys form: Ctrl+Tab becomes CSI 27;5;9~ where legacy has a bare \t with the Ctrl silently lost, and Ctrl+Shift+0 becomes CSI 41;5u. That is a richer answer to a different question, not a different answer to this one, so asserting it and then explaining it away would only teach readers to ignore the test. F3 is excluded too: ghostty and kitty spell it CSI 13~, xterm spells it SS3 R, and that split predates both.

The one real disagreement

Named in ORACLE_KNOWN_DIVERGENCES with its reasoning rather than deleted:

Under report-all-keys, ghostty sends Space as the text ` ` where the shared encoder sends CSI 32u. Report-all-keys means what it says — the Kitty spec has text-producing keys report as escape codes under this flag — so the shared encoder looks right here and it is worth raising upstream.

The test asserts that a listed divergence still diverges, so the list cannot silently go stale: if ghostty changes, the entry fails as loudly as a new disagreement would.

Result

495 compared, 0 unexplained disagreements, 1 documented. cargo test --workspace --features tui-test-rs/ghostty,tui-test-rs/rio,tui-test-rs/xtermjs gives 533 passed, 0 failed.

…ey-encoder-oracle

# Conflicts:
#	README.md
#	SKILL.md
#	crates/tui-test/src/terminal/ghostty/core.rs
#	crates/tui-test/src/terminal/ghostty/mod.rs
Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
…r-oracle

# Conflicts:
#	crates/tui-test/src/terminal/ghostty/core.rs
Three of the four backends have no key encoder of their own and always use
`keys.rs`, so a bug there is a bug on three backends at once, and nothing was
checking it against anything. Ghostty ships an encoder maintained by people
who work on nothing else, which makes it a reference implementation worth
comparing to.

The comparison runs 495 events: every token whose encoding both encoders
claim to define, across legacy, application cursor keys, and three Kitty mode
combinations.

It has already paid for itself. It found `key press A` sending `a`, `Space`
encoding to nothing, `Alt+a` losing its modifier, and `Shift+a` reporting a
modifier that had been consumed producing the text. All four are fixed in the
commits this one sits on.

Two spellings are normalized before comparing, because they are spellings
rather than meanings. Under event reporting ghostty writes the press event
type out as `:1` and the default parameters as `1;1`, where kitty and xterm
omit both; the spec makes both legal and every consumer accepts either.
Normalizing keeps those modes in the matrix instead of excluding them.

Combinations legacy cannot express are excluded rather than asserted and then
explained away. Ghostty upgrades `Ctrl+Tab` to `CSI 27;5;9~` where legacy has
a bare `\t` with the Ctrl lost, and `Ctrl+Shift+0` to `CSI 41;5u`. That is a
richer answer to a different question.

What is left over is one real disagreement, named in
`ORACLE_KNOWN_DIVERGENCES` with its reasoning rather than dropped: under
report-all-keys ghostty sends Space as text where the shared encoder sends
`CSI 32u`. The Kitty spec appears to be on the shared encoder's side. The
test asserts a known divergence still diverges, so the list cannot go stale.

Signed-off-by: Ayman Bagabas <ayman.bagabas@gmail.com>
…r-oracle

# Conflicts:
#	crates/tui-test/src/terminal/ghostty/core.rs
#	crates/tui-test/src/terminal/ghostty/mod.rs
…r-oracle

# Conflicts:
#	crates/tui-test/src/terminal/ghostty/core.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant