export interface TerminalKeyInput { key: string; ctrl?: boolean; alt?: boolean; shift?: boolean; meta?: boolean; } function modifierParam(input: TerminalKeyInput): number { let value = 1; if (input.shift) value += 1; if (input.alt || input.meta) value += 2; if (input.ctrl) value += 4; return value; } function applyAltLikePrefix(sequence: string, input: TerminalKeyInput): string { return input.alt || input.meta ? `\x1b${sequence}` : sequence; } function encodePrintableKey(input: TerminalKeyInput): string { const raw = input.key; const char = input.shift ? raw.toUpperCase() : raw; if (input.ctrl) { const upper = char.toUpperCase(); if (upper.length === 1 && upper >= "A" && upper <= "Z") { return applyAltLikePrefix(String.fromCharCode(upper.charCodeAt(0) - 64), input); } switch (char) { case " ": case "@": case "2": return applyAltLikePrefix("\x00", input); case "[": case "3": return applyAltLikePrefix("\x1b", input); case "\\": case "4": return applyAltLikePrefix("\x1c", input); case "]": case "5": return applyAltLikePrefix("\x1d", input); case "^": case "6": return applyAltLikePrefix("\x1e", input); case "_": case "/": case "7": return applyAltLikePrefix("\x1f", input); case "8": case "?": return applyAltLikePrefix("\x7f", input); default: break; } if (char.length === 1) { const code = char.charCodeAt(0) & 0x1f; return applyAltLikePrefix(String.fromCharCode(code), input); } } return applyAltLikePrefix(char, input); } function csiWithModifier(finalByte: string, input: TerminalKeyInput): string { const mod = modifierParam(input); return mod === 1 ? `\x1b[${finalByte}` : `\x1b[1;${mod}${finalByte}`; } function csiTilde(base: number, input: TerminalKeyInput): string { const mod = modifierParam(input); return mod === 1 ? `\x1b[${base}~` : `\x1b[${base};${mod}~`; } export function encodeTerminalKeyInput(input: TerminalKeyInput): string { const key = input.key; if (!key) { return ""; } if (key.length === 1) { return encodePrintableKey(input); } switch (key) { case "Enter": return applyAltLikePrefix("\r", input); case "Tab": if (input.shift && !input.ctrl && !input.alt && !input.meta) { return "\x1b[Z"; } return applyAltLikePrefix("\t", input); case "Backspace": return applyAltLikePrefix("\x7f", input); case "Escape": return "\x1b"; case "ArrowUp": return csiWithModifier("A", input); case "ArrowDown": return csiWithModifier("B", input); case "ArrowRight": return csiWithModifier("C", input); case "ArrowLeft": return csiWithModifier("D", input); case "Home": return csiWithModifier("H", input); case "End": return csiWithModifier("F", input); case "Insert": return csiTilde(2, input); case "Delete": return csiTilde(3, input); case "PageUp": return csiTilde(5, input); case "PageDown": return csiTilde(6, input); case "F1": return modifierParam(input) === 1 ? "\x1bOP" : csiWithModifier("P", input); case "F2": return modifierParam(input) === 1 ? "\x1bOQ" : csiWithModifier("Q", input); case "F3": return modifierParam(input) === 1 ? "\x1bOR" : csiWithModifier("R", input); case "F4": return modifierParam(input) === 1 ? "\x1bOS" : csiWithModifier("S", input); case "F5": return csiTilde(15, input); case "F6": return csiTilde(17, input); case "F7": return csiTilde(18, input); case "F8": return csiTilde(19, input); case "F9": return csiTilde(20, input); case "F10": return csiTilde(21, input); case "F11": return csiTilde(23, input); case "F12": return csiTilde(24, input); default: return ""; } }