import { decodeKittyPrintable, isKeyRelease, matchesKey } from "@earendil-works/pi-tui"; /** * Single owner of how a pi TUI key event reaches this extension. * * Terminals that negotiate the Kitty keyboard protocol — Ghostty among them — send CSI-u * sequences instead of the legacy bytes, so `data === "\r"` silently stops matching Enter and * an overlay appears frozen. `matchesKey` accepts both encodings regardless of the negotiated * protocol, so components must never compare raw bytes themselves. */ const ESCAPE_PREFIX = "\u001b"; const DELETE = "\u007f"; export function isEscape(data: string): boolean { return matchesKey(data, "escape"); } export function isEnter(data: string): boolean { return matchesKey(data, "enter"); } export function isUp(data: string): boolean { return matchesKey(data, "up"); } export function isDown(data: string): boolean { return matchesKey(data, "down"); } export function isLeft(data: string): boolean { return matchesKey(data, "left"); } export function isRight(data: string): boolean { return matchesKey(data, "right"); } export function isTab(data: string): boolean { return matchesKey(data, "tab"); } export function isBackTab(data: string): boolean { return matchesKey(data, "shift+tab"); } export function isBackspace(data: string): boolean { return matchesKey(data, "backspace"); } /** * The literal text a key event carries, or undefined when it is a control or navigation key. * Under the Kitty protocol an ordinary character arrives as a CSI-u sequence, so a plain * printable-range test on the raw bytes is not sufficient. */ export function printableText(data: string): string | undefined { const decoded = decodeKittyPrintable(data); if (decoded !== undefined) return decoded.length > 0 ? decoded : undefined; if (data.length === 0 || data.startsWith(ESCAPE_PREFIX)) return undefined; if (data.includes(DELETE)) return undefined; return [...data].every((character) => character >= " ") ? data : undefined; } /** True when the event is exactly the given printable character, in either encoding. */ export function isCharacter(data: string, character: string): boolean { return printableText(data) === character; } /** * True for a Kitty key-release event. `matchesKey` deliberately ignores the event type, so a * raw terminal-input hook sees both the press and the release of one keystroke and would act * twice. The TUI filters releases before reaching a focused component, but `onTerminalInput` * receives them unfiltered. Key repeat is a real repeat and must not be filtered. */ export function isReleaseEvent(data: string): boolean { return isKeyRelease(data); }