UNPKG

emit-keypress

Version:

Drop-dead simple keypress event emitter for Node.js. Create powerful CLI applications and experiences with ease.

1 lines 80 kB
{"version":3,"sources":["../index.ts","../src/emit-keypress.ts","../src/keypress.ts","../src/mousepress.ts","../src/keycodes.ts","../src/utils.ts"],"sourcesContent":["/* eslint-disable no-control-regex */\nimport type readline from 'node:readline';\nimport { stdin, stdout } from 'node:process';\nimport { emitKeypressEvents } from '~/emit-keypress';\nimport { mousepress } from '~/mousepress';\nimport { keycodes } from '~/keycodes';\nimport {\n createShortcut,\n isMousepress,\n isPrintableCharacter,\n parsePosition,\n prioritizeKeymap,\n sortShortcutModifier\n} from '~/utils';\n\nexport * from '~/utils';\n\nconst ESC = '\\x1b';\nconst isWindows = globalThis.process.platform === 'win32';\n\nconst MAX_PASTE_BUFFER = 1024 * 1024; // 1MB limit for paste buffer\nconst ENABLE_PASTE_BRACKET_MODE = `${ESC}[?2004h`;\nconst DISABLE_PASTE_BRACKET_MODE = `${ESC}[?2004l`;\n\nexport const enablePaste = (stdout: NodeJS.WriteStream) => {\n stdout.write(ENABLE_PASTE_BRACKET_MODE);\n};\n\nexport const disablePaste = (stdout: NodeJS.WriteStream) => {\n stdout.write(DISABLE_PASTE_BRACKET_MODE);\n};\n\nexport const enableMouse = (stdout: NodeJS.WriteStream) => {\n stdout.write(`${ESC}[?1003h`);\n};\n\nexport const disableMouse = (stdout: NodeJS.WriteStream) => {\n stdout.write(`${ESC}[?1003l`);\n};\n\nexport const cursor = {\n hide: (stdout: NodeJS.WriteStream) => {\n stdout.write(`${ESC}[?25l`);\n },\n show: (stdout: NodeJS.WriteStream) => {\n stdout.write(`${ESC}[?25h`);\n },\n position: (stdout: NodeJS.WriteStream) => {\n stdout.write(`${ESC}[6n`);\n }\n};\n\nconst hasMatchingModifiers = (a: readline.Key, b) => {\n return a.ctrl === b.ctrl && a.shift === b.shift && a.meta === b.meta && a.fn === b.fn;\n};\n\nconst hasModifier = (key: readline.Key) => {\n return key.ctrl || key.shift || key.meta || key.fn;\n};\n\nconst onExitHandlers = globalThis.onExitHandlers ||= [];\nconst onExitHandler = () => {\n for (const fn of onExitHandlers) {\n if (!fn.called) {\n fn.called = true;\n fn();\n }\n }\n};\n\nif (!globalThis.exitHandlers) {\n globalThis.exitHandlers = onExitHandlers;\n process.once('uncaughtException', onExitHandler);\n process.once('SIGINT', onExitHandler);\n process.once('exit', onExitHandler);\n}\n\nexport const emitKeypress = ({\n input = stdin,\n output = stdout,\n keymap = [],\n onKeypress,\n onMousepress,\n onExit,\n escapeCodeTimeout = 500,\n handleClose = true,\n hideCursor = false,\n initialPosition = false,\n enablePasteMode = false,\n pasteModeTimeout = 100\n}: {\n // eslint-disable-next-line no-undef\n input?: NodeJS.ReadStream;\n keymap?: Array<{ sequence: string; shortcut: string }>;\n // eslint-disable-next-line no-unused-vars\n onKeypress: (input: string, key: readline.Key, close: () => void) => void;\n // eslint-disable-next-line no-unused-vars\n onMousepress?: (input: string, key: any, close: () => void) => void;\n onExit?: () => void;\n}) => {\n if (!input || (input !== process.stdin && !input.isTTY)) {\n throw new Error('Invalid stream passed');\n }\n\n const isRaw = input.isRaw;\n const sortedShorcuts = new Set();\n let closed = false;\n let pasting = false;\n let initial = true;\n let sorted = false;\n let buffer = '';\n let pasteTimeout: NodeJS.Timeout | null = null;\n\n const clearPasteState = () => {\n pasting = false;\n buffer = '';\n if (pasteTimeout) {\n clearTimeout(pasteTimeout);\n pasteTimeout = null;\n }\n };\n\n // eslint-disable-next-line complexity\n async function handleKeypress(input: string, key: readline.Key) {\n if (initialPosition && initial) {\n const parsed = parsePosition(key.sequence);\n if (parsed) {\n initial = false;\n onKeypress('', parsed, close);\n return;\n }\n }\n\n if (key.name === 'paste-start' || /\\x1B\\[200~/.test(key.sequence)) {\n clearPasteState();\n pasting = true;\n pasteTimeout = setTimeout(clearPasteState, pasteModeTimeout);\n return;\n }\n\n if (key.name === 'paste-end' || /\\x1B\\[201~/.test(key.sequence)) {\n clearTimeout(pasteTimeout!);\n pasteTimeout = null;\n\n if (pasting) {\n key.name = 'paste';\n key.sequence = buffer.replace(/\\x1B\\[201~/g, '');\n key.ctrl = false;\n key.shift = false;\n key.meta = false;\n key.fn = false;\n key.printable = true;\n onKeypress(buffer, key, close);\n clearPasteState();\n }\n return;\n }\n\n if (pasting) {\n if (buffer.length < MAX_PASTE_BUFFER) {\n buffer += key.sequence?.replace(/\\r/g, '\\n') || '';\n } else {\n clearPasteState();\n }\n return;\n }\n\n if (!buffer && isMousepress(key.sequence, key)) {\n const k = mousepress(key.sequence, Buffer.from(key.sequence));\n k.mouse = true;\n onMousepress?.(k, close);\n } else {\n let addShortcut = false;\n\n if (typeof keymap === 'function') {\n keymap = keymap();\n }\n\n if (!sorted) {\n keymap = prioritizeKeymap(keymap);\n sorted = true;\n }\n\n const shortcut = key.shortcut ? sortShortcutModifier(key.shortcut) : createShortcut(key);\n\n if (!key.shortcut && hasModifier(key)) {\n key.shortcut = shortcut;\n }\n\n for (const mapping of keymap) {\n if (mapping.sequence) {\n if (key.sequence === mapping.sequence && hasMatchingModifiers(key, mapping)) {\n key = { ...key, ...mapping };\n addShortcut = false;\n break;\n }\n\n continue;\n }\n\n if (mapping.shortcut && !sortedShorcuts.has(mapping.shortcut)) {\n sortedShorcuts.add(mapping.shortcut);\n mapping.shortcut = sortShortcutModifier(mapping.shortcut);\n }\n\n // Only continue comparison if the custom key mapping does not have a sequence\n if ((shortcut === mapping.shortcut) || (key.name && key.name === mapping.shortcut && hasMatchingModifiers(key, mapping))) {\n key = { ...key, ...mapping };\n addShortcut = false;\n break;\n }\n }\n\n if (/^f[0-9]+$/.test(key.name)) {\n addShortcut = true;\n }\n\n if (addShortcut) {\n key.shortcut ||= shortcut;\n }\n\n key.printable ||= isPrintableCharacter(key.sequence);\n onKeypress(key.sequence, key, close);\n }\n }\n\n function close() {\n if (closed) return;\n if (!isWindows && input.isTTY) input.setRawMode(isRaw);\n if (hideCursor) cursor.show(output);\n if (onMousepress) disableMouse(output);\n if (enablePasteMode) disablePaste(output);\n if (onKeypress) input.off('keypress', handleKeypress);\n if (pasteTimeout) clearTimeout(pasteTimeout);\n closed = true;\n input.pause();\n onExit?.();\n }\n\n emitKeypressEvents(input, { escapeCodeTimeout });\n\n if (onMousepress) {\n enableMouse(output);\n }\n\n if (enablePasteMode === true) {\n enablePaste(output);\n }\n\n // Disable automatic character echoing\n if (!isWindows && input.isTTY) input.setRawMode(true);\n if (hideCursor) cursor.hide(output);\n if (onKeypress) input.on('keypress', handleKeypress);\n\n input.setEncoding('utf8');\n input.once('pause', close);\n input.resume();\n\n if (initialPosition) {\n cursor.position(output);\n }\n\n if (handleClose !== false) {\n onExitHandlers.push(close);\n }\n\n return close;\n};\n\nexport {\n createShortcut,\n emitKeypressEvents,\n isMousepress,\n isPrintableCharacter,\n keycodes,\n mousepress\n};\n\nexport default emitKeypress;\n","// This file is a modified version of the original file from the readline module of Node.js\n// Copyright Joyent, Inc. and other Node contributors.\n// SPDX-License-Identifier: MIT\nimport { StringDecoder } from 'node:string_decoder';\nimport { clearTimeout, setTimeout } from 'node:timers';\nimport { charLengthAt, CSI, emitKeys } from '~/keypress';\n\nconst { kEscape } = CSI;\nconst KEYPRESS_DECODER = Symbol('keypress-decoder');\nconst ESCAPE_DECODER = Symbol('escape-decoder');\nconst kSawKeyPress = Symbol('saw-key-press');\n\n// GNU readline library - keyseq-timeout is 500ms (default)\nconst ESCAPE_CODE_TIMEOUT = 500;\n\n/**\n * accepts a readable Stream instance and makes it emit \"keypress\" events\n */\n\nexport function emitKeypressEvents(stream, iface = {}) {\n if (stream[KEYPRESS_DECODER]) return;\n\n stream[KEYPRESS_DECODER] = new StringDecoder('utf8');\n\n stream[ESCAPE_DECODER] = emitKeys(stream);\n stream[ESCAPE_DECODER].next();\n\n const triggerEscape = () => stream[ESCAPE_DECODER].next('');\n const { escapeCodeTimeout = ESCAPE_CODE_TIMEOUT } = iface;\n let timeoutId;\n\n function onData(input) {\n if (stream.listenerCount('keypress') > 0) {\n const string = stream[KEYPRESS_DECODER].write(input);\n if (string) {\n clearTimeout(timeoutId);\n\n // This supports characters of length 2.\n iface[kSawKeyPress] = charLengthAt(string, 0) === string.length;\n iface.isCompletionEnabled = false;\n\n let length = 0;\n for (const character of string) {\n length += character.length;\n\n if (length === string.length) {\n iface.isCompletionEnabled = true;\n }\n\n try {\n stream[ESCAPE_DECODER].next(character);\n // Escape letter at the tail position\n if (length === string.length && character === kEscape) {\n timeoutId = setTimeout(triggerEscape, escapeCodeTimeout);\n }\n } catch (err) {\n // If the generator throws (it could happen in the `keypress`\n // event), we need to restart it.\n stream[ESCAPE_DECODER] = emitKeys(stream);\n stream[ESCAPE_DECODER].next();\n throw err;\n }\n }\n }\n } else {\n // Nobody's watching anyway\n stream.removeListener('data', onData);\n stream.on('newListener', onNewListener);\n }\n }\n\n function onNewListener(event) {\n if (event === 'keypress') {\n stream.on('data', onData);\n stream.removeListener('newListener', onNewListener);\n }\n }\n\n if (stream.listenerCount('keypress') > 0) {\n stream.on('data', onData);\n } else {\n stream.on('newListener', onNewListener);\n }\n}\n\n","/* eslint-disable no-extra-parens, no-control-regex */\n// This file is a modified version of the original file from the readline module of Node.js\n// Copyright Joyent, Inc. and other Node contributors.\n// SPDX-License-Identifier: MIT\nexport const kEscape = '\\x1b';\nexport const kSubstringSearch = Symbol('kSubstringSearch');\nexport const kUTF16SurrogateThreshold = 0x10000; // 2 ** 16\n\nexport function CSI(strings, ...args) {\n let ret = `${kEscape}[`;\n for (let n = 0; n < strings.length; n++) {\n ret += strings[n];\n if (n < args.length) {\n ret += args[n];\n }\n }\n return ret;\n}\n\nCSI.kEscape = kEscape;\nCSI.kClearToLineBeginning = CSI`1K`;\nCSI.kClearToLineEnd = CSI`0K`;\nCSI.kClearLine = CSI`2K`;\nCSI.kClearScreenDown = CSI`0J`;\n\n// TODO(BridgeAR): Treat combined characters as single character, i.e,\n// 'a\\u0301' and '\\u0301a' (both have the same visual output).\n// Check Canonical_Combining_Class in\n// http://userguide.icu-project.org/strings/properties\nexport function charLengthLeft(str, i) {\n if (i <= 0) { return 0; }\n if ((i > 1 &&\n str.codePointAt(i - 2) >= kUTF16SurrogateThreshold) ||\n str.codePointAt(i - 1) >= kUTF16SurrogateThreshold) {\n return 2;\n }\n return 1;\n}\n\nexport function charLengthAt(str, i) {\n if (str.length <= i) {\n // Pretend to move to the right. This is necessary to autocomplete while\n // moving to the right.\n return 1;\n }\n return str.codePointAt(i) >= kUTF16SurrogateThreshold ? 2 : 1;\n}\n\n/*\n Some patterns seen in terminal key escape codes, derived from combos seen\n at http://www.midnight-commander.org/browser/lib/tty/key.c\n\n ESC letter\n ESC [ letter\n ESC [ modifier letter\n ESC [ 1 ; modifier letter\n ESC [ num char\n ESC [ num ; modifier char\n ESC O letter\n ESC O modifier letter\n ESC O 1 ; modifier letter\n ESC N letter\n ESC [ [ num ; modifier char\n ESC [ [ 1 ; modifier letter\n ESC ESC [ num char\n ESC ESC O letter\n\n - char is usually ~ but $ and ^ also happen with rxvt\n - modifier is 1 +\n (shift * 1) +\n (left_alt * 2) +\n (ctrl * 4) +\n (right_alt * 8)\n - two leading ESCs apparently mean the same as one leading ESC\n*/\n\n// eslint-disable-next-line complexity\nexport function * emitKeys(stream) {\n while (true) {\n let ch = yield;\n let s = ch;\n let escaped = false;\n\n const key = {\n sequence: null,\n name: undefined,\n ctrl: false,\n meta: false,\n shift: false,\n fn: false\n };\n\n if (ch === kEscape) {\n escaped = true;\n s += (ch = yield);\n\n if (ch === kEscape) {\n s += (ch = yield);\n }\n }\n\n if (escaped && (ch === 'O' || ch === '[')) {\n // ANSI escape sequence\n let code = ch;\n let modifier = 0;\n\n if (ch === 'O') {\n // ESC O letter\n // ESC O modifier letter\n s += (ch = yield);\n\n if (ch >= '0' && ch <= '9') {\n modifier = (ch >> 0) - 1;\n s += (ch = yield);\n }\n\n code += ch;\n } else if (ch === '[') {\n // ESC [ letter\n // ESC [ modifier letter\n // ESC [ [ modifier letter\n // ESC [ [ num char\n s += (ch = yield);\n\n if (ch === '[') {\n // \\x1b[[A\n // ^--- escape codes might have a second bracket\n code += ch;\n s += (ch = yield);\n }\n\n /*\n * Here and later we try to buffer just enough data to get\n * a complete ascii sequence.\n *\n * We have basically two classes of ascii characters to process:\n *\n *\n * 1. `\\x1b[24;5~` should be parsed as { code: '[24~', modifier: 5 }\n *\n * This particular example is featuring Ctrl+F12 in xterm.\n *\n * - `;5` part is optional, e.g. it could be `\\x1b[24~`\n * - first part can contain one or two digits\n * - there is also special case when there can be 3 digits\n * but without modifier. They are the case of paste bracket mode\n *\n * So the generic regexp is like /^(?:\\d\\d?(;\\d)?[~^$]|\\d{3}~)$/\n *\n *\n * 2. `\\x1b[1;5H` should be parsed as { code: '[H', modifier: 5 }\n *\n * This particular example is featuring Ctrl+Home in xterm.\n *\n * - `1;5` part is optional, e.g. it could be `\\x1b[H`\n * - `1;` part is optional, e.g. it could be `\\x1b[5H`\n *\n * So the generic regexp is like /^((\\d;)?\\d)?[A-Za-z]$/\n *\n */\n const cmdStart = s.length - 1;\n\n // Skip one or two leading digits\n if (ch >= '0' && ch <= '9') {\n s += (ch = yield);\n\n if (ch >= '0' && ch <= '9') {\n s += (ch = yield);\n\n if (ch >= '0' && ch <= '9') {\n s += (ch = yield);\n }\n }\n }\n\n // skip modifier\n if (ch === ';') {\n s += (ch = yield);\n\n if (ch >= '0' && ch <= '9') {\n s += yield;\n }\n }\n\n /*\n * We buffered enough data, now trying to extract code\n * and modifier from it\n */\n const cmd = s.slice(cmdStart);\n let match;\n\n if ((match = /^(?:(\\d\\d?)(?:;(\\d))?([~^$])|(\\d{3}~))$/.exec(cmd))) {\n if (match[4]) {\n code += match[4];\n } else {\n code += match[1] + match[3];\n modifier = (match[2] || 1) - 1;\n }\n } else if ((match = /^((\\d;)?(\\d))?([A-Za-z])$/.exec(cmd))) {\n code += match[4];\n modifier = (match[3] || 1) - 1;\n } else {\n code += cmd;\n }\n }\n\n if (/\\[(?:1[5-9]|2[0-2]);10/.test(code)) {\n s += ch = yield;\n }\n\n // Parse the key modifier\n key.ctrl = Boolean(modifier & 4);\n key.meta = Boolean(modifier & 10);\n key.shift = Boolean(modifier & 1);\n key.code = code;\n\n if (!key.meta) {\n const parts = [...s];\n\n if (parts[0] === '\\u001b' && parts[1] === '\\u001b') {\n key.meta = true;\n }\n }\n\n if (/\\x1B\\[1;[59][FH]/.test(s)) {\n key.fn = true;\n }\n\n // Parse the key itself\n switch (code) {\n case '[M': {\n key.name = 'mouse';\n s += (ch = yield); // button-byte\n s += (ch = yield); // x-coordinate\n s += (ch = yield); // y-coordinate\n break;\n }\n\n /* xterm/gnome ESC [ letter (with modifier) */\n case '[P': key.name = 'f1'; key.fn = true; break;\n case '[Q': key.name = 'f2'; key.fn = true; break;\n case '[R': key.name = 'f3'; key.fn = true; break;\n case '[S': key.name = 'f4'; key.fn = true; break;\n\n /* xterm/gnome ESC O letter (without modifier) */\n case 'OP': key.name = 'f1'; key.fn = true; break;\n case 'OQ': key.name = 'f2'; key.fn = true; break;\n case 'OR': key.name = 'f3'; key.fn = true; break;\n case 'OS': key.name = 'f4'; key.fn = true; break;\n\n /* xterm/rxvt ESC [ number ~ */\n case '[11~': key.name = 'f1'; key.fn = true; break;\n case '[12~': key.name = 'f2'; key.fn = true; break;\n case '[13~': key.name = 'f3'; key.fn = true; break;\n case '[14~': key.name = 'f4'; key.fn = true; break;\n\n /* paste bracket mode */\n case '[200~': key.name = 'paste-start'; break;\n case '[201~': key.name = 'paste-end'; break;\n\n /* from Cygwin and used in libuv */\n case '[[A': key.name = 'f1'; key.fn = true; break;\n case '[[B': key.name = 'f2'; key.fn = true; break;\n case '[[C': key.name = 'f3'; key.fn = true; break;\n case '[[D': key.name = 'f4'; key.fn = true; break;\n case '[[E': key.name = 'f5'; key.fn = true; break;\n\n /* common */\n case '[15~': key.name = 'f5'; key.fn = true; break;\n case '[17~': key.name = 'f6'; key.fn = true; break;\n case '[18~': key.name = 'f7'; key.fn = true; break;\n case '[19~': key.name = 'f8'; key.fn = true; break;\n case '[20~': key.name = 'f9'; key.fn = true; break;\n case '[21~': key.name = 'f10'; key.fn = true; break;\n case '[23~': key.name = 'f11'; key.fn = true; break;\n case '[24~': key.name = 'f12'; key.fn = true; break;\n\n /* common */\n case '[15;10': key.name = 'f5'; key.fn = true; key.shift = true; key.meta = true; break;\n case '[17;10': key.name = 'f7'; key.fn = true; key.shift = true; key.meta = true; break;\n case '[18;10': key.name = 'f8'; key.fn = true; key.shift = true; key.meta = true; break;\n case '[19;10': key.name = 'f9'; key.fn = true; key.shift = true; key.meta = true; break;\n case '[20;10': key.name = 'f10'; key.fn = true; key.shift = true; key.meta = true; break;\n case '[21;10': key.name = 'f11'; key.fn = true; key.shift = true; key.meta = true; break;\n case '[22;10': key.name = 'f12'; key.fn = true; key.shift = true; key.meta = true; break;\n\n /* xterm ESC [ letter */\n case '[A': key.name = 'up'; break;\n case '[B': key.name = 'down'; break;\n case '[C': key.name = 'right'; break;\n case '[D': key.name = 'left'; break;\n case '[E': key.name = 'clear'; break;\n case '[F': key.name = 'end'; break;\n case '[H': key.name = 'home'; break;\n\n /* xterm/gnome ESC O letter */\n case 'OA': key.name = 'up'; break;\n case 'OB': key.name = 'down'; break;\n case 'OC': key.name = 'right'; break;\n case 'OD': key.name = 'left'; break;\n case 'OE': key.name = 'clear'; break;\n case 'OF': key.name = 'end'; break;\n case 'OH': key.name = 'home'; break;\n\n /* xterm/rxvt ESC [ number ~ */\n case '[1~': key.name = 'home'; break;\n case '[2~': key.name = 'insert'; break;\n case '[3~':\n key.name = 'delete';\n key.shift = !s.includes('3;5~');\n key.fn = /^\\[3;[256]~$/.test(s.slice(1));\n break;\n\n case '[4~': key.name = 'end'; break;\n case '[5~': key.name = 'pageup'; break;\n case '[6~': key.name = 'pagedown'; break;\n\n /* putty */\n case '[[5~': key.name = 'pageup'; break;\n case '[[6~': key.name = 'pagedown'; break;\n\n /* rxvt */\n case '[7~': key.name = 'home'; break;\n case '[8~': key.name = 'end'; break;\n\n /* rxvt keys with modifiers */\n case '[a': key.name = 'up'; key.shift = true; break;\n case '[b': key.name = 'down'; key.shift = true; break;\n case '[c': key.name = 'right'; key.shift = true; break;\n case '[d': key.name = 'left'; key.shift = true; break;\n case '[e': key.name = 'clear'; key.shift = true; break;\n\n case '[2$': key.name = 'insert'; key.shift = true; break;\n case '[3$': key.name = 'delete'; key.shift = true; break;\n case '[5$': key.name = 'pageup'; key.shift = true; break;\n case '[6$': key.name = 'pagedown'; key.shift = true; break;\n case '[7$': key.name = 'home'; key.shift = true; break;\n case '[8$': key.name = 'end'; key.shift = true; break;\n\n case 'Oa': key.name = 'up'; key.ctrl = true; break;\n case 'Ob': key.name = 'down'; key.ctrl = true; break;\n case 'Oc': key.name = 'right'; key.ctrl = true; break;\n case 'Od': key.name = 'left'; key.ctrl = true; break;\n case 'Oe': key.name = 'clear'; key.ctrl = true; break;\n\n case '[2^': key.name = 'insert'; key.ctrl = true; break;\n case '[3^': key.name = 'delete'; key.ctrl = true; break;\n case '[5^': key.name = 'pageup'; key.ctrl = true; break;\n case '[6^': key.name = 'pagedown'; key.ctrl = true; break;\n case '[7^': key.name = 'home'; key.ctrl = true; break;\n case '[8^': key.name = 'end'; key.ctrl = true; break;\n\n case '[3;5~':\n key.name = 'delete';\n key.ctrl = true;\n key.fn = true;\n break;\n\n case '[1;13':\n case '[1;14':\n key.shift = code === '[1;14';\n key.ctrl = true;\n key.meta = true;\n\n if ((ch = yield)) {\n s += ch;\n\n switch (ch) {\n case 'A': key.name = 'up'; break;\n case 'B': key.name = 'down'; break;\n case 'C': key.name = 'right'; break;\n case 'D': key.name = 'left'; break;\n case 'F': key.name = 'right'; key.fn = true; break;\n case 'H': key.name = 'left'; key.fn = true; break;\n default: break;\n }\n }\n break;\n\n case '[3;10':\n if ((ch = yield)) {\n s += ch;\n key.name = 'delete';\n key.shift = true;\n key.meta = true;\n key.fn = true;\n }\n break;\n\n case '[3;13':\n case '[3;14':\n if ((ch = yield)) {\n s += ch;\n key.name = 'delete';\n key.ctrl = true;\n key.shift = code === '[3;14';\n key.meta = true;\n key.fn = true;\n }\n break;\n\n case '[5;14':\n case '[6;14':\n if ((ch = yield)) {\n s += ch;\n key.name = code === '[5;14' ? 'up' : 'down';\n key.ctrl = true;\n key.shift = true;\n key.meta = true;\n key.fn = true;\n }\n break;\n\n case '[1;10':\n if ((ch = yield)) {\n s += ch;\n }\n\n switch (ch) {\n case 'A': key.name = 'up'; break;\n case 'B': key.name = 'down'; break;\n case 'C': key.name = 'right'; break;\n case 'D': key.name = 'left'; break;\n case 'F': key.name = 'right'; key.fn = true; break;\n case 'H': key.name = 'left'; key.fn = true; break;\n case 'P': key.name = 'f1'; key.fn = true; break;\n case 'Q': key.name = 'f2'; key.fn = true; break;\n case 'R': key.name = 'f3'; key.fn = true; break;\n case 'S': key.name = 'f4'; key.fn = true; break;\n default: break;\n }\n\n key.shift = true;\n key.meta = true;\n break;\n\n case '[5;10':\n case '[6;10':\n if ((ch = yield)) {\n s += ch;\n key.name = code === '[5;10' ? 'up' : 'down';\n key.shift = true;\n key.fn = true;\n }\n\n key.meta = true;\n break;\n\n /* misc. */\n case '[Z': key.name = 'tab'; key.shift = true; break;\n default: key.name = undefined; break;\n }\n\n } else if (ch === '\\r') {\n // carriage return\n key.name = 'return';\n key.meta = escaped;\n } else if (ch === '\\n') {\n // Enter, should have been called linefeed\n key.name = 'enter';\n key.meta = escaped;\n } else if (ch === '\\t') {\n // tab\n key.name = 'tab';\n key.meta = escaped;\n } else if (ch === '\\b') {\n // ctrl+backspace or ctrl+h\n key.name = 'backspace';\n key.ctrl = true;\n key.meta = escaped;\n } else if (ch === '\\x7f') {\n // backspace\n key.name = 'backspace';\n key.meta = escaped;\n } else if (ch === kEscape) {\n // escape key\n key.name = 'escape';\n key.meta = escaped;\n } else if (ch === ' ') {\n key.name = 'space';\n key.meta = escaped;\n } else if (!escaped && ch <= '\\x1a') {\n // ctrl+letter\n key.name = String.fromCharCode(ch.charCodeAt(0) + 'a'.charCodeAt(0) - 1);\n key.ctrl = true;\n } else if (/^[0-9A-Za-z]$/.exec(ch) !== null) {\n // Letter, number, shift+letter\n key.name = ch;\n key.shift = /^[A-Z]$/.exec(ch) !== null;\n key.meta = escaped;\n } else if (escaped) {\n // Escape sequence timeout\n key.name = ch.length ? undefined : 'escape';\n key.meta = false;\n }\n\n key.sequence = s;\n\n if (s.length !== 0 && (key.name !== undefined || escaped)) {\n /* Named character or sequence */\n stream.emit('keypress', escaped ? undefined : s, key);\n } else if (charLengthAt(s, 0) === s.length) {\n /* Single unnamed character, e.g. \".\" */\n stream.emit('keypress', s, key);\n }\n\n /* Unrecognized or broken escape sequence, don't emit anything */\n }\n}\n\n// This runs in O(n log n).\nexport function commonPrefix(strings) {\n if (strings.length === 0) {\n return '';\n }\n if (strings.length === 1) {\n return strings[0];\n }\n\n const sorted = strings.slice().sort();\n const min = sorted[0];\n const max = sorted[sorted.length - 1];\n\n for (let i = 0; i < min.length; i++) {\n if (min[i] !== max[i]) {\n return min.slice(0, i);\n }\n }\n return min;\n}\n","/* eslint-disable no-control-regex */\n// Based on code from chjj/blessed\n// Copyright (c) 2013-2015, Christopher Jeffrey and contributors\n// eslint-disable-next-line complexity\nexport const mousepress = (s: string, buf: Buffer, state = {}) => {\n let parts;\n let b;\n let x;\n let y;\n let mod;\n let params;\n let down;\n let page;\n let button;\n\n const key = {\n name: undefined,\n ctrl: false,\n meta: false,\n shift: false\n };\n\n if (Buffer.isBuffer(s)) {\n if (s[0] > 127 && s[1] === undefined) {\n s[0] -= 128;\n s = '\\x1b' + s.toString('utf8');\n } else {\n s = s.toString('utf8');\n }\n }\n\n // XTerm / X10 for buggy VTE\n // VTE can only send unsigned chars and no unicode for coords. This limits\n // them to 0xff. However, normally the x10 protocol does not allow a byte\n // under 0x20, but since VTE can have the bytes overflow, we can consider\n // bytes below 0x20 to be up to 0xff + 0x20. This gives a limit of 287. Since\n // characters ranging from 223 to 248 confuse javascript's utf parser, we\n // need to parse the raw binary. We can detect whether the terminal is using\n // a bugged VTE version by examining the coordinates and seeing whether they\n // are a value they would never otherwise be with a properly implemented x10\n // protocol. This method of detecting VTE is only 99% reliable because we\n // can't check if the coords are 0x00 (255) since that is a valid x10 coord\n // technically.\n const bx = s.charCodeAt(4);\n const by = s.charCodeAt(5);\n\n if (buf[0] === 0x1b && buf[1] === 0x5b && buf[2] === 0x4d && (state.isVTE\n || bx >= 65533 || by >= 65533\n || (bx > 0x00 && bx < 0x20)\n || (by > 0x00 && by < 0x20)\n || (buf[4] > 223 && buf[4] < 248 && buf.length === 6)\n || (buf[5] > 223 && buf[5] < 248 && buf.length === 6))) {\n b = buf[3];\n x = buf[4];\n y = buf[5];\n\n // Handle coordinate, unsigned char overflow\n if (x < 0x20) x += 0xff;\n if (y < 0x20) y += 0xff;\n\n // Handle large terminal windows (coordinates > 255)\n if (x === 0xff) x = 255;\n if (y === 0xff) y = 255;\n\n // Convert the coordinates into a\n // properly formatted x10 utf8 sequence.\n s = '\\x1b[M' + String.fromCharCode(b) + String.fromCharCode(x) + String.fromCharCode(y);\n }\n\n // XTerm / X10\n if ((parts = /^\\x1b\\[M([\\x00\\u0020-\\uffff]{3})/.exec(s))) {\n b = parts[1].charCodeAt(0);\n x = parts[1].charCodeAt(1);\n y = parts[1].charCodeAt(2);\n\n key.name = 'mouse';\n key.type = 'X10';\n\n key.raw = [b, x, y, parts[0]];\n key.buf = buf;\n key.x = x - 32;\n key.y = y - 32;\n\n if (state.zero) {\n key.x--;\n key.y--;\n }\n\n if (x === 0) key.x = 255;\n if (y === 0) key.y = 255;\n\n mod = b >> 2;\n key.shift = Boolean(mod & 1);\n key.meta = Boolean((mod >> 1) & 1);\n key.ctrl = Boolean((mod >> 2) & 1);\n\n b -= 32;\n\n if ((b >> 6) & 1) {\n key.action = b & 1 ? 'wheeldown' : 'wheelup';\n key.button = 'middle';\n } else if (b === 3) {\n // NOTE: x10 and urxvt have no way\n // of telling which button mouseup used.\n key.action = 'mouseup';\n key.button = state._lastButton || 'unknown';\n delete state._lastButton;\n } else {\n key.action = 'mousedown';\n button = b & 3;\n key.button = button === 0\n ? 'left' : button === 1\n ? 'middle' : button === 2\n ? 'right' : 'unknown';\n\n state._lastButton = key.button;\n }\n\n // Probably a movement.\n // The *newer* VTE gets mouse movements comepletely wrong.\n // This presents a problem: older versions of VTE that get it right might\n // be confused by the second conditional in the if statement.\n // NOTE: Possibly just switch back to the if statement below.\n // none, shift, ctrl, alt\n // gnome: 32, 36, 48, 40\n // xterm: 35, _, 51, _\n // urxvt: 35, _, _, _\n // if (key.action === 'mousedown' && key.button === 'unknown') {\n if (b === 35 || b === 39 || b === 51 || b === 43\n || (state.isVTE && (b === 32 || b === 36 || b === 48 || b === 40))) {\n delete key.button;\n key.action = 'mousemove';\n }\n\n key.sequence = s;\n return key;\n }\n\n // URxvt\n if ((parts = /^\\x1b\\[(\\d+;\\d+;\\d+)M/.exec(s))) {\n params = parts[1].split(';');\n b = Number(params[0]);\n x = Number(params[1]);\n y = Number(params[2]);\n\n key.name = 'mouse';\n key.type = 'urxvt';\n\n key.raw = [b, x, y, parts[0]];\n key.buf = buf;\n key.x = x;\n key.y = y;\n\n if (state.zero) {\n key.x--;\n key.y--;\n }\n\n mod = b >> 2;\n key.shift = Boolean(mod & 1);\n key.meta = Boolean((mod >> 1) & 1);\n key.ctrl = Boolean((mod >> 2) & 1);\n\n // XXX Bug in urxvt after wheelup/down on mousemove\n // NOTE: This may be different than 128/129 depending\n // on mod keys.\n if (b === 128 || b === 129) b = 67;\n b -= 32;\n\n if ((b >> 6) & 1) {\n key.action = b & 1 ? 'wheeldown' : 'wheelup';\n key.button = 'middle';\n } else if (b === 3) {\n // NOTE: x10 and urxvt have no way\n // of telling which button mouseup used.\n key.action = 'mouseup';\n key.button = state._lastButton || 'unknown';\n delete state._lastButton;\n } else {\n key.action = 'mousedown';\n button = b & 3;\n key.button = button === 0 ? 'left'\n : button === 1 ? 'middle'\n : button === 2 ? 'right'\n : 'unknown';\n\n // NOTE: 0/32 = mousemove, 32/64 = mousemove with left down\n // if ((b >> 1) === 32)\n state._lastButton = key.button;\n }\n\n // Probably a movement.\n // The *newer* VTE gets mouse movements comepletely wrong.\n // This presents a problem: older versions of VTE that get it right might\n // be confused by the second conditional in the if statement.\n // NOTE: Possibly just switch back to the if statement below.\n // none, shift, ctrl, alt\n // urxvt: 35, _, _, _\n // gnome: 32, 36, 48, 40\n // if (key.action === 'mousedown' && key.button === 'unknown') {\n if (b === 35 || b === 39 || b === 51 || b === 43\n || (state.isVTE && (b === 32 || b === 36 || b === 48 || b === 40))) {\n delete key.button;\n key.action = 'mousemove';\n }\n\n key.sequence = s;\n return key;\n }\n\n // SGR\n if ((parts = /^\\x1b\\[<(\\d+;\\d+;\\d+)([mM])/.exec(s))) {\n down = parts[2] === 'M';\n params = parts[1].split(';');\n b = Number(params[0]);\n x = Number(params[1]);\n y = Number(params[2]);\n\n key.name = 'mouse';\n key.type = 'sgr';\n\n key.raw = [b, x, y, parts[0]];\n key.buf = buf;\n key.x = x;\n key.y = y;\n\n if (state.zero) {\n key.x--;\n key.y--;\n }\n\n mod = b >> 2;\n key.shift = Boolean(mod & 1);\n key.meta = Boolean((mod >> 1) & 1);\n key.ctrl = Boolean((mod >> 2) & 1);\n\n if ((b >> 6) & 1) {\n key.action = b & 1 ? 'wheeldown' : 'wheelup';\n key.button = 'middle';\n } else {\n key.action = down ? 'mousedown' : 'mouseup';\n button = b & 3;\n key.button = button === 0\n ? 'left' : button === 1\n ? 'middle' : button === 2\n ? 'right' : 'unknown';\n }\n\n // Probably a movement.\n // The *newer* VTE gets mouse movements comepletely wrong.\n // This presents a problem: older versions of VTE that get it right might\n // be confused by the second conditional in the if statement.\n // NOTE: Possibly just switch back to the if statement below.\n // none, shift, ctrl, alt\n // xterm: 35, _, 51, _\n // gnome: 32, 36, 48, 40\n // if (key.action === 'mousedown' && key.button === 'unknown') {\n if (b === 35 || b === 39 || b === 51 || b === 43\n || (state.isVTE && (b === 32 || b === 36 || b === 48 || b === 40))) {\n delete key.button;\n key.action = 'mousemove';\n }\n\n key.sequence = s;\n return key;\n }\n\n // DEC\n // The xterm mouse documentation says there is a\n // `<` prefix, the DECRQLP says there is no prefix.\n if ((parts = /^\\x1b\\[<(\\d+;\\d+;\\d+;\\d+)&w/.exec(s))) {\n params = parts[1].split(';');\n b = Number(params[0]);\n x = Number(params[1]);\n y = Number(params[2]);\n page = Number(params[3]);\n\n key.name = 'mouse';\n key.type = 'dec';\n\n key.raw = [b, x, y, parts[0]];\n key.buf = buf;\n key.x = x;\n key.y = y;\n key.page = page;\n\n if (state.zero) {\n key.x--;\n key.y--;\n }\n\n key.action = b === 3 ? 'mouseup' : 'mousedown';\n key.button = b === 2\n ? 'left' : b === 4\n ? 'middle' : b === 6\n ? 'right' : 'unknown';\n\n key.sequence = s;\n return key;\n }\n\n // vt300\n if ((parts = /^\\x1b\\[24([0135])~\\[(\\d+),(\\d+)\\]\\r/.exec(s))) {\n b = Number(parts[1]);\n x = Number(parts[2]);\n y = Number(parts[3]);\n\n key.name = 'mouse';\n key.type = 'vt300';\n\n key.raw = [b, x, y, parts[0]];\n key.buf = buf;\n key.x = x;\n key.y = y;\n\n if (state.zero) {\n key.x--;\n key.y--;\n }\n\n key.action = 'mousedown';\n key.button = b === 1 ? 'left' : b === 2 ? 'middle' : b === 5 ? 'right' : 'unknown';\n\n key.sequence = s;\n return key;\n }\n\n if ((parts = /^\\x1b\\[(O|I)/.exec(s))) {\n key.action = parts[1] === 'I' ? 'focus' : 'blur';\n }\n\n // s = key.sequence;\n // b = s.charCodeAt(3);\n key.x = s.charCodeAt(4) - 0o040;\n key.y = s.charCodeAt(5) - 0o040;\n\n key.scroll = 0;\n\n key.ctrl = Boolean(1 << 4 & b);\n key.meta = Boolean(1 << 3 & b);\n key.shift = Boolean(1 << 2 & b);\n\n key.release = (3 & b) === 3;\n\n if (1 << 6 & b) { //scroll\n key.scroll = 1 & b ? 1 : -1;\n }\n\n if (!key.release && !key.scroll) {\n key.button = b & 3;\n }\n\n key.sequence = s;\n return key;\n};\n","export const keycodes = [\n { sequence: '\\r', shortcut: 'return' },\n { sequence: '\\x7F', shortcut: 'backspace' },\n { sequence: '\\x1B', shortcut: 'escape' },\n { sequence: '\\x1B[1~', shortcut: 'home' },\n { sequence: '\\x1B\\x1B[1~', shortcut: 'home' },\n { sequence: '\\x1B[2~', shortcut: 'insert' },\n { sequence: '\\x1B[3~', shortcut: 'delete' },\n { sequence: '\\x1B[4~', shortcut: 'end' },\n { sequence: '\\x1B[A', shortcut: 'up' },\n { sequence: '\\x1B[B', shortcut: 'down' },\n { sequence: '\\x1B[C', shortcut: 'right' },\n { sequence: '\\x1B[D', shortcut: 'left' },\n\n // <shift>\n { sequence: '\\x1B[2;2~', shortcut: 'shift+insert' },\n { sequence: '\\x1B[5;2~', shortcut: 'shift+pageup' },\n { sequence: '\\x1B[6;2~', shortcut: 'shift+pagedown' },\n { sequence: '\\x1B[1;2A', shortcut: 'shift+up' },\n { sequence: '\\x1B[1;2B', shortcut: 'shift+down' },\n { sequence: '\\x1B[1;2C', shortcut: 'shift+right' },\n { sequence: '\\x1B[1;2D', shortcut: 'shift+left' },\n { sequence: '\\x1B[Z', shortcut: 'shift+tab' },\n\n // <meta>\n { sequence: '\\x1Bf', shortcut: 'meta+right' },\n { sequence: '\\x1Bb', shortcut: 'meta+left' },\n { sequence: '\\x1B[1;9A', shortcut: 'meta+up' },\n { sequence: '\\x1B[1;9B', shortcut: 'meta+down' },\n { sequence: '\\x1B[1;9C', shortcut: 'cmd+meta+right' },\n { sequence: '\\x1B[1;9D', shortcut: 'cmd+meta+left' },\n { sequence: '\\x1B[2;3~', shortcut: 'meta+insert' },\n { sequence: '\\x1B[3;3~', shortcut: 'meta+delete' },\n { sequence: '\\x1B[5;3~', shortcut: 'meta+pageup' },\n { sequence: '\\x1B[6;3~', shortcut: 'meta+pagedown' },\n { sequence: '\\x1B\\x7F', shortcut: 'meta+backspace' },\n\n // <shift+meta>\n { sequence: '\\x1B[1;10A', shortcut: 'shift+meta+up' },\n { sequence: '\\x1B[1;10B', shortcut: 'shift+meta+down' },\n { sequence: '\\x1B[1;10C', shortcut: 'shift+meta+right' },\n { sequence: '\\x1B[1;10D', shortcut: 'shift+meta+left' },\n { sequence: '\\x1B[2;4~', shortcut: 'shift+meta+insert' },\n { sequence: '\\x1B[3;4~', shortcut: 'shift+meta+delete' },\n { sequence: '\\x1B[5;4~', shortcut: 'shift+meta+pageup' },\n { sequence: '\\x1B[6;4~', shortcut: 'shift+meta+pagedown' },\n\n // <ctrl>\n { sequence: '\\x00', shortcut: 'ctrl+`' },\n { sequence: '\\x0c', shortcut: 'ctrl+4' },\n // { sequence: '\\x0d', shortcut: 'ctrl+5' },\n { sequence: '\\x0e', shortcut: 'ctrl+6' },\n { sequence: '\\x0f', shortcut: 'ctrl+7' },\n { sequence: '\\x01', shortcut: 'ctrl+a' },\n { sequence: '\\x02', shortcut: 'ctrl+b' },\n { sequence: '\\x03', shortcut: 'ctrl+c' },\n { sequence: '\\x04', shortcut: 'ctrl+d' },\n { sequence: '\\x05', shortcut: 'ctrl+e' },\n { sequence: '\\x06', shortcut: 'ctrl+f' },\n { sequence: '\\x07', shortcut: 'ctrl+g' },\n // { sequence: '\\b', shortcut: 'ctrl+h' },\n { sequence: '\\t', shortcut: 'ctrl+i' },\n { sequence: '\\n', shortcut: 'ctrl+j' },\n { sequence: '\\x0B', shortcut: 'ctrl+k' },\n { sequence: '\\f', shortcut: 'ctrl+l' },\n { sequence: '\\x0E', shortcut: 'ctrl+n' },\n { sequence: '\\x0F', shortcut: 'ctrl+o' },\n { sequence: '\\x10', shortcut: 'ctrl+p' },\n { sequence: '\\x11', shortcut: 'ctrl+q' },\n { sequence: '\\x12', shortcut: 'ctrl+r' },\n { sequence: '\\x13', shortcut: 'ctrl+s' },\n { sequence: '\\x14', shortcut: 'ctrl+t' },\n { sequence: '\\x15', shortcut: 'ctrl+u' },\n { sequence: '\\x16', shortcut: 'ctrl+v' },\n { sequence: '\\x17', shortcut: 'ctrl+w' },\n { sequence: '\\x18', shortcut: 'ctrl+x' },\n { sequence: '\\x19', shortcut: 'ctrl+y' },\n { sequence: '\\x1A', shortcut: 'ctrl+z' },\n { sequence: '\\x1F', shortcut: 'ctrl+-' },\n { sequence: '\\x1C', shortcut: 'ctrl+|' },\n { sequence: '\\x1D', shortcut: 'ctrl+]' },\n { sequence: '\\x1B[1;5A', shortcut: 'ctrl+up', weight: -1 },\n { sequence: '\\x1B[1;5B', shortcut: 'ctrl+down', weight: -1 },\n { sequence: '\\x1B[1;5C', shortcut: 'ctrl+right', weight: -1 },\n { sequence: '\\x1B[1;5D', shortcut: 'ctrl+left', weight: -1 },\n { sequence: '\\x1B[2;5~', shortcut: 'ctrl+insert', weight: -1 },\n { sequence: '\\x1B[5;5~', shortcut: 'ctrl+pageup', weight: -1 },\n { sequence: '\\x1B[6;5~', shortcut: 'ctrl+pagedown', weight: -1 },\n\n // <ctrl+shift>\n { sequence: '\\x1B[1;6B', shortcut: 'ctrl+shift+down' },\n { sequence: '\\x1B[1;6D', shortcut: 'ctrl+shift+left' },\n { sequence: '\\x1B[1;6C', shortcut: 'ctrl+shift+right' },\n { sequence: '\\x1B[1;6A', shortcut: 'ctrl+shift+up' },\n\n // <ctrl+shift+meta>\n { sequence: '\\x1B[1;14B', shortcut: 'ctrl+shift+meta+down' },\n { sequence: '\\x1B[1;14D', shortcut: 'ctrl+shift+meta+left' },\n { sequence: '\\x1B[1;14C', shortcut: 'ctrl+shift+meta+right' },\n\n // <fn>\n { sequence: '\\x1B[6~', shortcut: 'fn+down', name: 'end' },\n { sequence: '\\x1B[H', shortcut: 'fn+left', name: 'home' },\n { sequence: '\\x1B[F', shortcut: 'fn+right', name: 'pagedown' },\n { sequence: '\\x1B[5~', shortcut: 'fn+up', name: 'pageup' },\n\n // <fn+ctrl>\n { sequence: '\\x1B[1;5F', shortcut: 'fn+ctrl+right' },\n { sequence: '\\x1B[1;5H', shortcut: 'fn+ctrl+left' },\n { sequence: '\\x1B[3;5~', shortcut: 'fn+ctrl+delete' },\n\n // <fn+shift>\n { sequence: '\\x1B[1;2F', shortcut: 'fn+shift+right', name: 'shift+home' },\n { sequence: '\\x1B[1;2H', shortcut: 'fn+shift+left', name: 'shift+end' },\n { sequence: '\\x1B[3;2~', shortcut: 'fn+shift+delete' },\n\n // <fn+meta>\n { sequence: '\\x1B[1;9F', shortcut: 'fn+meta+right' },\n { sequence: '\\x1B[1;9H', shortcut: 'fn+meta+left' },\n { sequence: '\\x1B[5;9~', shortcut: 'fn+meta+up' },\n { sequence: '\\x1B[6;9~', shortcut: 'fn+meta+down' },\n { sequence: '\\x1B\\x1B[5~', shortcut: 'fn+meta+up' },\n { sequence: '\\x1B\\x1B[6~', shortcut: 'fn+meta+down' },\n { sequence: '\\x1Bd', shortcut: 'fn+meta+delete', fn: true },\n\n // <fn+ctrl+shift>\n { sequence: '\\x1B[1;6F', shortcut: 'fn+ctrl+shift+right' },\n { sequence: '\\x1B[1;6H', shortcut: 'fn+ctrl+shift+left' },\n { sequence: '\\x1B[3;6~', shortcut: 'fn+ctrl+shift+delete' },\n { sequence: '\\x1B[5;6~', shortcut: 'fn+ctrl+shift+up' },\n { sequence: '\\x1B[6;6~', shortcut: 'fn+ctrl+shift+down' },\n\n // <fn+ctrl+meta>\n { sequence: '\\x1B[1;13F', shortcut: 'fn+ctrl+meta+right' },\n { sequence: '\\x1B[1;13H', shortcut: 'fn+ctrl+meta+left' },\n { sequence: '\\x1B[3;13~', shortcut: 'fn+ctrl+meta+delete' },\n\n // <fn+shift+meta>\n { sequence: '\\x1B[1;10F', shortcut: 'fn+shift+meta+right' },\n { sequence: '\\x1B[1;10H', shortcut: 'fn+shift+meta+left' },\n { sequence: '\\x1B[3;10~', shortcut: 'fn+shift+meta+delete' },\n { sequence: '\\x1B[5;10~', shortcut: 'fn+shift+meta+up' },\n { sequence: '\\x1B[6;10~', shortcut: 'fn+shift+meta+down' },\n\n // <fn+ctrl+shift+meta>\n { sequence: '\\x1B[1;14F', shortcut: 'fn+ctrl+shift+meta+right' },\n { sequence: '\\x1B[1;14H', shortcut: 'fn+ctrl+shift+meta+left' },\n { sequence: '\\x1B[3;14~', shortcut: 'fn+ctrl+shift+meta+delete' },\n { sequence: '\\x1B[5;14~', shortcut: 'fn+ctrl+shift+meta+up' },\n { sequence: '\\x1B[6;14~', shortcut: 'fn+ctrl+shift+meta+down' },\n\n // <f1-f4>\n { sequence: '\\x1BOP', shortcut: 'f1' },\n { sequence: '\\x1BOQ', shortcut: 'f2' },\n { sequence: '\\x1BOR', shortcut: 'f3' },\n { sequence: '\\x1BOS', shortcut: 'f4' },\n\n // <f1-f20>\n { sequence: '\\x1B[11~', shortcut: 'f1' },\n { sequence: '\\x1B[12~', shortcut: 'f2' },\n { sequence: '\\x1B[13~', shortcut: 'f3' },\n { sequence: '\\x1B[14~', shortcut: 'f4' },\n { sequence: '\\x1B[15~', shortcut: 'f5' },\n { sequence: '\\x1B[17~', shortcut: 'f6' },\n { sequence: '\\x1B[18~', shortcut: 'f7' },\n { sequence: '\\x1B[19~', shortcut: 'f8' },\n { sequence: '\\x1B[20~', shortcut: 'f9' },\n { sequence: '\\x1B[21~', shortcut: 'f10' },\n { sequence: '\\x1B[23~', shortcut: 'f11' },\n { sequence: '\\x1B[24~', shortcut: 'f12' },\n { sequence: '\\x1B[25~', shortcut: 'f13' },\n { sequence: '\\x1B[26~', shortcut: 'f14' },\n { sequence: '\\x1B[28~', shortcut: 'f15' },\n { sequence: '\\x1B[29~', shortcut: 'f16' },\n { sequence: '\\x1B[31~', shortcut: 'f17' },\n { sequence: '\\x1B[32~', shortcut: 'f18' },\n { sequence: '\\x1B[33~', shortcut: 'f19' },\n { sequence: '\\x1B[34~', shortcut: 'f20' },\n\n // <shift+f1-f12>\n { sequence: '\\x1B[1;2P', shortcut: 'shift+f1' },\n { sequence: '\\x1B[1;2Q', shortcut: 'shift+f2' },\n { sequence: '\\x1B[1;2R', shortcut: 'shift+f3' },\n { sequence: '\\x1B[1;2S', shortcut: 'shift+f4' },\n { sequence: '\\x1B[15;2~', shortcut: 'shift+f5' },\n { sequence: '\\x1B[17;2~', shortcut: 'shift+f6' },\n { sequence: '\\x1B[18;2~', shortcut: 'shift+f7' },\n { sequence: '\\x1B[19;2~', shortcut: 'shift+f8' },\n { sequence: '\\x1B[20;2~', shortcut: 'shift+f9' },\n { sequence: '\\x1B[21;2~', shortcut: 'shift+f10' },\n { sequence: '\\x1B[23;2~', shortcut: 'shift+f11' },\n { sequence: '\\x1B[24;2~', shortcut: 'shift+f12' },\n\n // <num_key>\n { sequence: '\\x1BOp', shortcut: 'num_key_0' },\n { sequence: '\\x1BOq', shortcut: 'num_key_1' },\n { sequence: '\\x1BOr', shortcut: 'num_key_2' },\n { sequence: '\\x1BOs', shortcut: 'num_key_3' },\n { sequence: '\\x1BOt', shortcut: 'num_key_4' },\n { sequence: '\\x1BOu', shortcut: 'num_key_5' },\n { sequence: '\\x1BOv', shortcut: 'num_key_6' },\n { sequence: '\\x1BOw', shortcut: 'num_key_7' },\n { sequence: '\\x1BOx', shortcut: 'num_key_8' },\n { sequence: '\\x1BOy', shortcut: 'num_key_9' },\n { sequence: '\\x1BOl', shortcut: 'num_key_comma' },\n { sequence: '\\x1BOm', shortcut: 'num_key_minus' },\n { sequence: '\\x1BOn', shortcut: 'num_key_period' }\n].map(key => ({ ...key, weight: key.weight || 0 }));\n","/* eslint-disable no-control-regex */\nimport type readline from 'node:readline';\nexport const PRINTABLE_CHAR_REGEX = /^(?!.*[\\uFEFF])[\\p{L}\\p{N}\\p{P}\\p{S}\\p{Z}\\p{M}\\s]+$/u;\nexport const NON_PRINTABLE_CHAR_REGEX = /[\\p{Cc}\\p{Cf}]/u;\n\nexport const metaKeys = new Set(['alt', 'meta', 'option']);\nexport const modifierKeys = ['fn', 'ctrl', 'shift', ...[...metaKeys], 'cmd'];\n\n// Based on isMouse from chjj/blessed\n// Copyright (c) 2013-2015, Christopher Jeffrey and contributors\nexport function isMousepress(input, key) {\n if (key.code === '[M' || key.code === '[I' || key.code === '[O') {\n return true;\n }\n\n return /\\x1b\\[M/.test(input)\n || /\\x1b\\[M([\\x00\\u0020-\\uffff]{3})/.test(input)\n || /\\x1b\\[(\\d+;\\d+;\\d+)M/.test(input)\n || /\\x1b\\[<(\\d+;\\d+;\\d+)([mM])/.test(input)\n || /\\x1b\\[<(\\d+;\\d+;\\d+;\\d+)&w/.test(input)\n || /\\x1b\\[24([0135])~\\[(\\d+),(\\d+)\\]\\r/.test(input)\n || /\\x1b\\[(O|I)/.test(input);\n}\n\nexport const parsePosition = input => {\n if (!input) return null;\n // eslint-disable-next-line no-control-regex\n const match = /^\\x1B\\[([0-9]+);([0-9]+)R/.exec(String(input));\n\n if (match) {\n return {\n name: 'position',\n position: { y: match[1], x: match[2] },\n printable: false\n };\n }\n\n return null;\n};\n\nconst order = [\n 'sequence',\n 'name',\n 'shortcut',\n 'command',\n 'ctrl',\n 'shift',\n 'meta',\n 'fn',\n 'printable',\n 'pasted',\n 'weight'\n];\n\nexport const sortKeys = (obj, keys = order) => {\n const ordered = {};\n\n for (const key of keys) {\n if (obj[key] !== undefined) {\n ordered[key] = obj[key];\n }\n }\n\n for (const [k, v] of Object.entries(obj)) {\n if (!(k in ordered)) {\n ordered[k] = v;\n }\n }\n\n return ordered;\n};\n\nexport const sortModifiers = (names: string[]): string[] => {\n const modifiers = [];\n const after = [];\n\n for (const name of modifierKeys) {\n if (names.includes(name)) {\n modifiers.push(name);\n }\n }\n\n for (const name of names) {\n if (!modifiers.includes(name)) {\n after.push(name);\n }\n }\n\n return modifiers.concat(after);\n};\n\nexport const createShortcut = (key: readline.Key): string => {\n const modifiers = new Set();\n if (key.fn) modifiers.add('fn');\n if (key.ctrl) modifiers.add('ctrl');\n if (key.shift) modifiers.add('shift');\n if (key.alt) modifiers.add('meta');\n if (key.option) modifiers.add('meta');\n if (key.meta) modifiers.add('meta');\n\n let keyName = isPrintableCharacter(key.sequence) ? key.sequence : key.name;\n if (keyName === 'undefined') keyName = '';\n\n const output = modifiers.size > 0 && keyName\n ? `${sortModifiers([...modifiers]).join('+')}+${keyName}`\n : keyName;\n\n return output.length > 1 ? output : '';\n};\