fix(tui): support named-key tokens in voice.record_key (space, enter, …)

Reviewer caught that the round-1 parser in #18994 rejected every
multi-character token, so a config value like ``ctrl+space`` (which the
CLI happily binds via prompt_toolkit's ``c-space`` rewrite in
``cli.py``) silently fell back to the documented Ctrl+B default —
re-introducing the same false-shortcut bug the PR was meant to fix,
just at a different surface.

Add explicit named-key support that mirrors what the CLI accepts:

* ``space``         (alias: ``spc``)        → matches ``ch === ' '``
* ``enter``         (alias: ``return``, ``ret``) → matches ``key.return``
* ``tab``                                   → matches ``key.tab``
* ``escape``        (alias: ``esc``)        → matches ``key.escape``
* ``backspace``     (alias: ``bs``)         → matches ``key.backspace``
* ``delete``        (alias: ``del``)        → matches ``key.delete``

``ParsedVoiceRecordKey`` gains an optional ``named`` field; ``ch``
holds either a single char (back-compat) or the canonical named token,
and the runtime matcher dispatches on ``named`` before checking the
modifier shape. Aliases collapse to one canonical name so
``ctrl+esc`` and ``ctrl+escape`` behave identically.

Unrecognised multi-character tokens (e.g. ``ctrl+spcae`` typo, or
unsupported keys like ``ctrl+f5``) still fall back to the Ctrl+B
default rather than silently disabling the binding — keeps the "typo
never silently kills the shortcut" guarantee.

Tests:

* ``parseVoiceRecordKey`` parametrised over every named token + each
  alias variant.
* New ``isVoiceToggleKey`` cases for space (ch-based match), enter
  (``key.return``), tab, escape, backspace, delete, including
  modifier-mismatch negatives.
* ``formatVoiceRecordKey`` renders named keys in title case
  (``Ctrl+Space``, ``Ctrl+Enter``).
* Existing fall-back-to-Ctrl+B contract preserved for empty input
  AND unrecognised multi-char tokens.

Full TUI suite: 559/559 pass; ``tsc --noEmit`` clean.

Refs #18994 (round-1 review feedback)

Co-authored-by: asheriif <ahmedsherif95@gmail.com>
This commit is contained in:
Tranquil-Flow
2026-05-04 12:04:03 -05:00
committed by Brooklyn Nicholson
co-authored by asheriif
parent 5231815854
commit 2e7bbf2ee7
2 changed files with 170 additions and 17 deletions
+69 -4
View File
@@ -90,13 +90,10 @@ describe('isVoiceToggleKey', () => {
})
describe('parseVoiceRecordKey (#18994)', () => {
it('falls back to Ctrl+B for empty / malformed input', async () => {
it('falls back to Ctrl+B for empty input', async () => {
const { DEFAULT_VOICE_RECORD_KEY, parseVoiceRecordKey } = await importPlatform('linux')
expect(parseVoiceRecordKey('')).toEqual(DEFAULT_VOICE_RECORD_KEY)
// Multi-character chunks are unsupported (CLI binds single keys), so a
// typo like "ctrl+space" falls back to the doc default.
expect(parseVoiceRecordKey('ctrl+space')).toEqual(DEFAULT_VOICE_RECORD_KEY)
})
it('parses ctrl+<letter> bindings', async () => {
@@ -116,6 +113,37 @@ describe('parseVoiceRecordKey (#18994)', () => {
expect(parseVoiceRecordKey('super+b').mod).toBe('super')
expect(parseVoiceRecordKey('win+b').mod).toBe('super')
})
it('parses named keys (space, enter, tab, escape, backspace, delete)', async () => {
const { parseVoiceRecordKey } = await importPlatform('linux')
// Every named token from the CLI's prompt_toolkit ``c-<name>`` set is
// accepted with both the canonical name and its common alias.
expect(parseVoiceRecordKey('ctrl+space')).toEqual({
ch: 'space',
mod: 'ctrl',
named: 'space',
raw: 'ctrl+space'
})
expect(parseVoiceRecordKey('alt+enter').named).toBe('enter')
expect(parseVoiceRecordKey('alt+return').named).toBe('enter') // ``return`` ↔ ``enter``
expect(parseVoiceRecordKey('ctrl+tab').named).toBe('tab')
expect(parseVoiceRecordKey('ctrl+escape').named).toBe('escape')
expect(parseVoiceRecordKey('ctrl+esc').named).toBe('escape') // ``esc`` alias
expect(parseVoiceRecordKey('ctrl+backspace').named).toBe('backspace')
expect(parseVoiceRecordKey('ctrl+delete').named).toBe('delete')
expect(parseVoiceRecordKey('ctrl+del').named).toBe('delete') // ``del`` alias
})
it('falls back to Ctrl+B for unrecognised multi-character tokens', async () => {
const { DEFAULT_VOICE_RECORD_KEY, parseVoiceRecordKey } = await importPlatform('linux')
// Typos / unsupported names (``ctrl+spcae``, ``ctrl+f5``, …) fall back
// to the documented Ctrl+B default rather than silently disabling the
// binding.
expect(parseVoiceRecordKey('ctrl+spcae')).toEqual(DEFAULT_VOICE_RECORD_KEY)
expect(parseVoiceRecordKey('ctrl+f5')).toEqual(DEFAULT_VOICE_RECORD_KEY)
})
})
describe('formatVoiceRecordKey (#18994)', () => {
@@ -127,6 +155,14 @@ describe('formatVoiceRecordKey (#18994)', () => {
expect(formatVoiceRecordKey(parseVoiceRecordKey('alt+r'))).toBe('Alt+R')
expect(formatVoiceRecordKey(parseVoiceRecordKey('cmd+b'))).toBe('Cmd+B')
})
it('renders named keys in title case (Ctrl+Space, Ctrl+Enter)', async () => {
const { formatVoiceRecordKey, parseVoiceRecordKey } = await importPlatform('linux')
expect(formatVoiceRecordKey(parseVoiceRecordKey('ctrl+space'))).toBe('Ctrl+Space')
expect(formatVoiceRecordKey(parseVoiceRecordKey('alt+enter'))).toBe('Alt+Enter')
expect(formatVoiceRecordKey(parseVoiceRecordKey('ctrl+esc'))).toBe('Ctrl+Escape')
})
})
describe('isVoiceToggleKey honours configured record key (#18994)', () => {
@@ -148,6 +184,35 @@ describe('isVoiceToggleKey honours configured record key (#18994)', () => {
expect(isVoiceToggleKey({ ctrl: false, meta: false, super: false }, 'r', altR)).toBe(false)
})
it('binds named keys via ink event flags (space → ch === " ", enter → key.return, …)', async () => {
const { isVoiceToggleKey, parseVoiceRecordKey } = await importPlatform('linux')
const ctrlSpace = parseVoiceRecordKey('ctrl+space')
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, ' ', ctrlSpace)).toBe(true)
// Single-char ``b`` must NOT match a ``space``-configured binding.
expect(isVoiceToggleKey({ ctrl: true, meta: false, super: false }, 'b', ctrlSpace)).toBe(false)
// Space without the configured modifier must not fire either.
expect(isVoiceToggleKey({ ctrl: false, meta: false, super: false }, ' ', ctrlSpace)).toBe(false)
const ctrlEnter = parseVoiceRecordKey('ctrl+enter')
expect(isVoiceToggleKey({ ctrl: true, meta: false, return: true, super: false }, '', ctrlEnter)).toBe(true)
expect(isVoiceToggleKey({ ctrl: true, meta: false, return: false, super: false }, '', ctrlEnter)).toBe(false)
const altTab = parseVoiceRecordKey('alt+tab')
expect(isVoiceToggleKey({ alt: true, ctrl: false, meta: false, super: false, tab: true }, '', altTab)).toBe(true)
expect(isVoiceToggleKey({ alt: false, ctrl: false, meta: false, super: false, tab: true }, '', altTab)).toBe(false)
const ctrlEscape = parseVoiceRecordKey('ctrl+escape')
expect(isVoiceToggleKey({ ctrl: true, escape: true, meta: false, super: false }, '', ctrlEscape)).toBe(true)
expect(isVoiceToggleKey({ ctrl: true, escape: false, meta: false, super: false }, '', ctrlEscape)).toBe(false)
const ctrlBackspace = parseVoiceRecordKey('ctrl+backspace')
expect(isVoiceToggleKey({ backspace: true, ctrl: true, meta: false, super: false }, '', ctrlBackspace)).toBe(true)
const ctrlDelete = parseVoiceRecordKey('ctrl+delete')
expect(isVoiceToggleKey({ ctrl: true, delete: true, meta: false, super: false }, '', ctrlDelete)).toBe(true)
})
it('omitted configured key falls back to ctrl+b (back-compat)', async () => {
const { isVoiceToggleKey } = await importPlatform('linux')