// Terminal key encoding: control characters, meta prefixes, and cursor escape // sequences. Table-driven and easy to get subtly wrong, so it lives apart from // the DOM and is tested directly. export function toCtrl(ch: string): string { if (ch.length !== 1) return ch; const upper = ch.toUpperCase().codePointAt(0) ?? 0; if (upper >= 0x40 && upper <= 0x5f) return String.fromCodePoint(upper & 0x1f); if (ch === " ") return "\x00"; if (ch === "?") return "\x7f"; return ch; } /** Apply pending modifiers to a single typed character: Ctrl → control code, * Alt → ESC prefix (xterm meta). Both compose (Ctrl+Alt = ESC + control). */ export function applyModsToChar(ch: string, ctrl: boolean, alt: boolean): string { let out = ctrl ? toCtrl(ch) : ch; if (alt) out = "\x1b" + out; return out; } /** Cursor/nav-key sequence, promoted to the modified CSI form when Ctrl/Alt are * pending — `\x1b[1;`, n = 1 + alt(2) + ctrl(4) (xterm convention). */ export function cursorSeq(final: string, ctrl: boolean, alt: boolean): string { if (!ctrl && !alt) return `\x1b[${final}`; return `\x1b[1;${modParam(ctrl, alt)}${final}`; } /** Modifier parameter of the xterm CSI forms: n = 1 + alt(2) + ctrl(4). */ function modParam(ctrl: boolean, alt: boolean): number { return 1 + (alt ? 2 : 0) + (ctrl ? 4 : 0); } /** Sequence for the `~`-terminated navigation keys (PgUp `5`, PgDn `6`), which * carry modifiers as `\x1b[;~` rather than the cursor keys' `1;` * form. */ export function tildeSeq(code: number, ctrl: boolean, alt: boolean): string { if (!ctrl && !alt) return `\x1b[${code}~`; return `\x1b[${code};${modParam(ctrl, alt)}~`; }