From 838aaaf806fdd955f3fb4ebbdbe353f91be55d89 Mon Sep 17 00:00:00 2001 From: Yunfei He Date: Wed, 27 May 2026 14:50:07 +0800 Subject: [PATCH] feat: wire kitty keyboard controller into mount/teardown and add exports Co-Authored-By: Claude Sonnet 4.6 --- packages/runtime/src/index.ts | 1 + packages/runtime/src/internal.ts | 8 ++++++++ packages/runtime/src/render.ts | 18 ++++++++++++++++++ 3 files changed, 27 insertions(+) diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index 04bb7e4..3cac8be 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -32,3 +32,4 @@ export { } from "./composables/useBoxMetrics.ts"; export { renderScreenReaderOutput, type ScreenReaderOptions } from "./paint/screen-reader.ts"; export type { DevState, DevErrorInfo } from "./hmr.ts"; +export { kittyFlags, type KittyKeyboardOptions, type KittyFlagName } from "./io/kitty-keyboard.ts"; diff --git a/packages/runtime/src/internal.ts b/packages/runtime/src/internal.ts index a5c267e..b1bf21e 100644 --- a/packages/runtime/src/internal.ts +++ b/packages/runtime/src/internal.ts @@ -11,3 +11,11 @@ export { } from "./host/nodes.ts"; export { renderScreenReaderOutput, type ScreenReaderOptions } from "./paint/screen-reader.ts"; export type { AppContext } from "./context.ts"; +export { + createKittyKeyboardController, + matchKittyQueryResponse, + hasCompleteKittyQueryResponse, + stripKittyQueryResponsesAndTrailingPartial, + resolveFlags, + type KittyKeyboardController, +} from "./io/kitty-keyboard.ts"; diff --git a/packages/runtime/src/render.ts b/packages/runtime/src/render.ts index 8c21ba6..bb1506d 100644 --- a/packages/runtime/src/render.ts +++ b/packages/runtime/src/render.ts @@ -15,6 +15,7 @@ import isInCi from "is-in-ci"; import patchConsoleFn from "patch-console"; import ansiEscapes from "ansi-escapes"; import { createInputParser, type InputEvent } from "./io/input-parser.ts"; +import { createKittyKeyboardController, type KittyKeyboardOptions } from "./io/kitty-keyboard.ts"; import { createRoot, type TuiRoot, type TuiNode } from "./host/nodes.ts"; import { attachYoga, detachYoga } from "./host/yoga.ts"; import { buildNodeOps } from "./host/node-ops.ts"; @@ -101,6 +102,14 @@ export interface MountOptions { * @default false */ alternateScreen?: boolean; + /** + * Configure kitty keyboard protocol support for enhanced keyboard input. + * Enables additional modifiers (super, hyper, capsLock, numLock) and + * disambiguated key events in terminals that support the protocol. + * + * @see https://sw.kovidgoyal.net/kitty/keyboard-protocol/ + */ + kittyKeyboard?: KittyKeyboardOptions; } export interface TuiApp extends Omit, "mount"> { @@ -162,6 +171,7 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp let mountedScheduler: ReturnType | null = null; let mountedCommit: (() => void) | null = null; let mountedAlternateScreen = false; + let mountedKittyController: ReturnType | null = null; // The renderer's onCommit closure is wired at createApp time but only does // real work after mount swaps in scheduler.schedule. One renderer per app @@ -231,6 +241,10 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp } catch { // Vue's unmount may throw on double-unmount; swallow for idempotency. } + if (mountedKittyController) { + mountedKittyController.dispose(); + mountedKittyController = null; + } if (!mountedDebug && !mountedInteractive && mountedAppContext) { // Non-interactive: write the deferred last frame at unmount (matching Ink). const lastFrame = mountedGetLastOutput?.() ?? ""; @@ -425,6 +439,10 @@ export function createApp(root: Component, rootProps?: RootProps | null): TuiApp }); mountedStdinController = stdinController; + const kittyController = createKittyKeyboardController(stdin, stdout); + kittyController.init(options.kittyKeyboard, interactive); + mountedKittyController = kittyController; + const tuiRoot = createRoot(appContext); attachYoga(tuiRoot); tuiRoot.yoga.setWidth(stdout.columns ?? 80);