Map mobile special keys to terminal PTY bytes (#6515)

Expand live input mapping to handle escape, tab, delete, insert,
arrows, home, end, page navigation, and F1–F12 keys.

- Safe Map lookup avoids collisions with object prototype methods.
- Enter is excluded to prevent double-sent carriage returns.
- Standardizes byte translation via buildTerminalShortcutKey.
This commit is contained in:
Jinjing 2026-06-27 14:06:25 -07:00 committed by GitHub
parent c0f4bdd020
commit ef292cb069
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 102 additions and 5 deletions

View File

@ -19,12 +19,48 @@ describe('terminal live input', () => {
vi.useRealTimers()
})
it('maps phone keyboard special keys to PTY bytes', () => {
expect(getTerminalLiveSpecialKeyBytes('Backspace')).toBe('\x7f')
it.each([
['Escape', '\x1b'],
['Esc', '\x1b'],
['Tab', '\t'],
['Backspace', '\x7f'],
['Delete', '\x1b[3~'],
['Insert', '\x1b[2~'],
['ArrowUp', '\x1b[A'],
['ArrowDown', '\x1b[B'],
['ArrowLeft', '\x1b[D'],
['ArrowRight', '\x1b[C'],
['Home', '\x1b[H'],
['End', '\x1b[F'],
['PageUp', '\x1b[5~'],
['PageDown', '\x1b[6~'],
['F1', '\x1bOP'],
['F2', '\x1bOQ'],
['F3', '\x1bOR'],
['F4', '\x1bOS'],
['F5', '\x1b[15~'],
['F6', '\x1b[17~'],
['F7', '\x1b[18~'],
['F8', '\x1b[19~'],
['F9', '\x1b[20~'],
['F10', '\x1b[21~'],
['F11', '\x1b[23~'],
['F12', '\x1b[24~']
])('maps %s to terminal PTY bytes', (key, bytes) => {
expect(getTerminalLiveSpecialKeyBytes(key)).toBe(bytes)
})
it('leaves submitted or printable keys on their existing input paths', () => {
expect(getTerminalLiveSpecialKeyBytes('Enter')).toBeNull()
expect(getTerminalLiveSpecialKeyBytes('a')).toBeNull()
})
it('ignores object prototype names from native key events', () => {
expect(getTerminalLiveSpecialKeyBytes('constructor')).toBeNull()
expect(getTerminalLiveSpecialKeyBytes('toString')).toBeNull()
expect(getTerminalLiveSpecialKeyBytes('hasOwnProperty')).toBeNull()
})
it('enforces the paste-sized byte budget', () => {
expect(isTerminalLiveInputWithinByteLimit('hello')).toBe(true)
expect(isTerminalLiveInputWithinByteLimit('x'.repeat(TERMINAL_LIVE_INPUT_MAX_BYTES))).toBe(true)

View File

@ -1,7 +1,67 @@
import { buildTerminalShortcutKey } from './terminal-accessory-keys'
const TERMINAL_LIVE_INPUT_MAX_BYTES = 256 * 1024
const encoder = new TextEncoder()
type TerminalLiveSpecialKeyId =
| 'arrowDown'
| 'arrowLeft'
| 'arrowRight'
| 'arrowUp'
| 'backspace'
| 'delete'
| 'end'
| 'escape'
| 'f1'
| 'f2'
| 'f3'
| 'f4'
| 'f5'
| 'f6'
| 'f7'
| 'f8'
| 'f9'
| 'f10'
| 'f11'
| 'f12'
| 'home'
| 'insert'
| 'pageDown'
| 'pageUp'
| 'tab'
// Why: Enter stays on onSubmitEditing; mapping it here can double-send carriage
// returns when native TextInput emits both submit and key events.
const TERMINAL_LIVE_SPECIAL_KEY_IDS = new Map<string, TerminalLiveSpecialKeyId>([
['Escape', 'escape'],
['Esc', 'escape'],
['Tab', 'tab'],
['Backspace', 'backspace'],
['Delete', 'delete'],
['Insert', 'insert'],
['ArrowUp', 'arrowUp'],
['ArrowDown', 'arrowDown'],
['ArrowLeft', 'arrowLeft'],
['ArrowRight', 'arrowRight'],
['Home', 'home'],
['End', 'end'],
['PageUp', 'pageUp'],
['PageDown', 'pageDown'],
['F1', 'f1'],
['F2', 'f2'],
['F3', 'f3'],
['F4', 'f4'],
['F5', 'f5'],
['F6', 'f6'],
['F7', 'f7'],
['F8', 'f8'],
['F9', 'f9'],
['F10', 'f10'],
['F11', 'f11'],
['F12', 'f12']
])
export type TerminalLiveInputFocusTimerRef = {
current: ReturnType<typeof setTimeout> | null
}
@ -15,10 +75,11 @@ export type TerminalLiveInputDefaultResult = {
export type TerminalLiveInputPruneResult = TerminalLiveInputDefaultResult
export function getTerminalLiveSpecialKeyBytes(key: string): string | null {
if (key === 'Backspace') {
return '\x7f'
const shortcutKey = TERMINAL_LIVE_SPECIAL_KEY_IDS.get(key)
if (!shortcutKey) {
return null
}
return null
return buildTerminalShortcutKey({ key: shortcutKey, modifiers: [] })?.bytes ?? null
}
export function isTerminalLiveInputWithinByteLimit(