import test from "node:test"; import assert from "node:assert/strict"; import { buildKittyKeyboardModeQueryResponse, createKittyKeyboardModeState, encodeKittyControlKey, popKittyKeyboardModeFlags, pushKittyKeyboardModeFlags, setKittyKeyboardAlternateScreenActive, setKittyKeyboardModeFlags, } from "./kittyKeyboardProtocol"; import { installKittyKeyboardProtocolHandlers, readKittyKeyboardCsiParam, type KittyKeyboardCsiParams, } from "./kittyKeyboardRuntime"; type CsiHandlerId = { prefix?: string; intermediates?: string; final: string; }; type CsiHandler = (params: KittyKeyboardCsiParams) => boolean; const csiKey = (id: CsiHandlerId): string => ( `${id.prefix ?? ""}|${id.intermediates ?? ""}|${id.final}` ); const createFakeCsiParser = () => { const handlers = new Map(); return { parser: { registerCsiHandler(id: CsiHandlerId, callback: CsiHandler) { const key = csiKey(id); const list = handlers.get(key) ?? []; list.push(callback); handlers.set(key, list); return { dispose: () => { const current = handlers.get(key); if (!current) return; const index = current.indexOf(callback); if (index >= 0) current.splice(index, 1); if (current.length === 0) handlers.delete(key); }, }; }, }, dispatch(id: CsiHandlerId, params: KittyKeyboardCsiParams = []) { const list = handlers.get(csiKey(id)); assert.ok(list?.length, `missing CSI handler for ${csiKey(id)}`); for (let index = list.length - 1; index >= 0; index -= 1) { if (list[index](params)) return true; } return false; }, hasHandler(id: CsiHandlerId) { return handlers.has(csiKey(id)); }, }; }; test("kitty keyboard query reports the active screen flags", () => { const state = createKittyKeyboardModeState(); setKittyKeyboardModeFlags(state, 1, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?1u"); setKittyKeyboardAlternateScreenActive(state, true); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?0u"); }); test("kitty keyboard set mode respects replace, union, and subtract semantics", () => { const state = createKittyKeyboardModeState(); setKittyKeyboardModeFlags(state, 1, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?1u"); setKittyKeyboardModeFlags(state, 8, 2); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?9u"); setKittyKeyboardModeFlags(state, 8, 3); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?1u"); }); test("kitty keyboard mode ignores unsupported progressive enhancement flags", () => { const state = createKittyKeyboardModeState(); setKittyKeyboardModeFlags(state, 1 | 2 | 4 | 8 | 16, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?9u"); setKittyKeyboardModeFlags(state, 2 | 4 | 16, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?0u"); }); test("kitty keyboard mode stacks are independent for main and alternate screen", () => { const state = createKittyKeyboardModeState(); setKittyKeyboardModeFlags(state, 1, 1); pushKittyKeyboardModeFlags(state, 0); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?0u"); setKittyKeyboardAlternateScreenActive(state, true); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?0u"); setKittyKeyboardModeFlags(state, 1, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?1u"); popKittyKeyboardModeFlags(state, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?0u"); setKittyKeyboardAlternateScreenActive(state, false); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?0u"); popKittyKeyboardModeFlags(state, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?1u"); }); test("kitty control key encoding keeps bare enter legacy but disambiguates modified enter", () => { const state = createKittyKeyboardModeState(); setKittyKeyboardModeFlags(state, 1, 1); assert.equal( encodeKittyControlKey(state, { key: "Enter" }), null, ); assert.equal( encodeKittyControlKey(state, { key: "Enter", shiftKey: true }), "\u001b[13;2u", ); assert.equal( encodeKittyControlKey(state, { key: "Escape" }), "\u001b[27u", ); assert.equal( encodeKittyControlKey(state, { key: "Backspace", ctrlKey: true, altKey: true }), "\u001b[127;7u", ); }); test("kitty keyboard CSI param reader applies fallbacks for odd params", () => { assert.equal(readKittyKeyboardCsiParam([], 0, 7), 7); assert.equal(readKittyKeyboardCsiParam([0], 0, 7), 7); assert.equal(readKittyKeyboardCsiParam([-1], 0, 7), 7); assert.equal(readKittyKeyboardCsiParam([[8, 9]], 0, 7), 8); assert.equal(readKittyKeyboardCsiParam([1], 1, 7), 7); }); test("kitty keyboard CSI handlers negotiate mode and enable Shift+Enter encoding", () => { const state = createKittyKeyboardModeState(); const fake = createFakeCsiParser(); const replies: string[] = []; const disposable = installKittyKeyboardProtocolHandlers( fake.parser, state, (payload) => replies.push(payload), ); assert.equal(fake.dispatch({ prefix: "?", final: "u" }), true); assert.deepEqual(replies, ["\u001b[?0u"]); assert.equal(fake.dispatch({ prefix: "=", final: "u" }, [1]), true); assert.equal( encodeKittyControlKey(state, { key: "Enter", shiftKey: true }), "\u001b[13;2u", ); assert.equal(encodeKittyControlKey(state, { key: "Enter" }), null); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?1u"); disposable.dispose(); assert.equal(fake.hasHandler({ prefix: "?", final: "u" }), false); assert.equal(fake.hasHandler({ prefix: "=", final: "u" }), false); assert.equal(fake.hasHandler({ prefix: ">", final: "u" }), false); assert.equal(fake.hasHandler({ prefix: "<", final: "u" }), false); assert.equal(fake.hasHandler({ prefix: "?", final: "h" }), false); assert.equal(fake.hasHandler({ prefix: "?", final: "l" }), false); }); test("kitty keyboard CSI handlers handle invalid modes and stack defaults", () => { const state = createKittyKeyboardModeState(); const fake = createFakeCsiParser(); const replies: string[] = []; installKittyKeyboardProtocolHandlers(fake.parser, state, (payload) => replies.push(payload)); fake.dispatch({ prefix: "=", final: "u" }, [8]); fake.dispatch({ prefix: "=", final: "u" }, [1, 99]); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?1u"); fake.dispatch({ prefix: "=", final: "u" }, [8, 2]); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?9u"); fake.dispatch({ prefix: "=", final: "u" }, [8, 3]); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?1u"); fake.dispatch({ prefix: ">", final: "u" }, [1 | 2 | 4 | 8 | 16]); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?9u"); fake.dispatch({ prefix: "<", final: "u" }, [0]); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?1u"); }); test("kitty keyboard CSI handlers keep main and alternate screen state separate", () => { const state = createKittyKeyboardModeState(); const fake = createFakeCsiParser(); const replies: string[] = []; installKittyKeyboardProtocolHandlers(fake.parser, state, (payload) => replies.push(payload)); fake.dispatch({ prefix: "=", final: "u" }, [1]); assert.equal(fake.dispatch({ prefix: "?", final: "h" }, [[1049]]), false); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?0u"); fake.dispatch({ prefix: "=", final: "u" }, [8]); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?8u"); assert.equal(fake.dispatch({ prefix: "?", final: "l" }, [1049]), false); fake.dispatch({ prefix: "?", final: "u" }); assert.equal(replies.at(-1), "\u001b[?1u"); }); test("kitty report-all mode enables the supported modified control key subset", () => { const state = createKittyKeyboardModeState(); setKittyKeyboardModeFlags(state, 8, 1); assert.equal(buildKittyKeyboardModeQueryResponse(state), "\u001b[?8u"); assert.equal( encodeKittyControlKey(state, { key: "Enter", shiftKey: true }), "\u001b[13;2u", ); assert.equal( encodeKittyControlKey(state, { key: "Tab", shiftKey: true }), "\u001b[9;2u", ); assert.equal( encodeKittyControlKey(state, { key: "Enter" }), null, ); });