UNPKG

emit-keypress

Version:

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

1 lines 144 kB
{"version":3,"sources":["../index.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/color.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/normalize.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/env.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/parent.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/process-title.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/shell.ts","../node_modules/.pnpm/detect-terminal@3.0.0/node_modules/detect-terminal/src/details.ts","../src/emit-keypress.ts","../src/keypress.ts","../src/mousepress.ts","../src/keycodes.ts","../src/keyboard-protocol.ts","../src/utils.ts"],"sourcesContent":["/* eslint-disable no-control-regex */\nimport type readline from 'node:readline';\nimport { stdin, stdout } from 'node:process';\nimport { detectTerminal } from 'detect-terminal';\nimport { emitKeypressEvents } from '~/emit-keypress';\nimport { mousepress } from '~/mousepress';\nimport { keycodes } from '~/keycodes';\nimport { kEscape } from '~/keypress';\nimport { enableKeyboardProtocol, resetKeyboardProtocol } from '~/keyboard-protocol';\nimport {\n createShortcut,\n isMousepress,\n isPrintableCharacter,\n parsePosition,\n prioritizeKeymap,\n sortShortcutModifier\n} from '~/utils';\n\nexport * from '~/utils';\n\nexport const isWindows = globalThis.process.platform === 'win32';\nexport const MAX_PASTE_BUFFER = 1024 * 1024; // 1MB limit for paste buffer\nexport const ENABLE_PASTE_BRACKET_MODE = `${kEscape}[?2004h`;\nexport const DISABLE_PASTE_BRACKET_MODE = `${kEscape}[?2004l`;\nexport const ENABLE_MOUSE = `${kEscape}[?1003h`;\nexport const DISABLE_MOUSE = `${kEscape}[?1003l`;\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(ENABLE_MOUSE);\n};\n\nexport const disableMouse = (stdout: NodeJS.WriteStream) => {\n stdout.write(DISABLE_MOUSE);\n};\n\nexport const cursor = {\n hide: (stdout: NodeJS.WriteStream) => {\n stdout.write(`${kEscape}[?25l`);\n },\n show: (stdout: NodeJS.WriteStream) => {\n stdout.write(`${kEscape}[?25h`);\n },\n position: (stdout: NodeJS.WriteStream) => {\n stdout.write(`${kEscape}[6n`);\n }\n};\n\nexport const hasMatchingModifiers = (a: readline.Key, b) => {\n return (\n (!hasModifier(a) && !hasModifier(b)) ||\n (a.ctrl === b.ctrl && a.shift === b.shift && a.meta === b.meta && a.fn === b.fn)\n );\n};\n\nexport const hasModifier = (key: readline.Key) => {\n return key.ctrl || key.shift || key.meta || key.fn;\n};\n\ntype RawModeStream = NodeJS.ReadableStream & {\n isRaw?: boolean;\n setRawMode?: (mode: boolean) => void;\n};\n\nconst isReadableStream = (input: unknown): input is NodeJS.ReadableStream => {\n if (!input || typeof input !== 'object') return false;\n\n const stream = input as NodeJS.ReadableStream;\n return (\n typeof stream.on === 'function' &&\n typeof stream.off === 'function' &&\n typeof stream.once === 'function' &&\n typeof stream.pause === 'function' &&\n typeof stream.resume === 'function' &&\n typeof stream.setEncoding === 'function'\n );\n};\n\nexport const createEmitKeypress = (config?: { setupProcessHandlers?: boolean }) => {\n const sessionCounts = new WeakMap<NodeJS.ReadableStream, number>();\n\n function acquireInput(input) {\n sessionCounts.set(input, (sessionCounts.get(input) || 0) + 1);\n }\n\n function releaseInput(input) {\n const count = sessionCounts.get(input) || 0;\n if (count > 1) {\n sessionCounts.set(input, count - 1);\n } else {\n sessionCounts.delete(input);\n input.pause(); // actually pause only when last session closes\n }\n }\n\n // If this is the singleton, use the (possibly global) handlers array\n const setupProcessHandlers = config?.setupProcessHandlers === true;\n let onExitHandlers: Set<() => void>;\n\n // If not explicitly told to skip, AND we are the singleton (first created),\n // use the process global array\n if (setupProcessHandlers || !config) {\n // Use process-global handlers for the singleton instance only\n onExitHandlers = globalThis.onExitHandlers ||= new Set();\n\n if (!globalThis.exitHandlers) {\n globalThis.exitHandlers = onExitHandlers;\n }\n\n const hasListener = (name, fn) => {\n return process.listeners(name).includes(fn);\n };\n\n // Register process listeners ONLY ONCE (singleton)\n if (!hasListener('uncaughtException', onExitHandler)) {\n process.once('uncaughtException', onExitHandler);\n }\n\n if (!hasListener('SIGINT', onExitHandler)) {\n process.once('SIGINT', onExitHandler);\n }\n\n if (!hasListener('exit', onExitHandler)) {\n process.once('exit', onExitHandler);\n }\n\n } else {\n // For non-singleton, just use a local handlers array\n onExitHandlers = new Set();\n }\n\n function onExitHandler() {\n for (const fn of onExitHandlers) {\n try {\n fn();\n onExitHandlers.delete(fn);\n } catch (err) {\n console.error('Error in exit handler:', err);\n }\n }\n }\n\n const emitKeypress = ({\n input = stdin,\n output = stdout,\n keymap = [],\n onKeypress,\n onMousepress,\n onExit,\n maxPasteBuffer = MAX_PASTE_BUFFER,\n escapeCodeTimeout = 500,\n handleClose = true,\n hideCursor = false,\n initialPosition = false,\n enablePasteMode = false,\n pasteModeTimeout = 100,\n keyboardProtocol = false\n }: {\n\n input?: NodeJS.ReadableStream;\n output?: NodeJS.WriteStream;\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: unknown, close: () => void) => void;\n onExit?: () => void;\n maxPasteBuffer?: number;\n escapeCodeTimeout?: number;\n handleClose?: boolean;\n hideCursor?: boolean;\n initialPosition?: boolean;\n enablePasteMode?: boolean;\n pasteModeTimeout?: number;\n keyboardProtocol?: boolean;\n }) => {\n if (!isReadableStream(input)) {\n throw new Error('Invalid stream passed');\n }\n\n const rawModeStream = input as RawModeStream;\n const isRaw = rawModeStream.isRaw === true;\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 let disableProtocol: (() => void) | null = null;\n\n const clearPasteState = () => {\n pasting = false;\n buffer = '';\n\n if (pasteTimeout) {\n clearTimeout(pasteTimeout);\n pasteTimeout = null;\n }\n };\n\n if (typeof keymap === 'function') {\n keymap = keymap();\n }\n\n async function handleKeypress(input: string, key: readline.Key) {\n if (input === undefined && key.sequence === '\\x1B[27u' && keyboardProtocol === true) {\n key.name = 'esc';\n key.sequence = '\\x1B';\n key.ctrl = false;\n key.meta = false;\n key.shift = false;\n key.printable = false;\n onKeypress('', key, close);\n return;\n }\n\n if (initialPosition && initial && key.name === 'position') {\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 < maxPasteBuffer) {\n buffer += key.sequence?.replace(/\\r/g, '\\n') || '';\n }\n\n // Ignore any more characters, but don't clear state yet!\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 (!sorted) {\n keymap = prioritizeKeymap(keymap);\n sorted = true;\n }\n\n const found = keymap.filter(k => k.sequence === key.sequence);\n\n if (found.length === 1) {\n key = { ...key, ...found[0] };\n addShortcut = false;\n }\n\n // console.log({ found, key });\n\n const shortcut = key.shortcut\n ? sortShortcutModifier(key.shortcut)\n : 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 // Only continue comparison if the custom key mapping does not have a sequence\n if (\n shortcut === mapping.shortcut ||\n (key.name && key.name === mapping.shortcut && hasMatchingModifiers(key, mapping))\n ) {\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 acquireInput(input);\n\n function close() {\n if (closed) return;\n closed = true;\n\n onExitHandlers.delete(close);\n if (!isWindows && typeof rawModeStream.setRawMode === 'function') {\n rawModeStream.setRawMode(isRaw);\n }\n if (hideCursor) cursor.show(output);\n if (onMousepress) disableMouse(output);\n if (enablePasteMode) disablePaste(output);\n if (disableProtocol) disableProtocol();\n if (onKeypress) input.off('keypress', handleKeypress);\n if (pasteTimeout) clearTimeout(pasteTimeout);\n input.off('pause', close);\n releaseInput(input);\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 resetKeyboardProtocol(output);\n\n if (keyboardProtocol) {\n disableProtocol = enableKeyboardProtocol(detectTerminal(), output);\n }\n\n // Disable automatic character echoing\n if (!isWindows && typeof rawModeStream.setRawMode === 'function') {\n rawModeStream.setRawMode(true);\n }\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 && !onExitHandlers.has(close)) {\n onExitHandlers.add(close);\n }\n\n return close;\n };\n\n return {\n emitKeypress,\n onExitHandlers\n };\n};\n\nexport declare global {\n var onExitHandlers: Set<() => void>;\n var exitHandlers: Array<() => void>;\n}\n\nexport const { emitKeypress } = createEmitKeypress();\n\nexport {\n createShortcut,\n emitKeypressEvents,\n isMousepress,\n isPrintableCharacter,\n keycodes,\n mousepress\n};\n\nexport default emitKeypress;\n","import type { ColorLevel } from './types';\n\nconst TRUE_COLOR_PATTERN = /^(truecolor|24bits?)$/;\n\nexport const has256Color = (value: string | undefined): boolean => {\n return Boolean(value?.match(/256/));\n};\n\nexport const supportsTrueColor = (value: string | undefined): boolean => {\n return Boolean(value?.match(TRUE_COLOR_PATTERN));\n};\n\nexport const getColorSupport = (env: NodeJS.ProcessEnv) => {\n return {\n supports256Color: has256Color(env.TERM) || has256Color(env.COLORTERM),\n supportsTrueColor: supportsTrueColor(env.COLORTERM)\n };\n};\n\nconst NO_BASIC_COLOR_TERMINALS = new Set([\n 'dumb',\n 'linux_console',\n 'none',\n 'unknown',\n 'vt100'\n]);\n\nexport const getColorLevel = (\n env: NodeJS.ProcessEnv,\n terminal?: string | null,\n colorSupport = getColorSupport(env)\n): ColorLevel => {\n const { supports256Color, supportsTrueColor } = colorSupport;\n\n if (supportsTrueColor) {\n return 3;\n }\n\n if (supports256Color) {\n return 2;\n }\n\n if (terminal && !NO_BASIC_COLOR_TERMINALS.has(terminal)) {\n return 1;\n }\n\n if (env.TERM_PROGRAM || env.COLORTERM || env.TERMUX_VERSION) {\n return 1;\n }\n\n return 0;\n};\n","import type { DetectOptions } from './types';\nimport path from 'node:path';\n\nexport const AMBIGUOUS_ENV_TERMINALS = new Set([\n 'linux_console',\n 'truecolor_terminal',\n 'vt100',\n 'xterm',\n 'xterm_256color',\n 'xterm_truecolor'\n]);\n\nexport const SHELL_PROCESS_NAMES = new Set([\n 'bash',\n 'csh',\n 'dash',\n 'fish',\n 'ksh',\n 'login',\n 'node',\n 'nodejs',\n 'nu',\n 'sh',\n 'sudo',\n 'tcsh',\n 'zsh'\n]);\n\nexport const normalizeIdentifier = (value: string): string => {\n return value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_');\n};\n\nexport const normalizeAppName = (value: string, platform: NodeJS.Platform): string => {\n const appName = platform === 'darwin' ? path.parse(value).name : value;\n return normalizeIdentifier(appName);\n};\n\nexport const isAmbiguousEnvResult = (\n terminal: string | null,\n options: DetectOptions = {}\n): boolean => {\n if (!terminal) {\n return true;\n }\n\n if (options.preferOuter && (terminal === 'tmux' || terminal === 'screen')) {\n return true;\n }\n\n return AMBIGUOUS_ENV_TERMINALS.has(terminal);\n};\n\nexport const hasKonsoleEnvironment = (env: NodeJS.ProcessEnv): boolean => {\n return Object.keys(env).some(envVar => /konsole/i.test(envVar));\n};\n\nexport const normalizeParentProcessName = (processName: string): string | null => {\n const normalized = normalizeIdentifier(path.parse(processName).name);\n\n switch (normalized) {\n case 'alacritty':\n case 'aterm':\n case 'atomic_terminal':\n case 'eterm':\n case 'foot':\n case 'ghostty':\n case 'hyper':\n case 'kitty':\n case 'konsole':\n case 'kuake':\n case 'mate_terminal':\n case 'mrxvt':\n case 'powershell':\n case 'putty':\n case 'qterminal':\n case 'rio':\n case 'rxvt':\n case 'terminator':\n case 'terminology':\n case 'tilda':\n case 'tmux':\n case 'warp':\n case 'wezterm':\n case 'wterm':\n return normalized;\n case 'apple_terminal':\n case 'terminal':\n return 'terminal';\n case 'cmd':\n return 'cmd';\n case 'code':\n case 'code_insiders':\n return 'vscode';\n case 'conhost':\n return 'conhost';\n case 'gnome':\n case 'gnome_terminal':\n case 'gnome_terminal_server':\n case 'guake':\n return 'gnome_terminal';\n case 'iterm':\n case 'iterm2':\n return 'iterm';\n case 'login':\n case 'linux':\n return 'linux_console';\n case 'pwsh':\n return 'powershell';\n case 'screen':\n return 'screen';\n case 'terminal_app':\n return 'terminal_app';\n case 'urxvt':\n case 'urxvt256c':\n case 'urxvt256c_ml':\n case 'urxvt_ml':\n return 'rxvt';\n case 'wt':\n case 'windows_terminal':\n return 'windows_terminal';\n case 'xfce':\n case 'xfce_terminal':\n case 'xfce4_terminal':\n return 'xfce4_terminal';\n default: {\n if (normalized.startsWith('gnome_terminal')) {\n return 'gnome_terminal';\n }\n\n if (normalized.startsWith('warp')) {\n return 'warp';\n }\n\n return null;\n }\n }\n};\n","import type { DetectOptions, DetectionResult } from './types';\nimport { getColorSupport } from './color';\nimport {\n hasKonsoleEnvironment,\n normalizeAppName,\n normalizeIdentifier\n} from './normalize';\n\nconst colorVariant = (\n terminal: string,\n supports256Color: boolean,\n supportsTrueColor: boolean\n): string => {\n return supports256Color || supportsTrueColor ? `${terminal}_256color` : terminal;\n};\n\nconst normalizeTermProgram = (termProgram: string): string | null => {\n switch (termProgram) {\n case 'apple_terminal':\n return 'terminal';\n case 'eterm':\n return 'eterm';\n case 'gnome_terminal':\n case 'gnome_terminal_server':\n return 'gnome_terminal';\n case 'hyper':\n return 'hyper';\n case 'iterm_app':\n case 'iterm':\n case 'iterm2':\n return 'iterm';\n case 'kitty':\n return 'kitty';\n case 'konsole':\n return 'konsole';\n case 'mate_terminal':\n return 'mate_terminal';\n case 'powershell':\n return 'powershell';\n case 'putty':\n return 'putty';\n case 'qterminal':\n return 'qterminal';\n case 'rio':\n return 'rio';\n case 'rxvt':\n return 'rxvt';\n case 'terminal_app':\n return 'terminal_app';\n case 'terminal':\n return 'terminal';\n case 'terminator':\n return 'terminator';\n case 'termux':\n return 'termux';\n case 'vscode':\n return 'vscode';\n case 'warp':\n return 'warp';\n case 'wezterm':\n return 'wezterm';\n case 'xfce4_terminal':\n return 'xfce4_terminal';\n case 'alacritty':\n return 'alacritty';\n default:\n return null;\n }\n};\n\nconst detectFromTerm = (\n term: string,\n env: NodeJS.ProcessEnv,\n platform: NodeJS.Platform\n): string => {\n const { supports256Color, supportsTrueColor } = getColorSupport(env);\n\n if (term === 'xterm' || term === 'xterm_256color') {\n if (supportsTrueColor) {\n return 'xterm_truecolor';\n }\n\n if (env.VTE_VERSION) {\n const vteVersion = Number.parseInt(env.VTE_VERSION, 10);\n if (vteVersion >= 3803) {\n return colorVariant('gnome_terminal', supports256Color, supportsTrueColor);\n }\n }\n\n if (hasKonsoleEnvironment(env)) {\n return colorVariant('konsole', supports256Color, supportsTrueColor);\n }\n\n if (platform === 'darwin') {\n return 'terminal';\n }\n\n return term;\n }\n\n switch (term) {\n case 'linux':\n return 'linux_console';\n case 'gnome':\n case 'gnome_256color':\n case 'gnome_terminal':\n case 'gnome_terminal_256color':\n case 'guake':\n case 'terminator':\n return colorVariant('gnome_terminal', supports256Color, supportsTrueColor);\n case 'konsole':\n return colorVariant('konsole', supports256Color, supportsTrueColor);\n case 'rxvt':\n case 'rxvt_xpm':\n case 'rxvt_unicode':\n case 'rxvt_unicode_256color':\n case 'urxvt':\n case 'urxvt_ml':\n case 'urxvt256c':\n case 'urxvt256c_ml':\n return colorVariant('rxvt', term === 'rxvt' || supports256Color, supportsTrueColor);\n case 'xfce':\n case 'xfce_terminal':\n case 'xfce4_terminal':\n return 'xfce4_terminal';\n case 'eterm':\n return colorVariant('eterm', supports256Color, supportsTrueColor);\n case 'xterm_kitty':\n case 'kitty':\n return 'kitty';\n case 'screen':\n case 'screen_256color':\n return 'screen';\n case 'tmux':\n case 'tmux_256color':\n return 'tmux';\n case 'vt100':\n return 'vt100';\n case 'aterm':\n case 'atomic_terminal':\n case 'dopamine':\n case 'foot':\n case 'ghostty':\n case 'kuake':\n case 'rio':\n case 'terminology':\n case 'termux':\n case 'tilda':\n case 'wterm':\n case 'mrxvt':\n return term;\n default:\n if (/screen/.test(term)) return 'screen';\n if (/tmux/.test(term)) return 'tmux';\n if (/rxvt/.test(term)) return 'rxvt';\n if (/vt100/.test(term)) return 'vt100';\n if (/linux/.test(term)) return 'linux_console';\n if (/alacritty/.test(term)) return 'alacritty';\n if (/dopamine/.test(term)) return 'dopamine';\n if (/kitty/.test(term)) return 'kitty';\n if (/ghostty/.test(term)) return 'ghostty';\n if (/^foot/.test(term)) return 'foot';\n if (/^rio/.test(term)) return 'rio';\n return term;\n }\n};\n\nexport const detectFromEnvResult = (\n options: DetectOptions = {}\n): DetectionResult | null => {\n const env = options.env ?? process.env;\n const platform = options.platform ?? process.platform;\n const term = env.TERM ? normalizeIdentifier(env.TERM) : undefined;\n\n if (platform === 'android' && env.TERMUX_VERSION) {\n return { terminal: 'termux', source: 'env', generic: term };\n }\n\n if (!options.preferOuter && term) {\n if (term.startsWith('tmux')) {\n return { terminal: 'tmux', source: 'env', generic: term };\n }\n if (term.startsWith('screen')) {\n return { terminal: 'screen', source: 'env', generic: term };\n }\n }\n\n if (env.KITTY_PID) {\n return { terminal: 'kitty', source: 'env', generic: term };\n }\n\n if (env.GHOSTTY_RESOURCES_DIR) {\n return { terminal: 'ghostty', source: 'env', generic: term };\n }\n\n if (env.ALACRITTY_SOCKET || env.ALACRITTY_LOG) {\n return { terminal: 'alacritty', source: 'env', generic: term };\n }\n\n const termProgram = env.TERM_PROGRAM\n ? normalizeAppName(env.TERM_PROGRAM, platform)\n : undefined;\n\n if (termProgram) {\n return {\n terminal: normalizeTermProgram(termProgram) ?? termProgram,\n source: 'env',\n generic: term\n };\n }\n\n if (\n typeof env.VSCODE_PID !== 'undefined' ||\n (typeof env.TERM_PROGRAM_VERSION !== 'undefined' &&\n /vscode/i.test(env.TERM_PROGRAM_VERSION))\n ) {\n return { terminal: 'vscode', source: 'env', generic: term };\n }\n\n if (term && term !== 'unknown') {\n return {\n terminal: detectFromTerm(term, env, platform),\n source: 'env',\n generic: term\n };\n }\n\n const colorTerm = env.COLORTERM ? normalizeIdentifier(env.COLORTERM) : undefined;\n\n if (colorTerm === 'truecolor' || colorTerm === '24bit' || colorTerm === '24bits') {\n return { terminal: 'truecolor_terminal', source: 'env', generic: term };\n }\n\n if (colorTerm) {\n return { terminal: colorTerm, source: 'env', generic: term };\n }\n\n return null;\n};\n\nexport const detectFromEnv = (options: DetectOptions = {}): string | null => {\n return detectFromEnvResult(options)?.terminal ?? null;\n};\n","import type { DetectOptions, DetectionResult, ExecFileSync, ParentProcessInfo } from './types';\nimport { execFileSync as nodeExecFileSync } from 'node:child_process';\nimport { getColorSupport } from './color';\nimport { normalizeIdentifier, normalizeParentProcessName, SHELL_PROCESS_NAMES } from './normalize';\n\nconst runPs = (run: ExecFileSync, args: string[]): string => {\n return run('ps', args, {\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore']\n }) as string;\n};\n\nexport const getParentProcess = (\n pid: number,\n options: Pick<DetectOptions, 'execFileSync'> = {}\n): ParentProcessInfo | null => {\n const run = options.execFileSync ?? nodeExecFileSync;\n const parentPid = Number.parseInt(\n runPs(run, ['-o', 'ppid=', '-p', String(pid)]).trim(),\n 10\n );\n\n if (!Number.isFinite(parentPid) || parentPid <= 1) {\n return null;\n }\n\n const appName = runPs(run, ['-o', 'comm=', '-p', String(parentPid)]).trim();\n\n if (!appName) {\n return null;\n }\n\n return {\n pid: parentPid,\n appName\n };\n};\n\nconst colorVariant = (\n terminal: string,\n supports256Color: boolean,\n supportsTrueColor: boolean\n): string => {\n return supports256Color || supportsTrueColor ? `${terminal}_256color` : terminal;\n};\n\nconst applyParentColorVariant = (terminal: string, env: NodeJS.ProcessEnv): string => {\n const { supports256Color, supportsTrueColor } = getColorSupport(env);\n\n switch (terminal) {\n case 'eterm':\n case 'konsole':\n case 'rxvt':\n return colorVariant(terminal, supports256Color, supportsTrueColor);\n case 'gnome_terminal':\n case 'terminator':\n return colorVariant('gnome_terminal', supports256Color, supportsTrueColor);\n default:\n return terminal;\n }\n};\n\nexport const detectFromParentProcessResult = (\n options: DetectOptions = {}\n): DetectionResult | null => {\n const env = options.env ?? process.env;\n const platform = options.platform ?? process.platform;\n\n if (platform === 'win32' || env.SSH_CONNECTION) {\n return null;\n }\n\n const seenPids = new Set<number>();\n let pid = options.pid ?? process.pid;\n\n for (let depth = 0; depth < 12; depth += 1) {\n if (seenPids.has(pid)) {\n break;\n }\n\n seenPids.add(pid);\n\n try {\n const parent = getParentProcess(pid, options);\n\n if (!parent) {\n break;\n }\n\n const normalized = normalizeParentProcessName(parent.appName);\n\n if (normalized === 'screen' || normalized === 'tmux') {\n if (!options.preferOuter) {\n return {\n terminal: normalized,\n source: 'parent',\n appName: parent.appName,\n pid: parent.pid\n };\n }\n\n pid = parent.pid;\n continue;\n }\n\n if (normalized) {\n return {\n terminal: applyParentColorVariant(normalized, env),\n source: 'parent',\n appName: parent.appName,\n pid: parent.pid\n };\n }\n\n if (!SHELL_PROCESS_NAMES.has(normalizeIdentifier(parent.appName))) {\n break;\n }\n\n pid = parent.pid;\n } catch {\n break;\n }\n }\n\n return null;\n};\n\nexport const detectFromParentProcess = (options: DetectOptions = {}): string | null => {\n return detectFromParentProcessResult(options)?.terminal ?? null;\n};\n","import type { DetectOptions, DetectionResult } from './types';\n\nexport const detectFromProcessTitleResult = (\n options: Pick<DetectOptions, 'processTitle'> = {}\n): DetectionResult | null => {\n const processTitle = (options.processTitle ?? process.title)?.toLowerCase() ?? '';\n\n if (/^alacritty/.test(processTitle)) {\n return {\n terminal: 'alacritty',\n source: 'process_title'\n };\n }\n\n if (/^kitty/.test(processTitle)) {\n return {\n terminal: 'kitty',\n source: 'process_title'\n };\n }\n\n if (/^wezterm/.test(processTitle)) {\n return {\n terminal: 'wezterm',\n source: 'process_title'\n };\n }\n\n if (/^hyper/.test(processTitle)) {\n return {\n terminal: 'hyper',\n source: 'process_title'\n };\n }\n\n if (/^foot/.test(processTitle)) {\n return {\n terminal: 'foot',\n source: 'process_title'\n };\n }\n\n if (/^rio/.test(processTitle)) {\n return {\n terminal: 'rio',\n source: 'process_title'\n };\n }\n\n if (/^ghostty/.test(processTitle)) {\n return {\n terminal: 'ghostty',\n source: 'process_title'\n };\n }\n\n if (/bash/.test(processTitle)) {\n return {\n terminal: 'bash',\n source: 'process_title'\n };\n }\n\n if (/zsh/.test(processTitle)) {\n return {\n terminal: 'zsh',\n source: 'process_title'\n };\n }\n\n if (/ksh/.test(processTitle)) {\n return {\n terminal: 'ksh',\n source: 'process_title'\n };\n }\n\n if (/fish/.test(processTitle)) {\n return {\n terminal: 'fish',\n source: 'process_title'\n };\n }\n\n if (/csh/.test(processTitle)) {\n return {\n terminal: 'csh',\n source: 'process_title'\n };\n }\n\n if (/tcsh/.test(processTitle)) {\n return {\n terminal: 'tcsh',\n source: 'process_title'\n };\n }\n\n if (/pwsh/.test(processTitle)) {\n return {\n terminal: 'powershell',\n source: 'process_title'\n };\n }\n\n if (/powershell/.test(processTitle)) {\n return {\n terminal: 'powershell',\n source: 'process_title'\n };\n }\n\n if (/cmd/.test(processTitle)) {\n return {\n terminal: 'cmd',\n source: 'process_title'\n };\n }\n\n if (/sh$/.test(processTitle)) {\n return {\n terminal: 'sh',\n source: 'process_title'\n };\n }\n\n if (/^node/.test(processTitle)) {\n return {\n terminal: 'node',\n source: 'process_title'\n };\n }\n\n return null;\n};\n\nexport const detectFromProcessTitle = (): string | null => {\n return detectFromProcessTitleResult()?.terminal ?? null;\n};\n","import type { DetectOptions, DetectionResult, ExecFileSync } from './types';\nimport { execFileSync as nodeExecFileSync } from 'node:child_process';\nimport { normalizeIdentifier } from './normalize';\n\nconst runShellTerm = (run: ExecFileSync): string => {\n return run('sh', ['-c', 'printf %s \"$TERM\"'], {\n encoding: 'utf8',\n timeout: 1000,\n stdio: ['ignore', 'pipe', 'ignore']\n }) as string;\n};\n\nexport const detectFromShellResult = (\n options: DetectOptions = {}\n): DetectionResult | null => {\n const env = options.env ?? process.env;\n const platform = options.platform ?? process.platform;\n\n try {\n if (platform === 'win32') {\n if (env.WT_SESSION) {\n return {\n terminal: 'windows_terminal',\n source: 'shell'\n };\n }\n\n const shell = env.COMSPEC?.toLowerCase() || '';\n if (/powershell/i.test(shell)) {\n return {\n terminal: 'powershell',\n source: 'shell'\n };\n }\n\n if (/pwsh/i.test(shell)) {\n return {\n terminal: 'powershell',\n source: 'shell'\n };\n }\n\n if (/cmd\\.exe/i.test(shell)) {\n return {\n terminal: 'cmd',\n source: 'shell'\n };\n }\n\n if (/wt\\.exe/i.test(shell)) {\n return {\n terminal: 'windows_terminal',\n source: 'shell'\n };\n }\n\n if (/conhost\\.exe/i.test(shell)) {\n return {\n terminal: 'conhost',\n source: 'shell'\n };\n }\n\n return {\n terminal: 'windows_cmd',\n source: 'shell'\n };\n }\n\n const run = options.execFileSync ?? nodeExecFileSync;\n const terminal = normalizeIdentifier(runShellTerm(run));\n\n if (!terminal) {\n return null;\n }\n\n if (!options.preferOuter) {\n if (terminal.startsWith('tmux')) {\n return {\n terminal: 'tmux',\n source: 'shell'\n };\n }\n\n if (terminal.startsWith('screen')) {\n return {\n terminal: 'screen',\n source: 'shell'\n };\n }\n }\n\n return {\n terminal,\n source: 'shell'\n };\n } catch {\n return null;\n }\n};\n\nexport const detectFromShell = (options: DetectOptions = {}): string | null => {\n return detectFromShellResult(options)?.terminal ?? null;\n};\n","import type { DetectOptions, DetectionResult, TerminalDetails } from './types';\nimport { detectFromEnvResult } from './env';\nimport { getColorLevel, getColorSupport } from './color';\nimport { isAmbiguousEnvResult } from './normalize';\nimport { detectFromParentProcessResult } from './parent';\nimport { detectFromProcessTitleResult } from './process-title';\nimport { detectFromShellResult } from './shell';\n\nconst getTerminalResult = (options: DetectOptions): DetectionResult => {\n const envTerminal = detectFromEnvResult(options);\n let result = envTerminal;\n\n if (\n options.resolveAmbiguousFromParent &&\n isAmbiguousEnvResult(envTerminal?.terminal ?? null, options)\n ) {\n result = detectFromParentProcessResult(options) ?? result;\n }\n\n result ??= detectFromShellResult(options);\n result ??= detectFromProcessTitleResult(options);\n\n return (\n result ?? {\n terminal: envTerminal?.terminal ?? 'unknown',\n source: envTerminal ? envTerminal.source : 'unknown',\n generic: envTerminal?.generic\n }\n );\n};\n\nexport const detectTerminalDetails = (options: DetectOptions = {}): TerminalDetails => {\n const env = options.env ?? process.env;\n const stdout = options.stdout ?? process.stdout;\n const isTTY = Boolean(stdout.isTTY);\n const isSSH = Boolean(env.SSH_CONNECTION);\n const term = env.TERM?.trim();\n const termProgram = env.TERM_PROGRAM?.trim();\n const colorTerm = env.COLORTERM?.trim();\n const colorSupport = getColorSupport(env);\n\n if (!isTTY && !options.unpipe) {\n return {\n terminal: 'none',\n source: 'none',\n isTTY,\n isSSH,\n isAmbiguous: false,\n supportsColor: false,\n colorLevel: 0,\n supports256Color: colorSupport.supports256Color,\n supportsTrueColor: colorSupport.supportsTrueColor,\n term,\n termProgram,\n colorTerm,\n generic: 'none'\n };\n }\n\n const result = getTerminalResult(options);\n const colorLevel = getColorLevel(env, result.terminal, colorSupport);\n\n return {\n terminal: result.terminal,\n source: result.source,\n isTTY,\n isSSH,\n isAmbiguous: result.source === 'env' && isAmbiguousEnvResult(result.terminal, options),\n colorLevel,\n supportsColor: colorLevel > 0,\n supports256Color: colorSupport.supports256Color,\n supportsTrueColor: colorSupport.supportsTrueColor,\n term,\n termProgram,\n colorTerm,\n generic: result.generic ?? term?.toLowerCase() ?? 'unknown',\n pid: result.pid,\n appName: result.appName\n };\n};\n\nexport const detectTerminal = (options: DetectOptions = {}): string => {\n return detectTerminalDetails(options).terminal;\n};\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.off('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.off('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// CSI u (fixterms/kitty) protocol sequences for enabling enhanced key reporting\n// Enable with: process.stdout.write(CSI_U_ENABLE)\n// Disable with: process.stdout.write(CSI_U_DISABLE)\nexport const CSI_U_ENABLE = `${kEscape}[>4;1m`; // modifyOtherKeys mode 1\nexport const CSI_U_DISABLE = `${kEscape}[>4;0m`;\nexport const KITTY_ENABLE = `${kEscape}[>1u`; // kitty protocol\nexport const KITTY_DISABLE = `${kEscape}[<u`;\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 CSI u (fixterms/kitty) format:\n ESC [ charcode ; modifier u\n\n - charcode is the unicode codepoint\n - modifier follows same formula as above\n - e.g. Ctrl+Shift+S = ESC [ 115 ; 6 u (115 = 's', 6 = 1 + 1 + 4)\n*/\n\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 // Special key names for CSI u parsing\n const SPECIAL_NAMES = {\n 8: 'backspace', // BS (ctrl+h legacy)\n 9: 'tab',\n 10: 'enter', // LF\n 13: 'return', // CR\n 27: 'escape',\n 32: 'space',\n 127: 'backspace' // DEL\n };\n\n // CSI u (kitty) format: \\x1b[<charcode>;<modifier>u\n // e.g. Ctrl+Shift+S = \\x1b[115;6u or \\x1b[83;5u\n if ((match = /^(\\d+);(\\d+)u$/.exec(cmd))) {\n const charCode = parseInt(match[1], 10);\n modifier = parseInt(match[2], 10) - 1;\n const char = String.fromCharCode(charCode);\n key.name = SPECIAL_NAMES[charCode] || char.toLowerCase();\n key.ctrl = Boolean(modifier & 4);\n key.meta = Boolean(modifier & 10);\n key.shift = Boolean(modifier & 1) || char !== char.toLowerCase();\n key.code = `[${match[1]}u`;\n key.sequence = s;\n\n stream.emit('keypress', undefined, key);\n continue;\n }\n\n // modifyOtherKeys format: \\x1b[27;<modifier>;<charcode>~\n // e.g. Ctrl+Shift+S = \\x1b[27;6;115~\n if ((match = /^27;(\\d+);(\\d+)~$/.exec(cmd))) {\n modifier = parseInt(match[1], 10) - 1;\n const charCode = parseInt(match[2], 10);\n const char = String.fromCharCode(charCode);\n key.name = SPECIAL_NAMES[charCode] || char.toLowerCase();\n key.ctrl = Boolean(modifier & 4);\n key.meta = Boolean(modifier & 10);\n key.shift = Boolean(modifier & 1) || char !== char.toLowerCase();\n key.code = `[27;${match[1]};${match[2]}~`;\n key.sequence = s;\n\n stream.emit('keypress', undefined, key);\n continue;\n }\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