| 25 | } |
| 26 | |
| 27 | function parseKey(keypress: ParsedKey): [Key, string] { |
| 28 | const key: Key = { |
| 29 | upArrow: keypress.name === 'up', |
| 30 | downArrow: keypress.name === 'down', |
| 31 | leftArrow: keypress.name === 'left', |
| 32 | rightArrow: keypress.name === 'right', |
| 33 | pageDown: keypress.name === 'pagedown', |
| 34 | pageUp: keypress.name === 'pageup', |
| 35 | wheelUp: keypress.name === 'wheelup', |
| 36 | wheelDown: keypress.name === 'wheeldown', |
| 37 | home: keypress.name === 'home', |
| 38 | end: keypress.name === 'end', |
| 39 | return: keypress.name === 'return', |
| 40 | escape: keypress.name === 'escape', |
| 41 | fn: keypress.fn, |
| 42 | ctrl: keypress.ctrl, |
| 43 | shift: keypress.shift, |
| 44 | tab: keypress.name === 'tab', |
| 45 | backspace: keypress.name === 'backspace', |
| 46 | delete: keypress.name === 'delete', |
| 47 | // `parseKeypress` parses \u001B\u001B[A (meta + up arrow) as meta = false |
| 48 | // but with option = true, so we need to take this into account here |
| 49 | // to avoid breaking changes in Ink. |
| 50 | // TODO(vadimdemedes): consider removing this in the next major version. |
| 51 | meta: keypress.meta || keypress.name === 'escape' || keypress.option, |
| 52 | // Super (Cmd on macOS / Win key) — only arrives via kitty keyboard |
| 53 | // protocol CSI u sequences. Distinct from meta (Alt/Option) so |
| 54 | // bindings like cmd+c can be expressed separately from opt+c. |
| 55 | super: keypress.super, |
| 56 | } |
| 57 | |
| 58 | let input = keypress.ctrl ? keypress.name : keypress.sequence |
| 59 | |
| 60 | // Handle undefined input case |
| 61 | if (input === undefined) { |
| 62 | input = '' |
| 63 | } |
| 64 | |
| 65 | // When ctrl is set, keypress.name for space is the literal word "space". |
| 66 | // Convert to actual space character for consistency with the CSI u branch |
| 67 | // (which maps 'space' → ' '). Without this, ctrl+space leaks the literal |
| 68 | // word "space" into text input. |
| 69 | if (keypress.ctrl && input === 'space') { |
| 70 | input = ' ' |
| 71 | } |
| 72 | |
| 73 | // Suppress unrecognized escape sequences that were parsed as function keys |
| 74 | // (matched by FN_KEY_RE) but have no name in the keyName map. |
| 75 | // Examples: ESC[25~ (F13/Right Alt on Windows), ESC[26~ (F14), etc. |
| 76 | // Without this, the ESC prefix is stripped below and the remainder (e.g., |
| 77 | // "[25~") leaks into the input as literal text. |
| 78 | if (keypress.code && !keypress.name) { |
| 79 | input = '' |
| 80 | } |
| 81 | |
| 82 | // Suppress ESC-less SGR mouse fragments. When a heavy React commit blocks |
| 83 | // the event loop past App's 50ms NORMAL_TIMEOUT flush, a CSI split across |
| 84 | // stdin chunks gets its buffered ESC flushed as a lone Escape key, and the |