Files
vue-tui/packages/runtime-tests/integration/pty/fixtures/use-input-kitty.tsx
T
Yunfei He f2397bfa00 fix(runtime): kitty key-release delivers input like Ink, not '' (parity) (#91)
useInput had an undocumented guard that blanked `input` to '' on any kitty key-RELEASE
event, so a printable release (and a ctrl+letter release) delivered nothing. Ink has no
release special-case -- it classifies a kitty event purely by isPrintable/ctrl+letter, so
a printable release delivers `text ?? name` and a ctrl+letter release delivers the letter
name (use-input.ts:204-217). Removes the guard to match.

The kept "Ctrl+C exits under kitty" divergence is unaffected: the exit check in emitInput
is already scoped to `eventType !== "release"`, so a Ctrl+C release flows through as
input='c' without spuriously exiting (press still exits).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 16:26:13 +08:00

147 lines
3.7 KiB
TypeScript

import process from "node:process";
import { createApp, useInput, useApp } from "@vue-tui/runtime";
import { defineComponent, onMounted } from "vue";
const KittyInput = defineComponent({
props: {
test: { type: String, default: undefined },
},
setup(props) {
const { exit } = useApp();
onMounted(() => {
process.stdout.write("__READY__");
});
useInput((input, key) => {
if (props.test === "super" && input === "s" && key.super) {
exit();
return;
}
if (props.test === "hyper" && input === "h" && key.hyper) {
exit();
return;
}
if (props.test === "capsLock" && key.capsLock) {
exit();
return;
}
if (props.test === "numLock" && key.numLock) {
exit();
return;
}
if (props.test === "superCtrl" && input === "s" && key.super && key.ctrl) {
exit();
return;
}
// Ctrl+Shift+C must NOT be treated as Ctrl+C exit (it's "copy" in many
// terminals). Even with exitOnCtrlC on, the kitty protocol disambiguates
// it (\x1b[67;6u -> input "C", ctrl+shift), so it must reach the handler.
if (props.test === "ctrlShiftC" && input === "C" && key.ctrl && key.shift) {
process.stdout.write("__CTRL_SHIFT_C__");
exit();
return;
}
if (props.test === "press" && key.eventType === "press") {
exit();
return;
}
if (props.test === "repeat" && key.eventType === "repeat") {
exit();
return;
}
// Ink (use-input.ts:204-217) has no release special-case: a printable
// 'a' release ('a' up, CSI 97;1:3 u) delivers input "a", not "".
// Asserting input === "a" here (not just eventType) is what guards against
// the old undocumented divergence that blanked input on release.
if (props.test === "release" && input === "a" && key.eventType === "release") {
exit();
return;
}
if (props.test === "escape" && key.escape && input === "") {
exit();
return;
}
if (props.test === "backspace" && key.backspace && input === "") {
exit();
return;
}
if (props.test === "delete" && key.delete && input === "") {
exit();
return;
}
if (props.test === "capslock-empty" && input === "") {
exit();
return;
}
if (props.test === "f13-empty" && input === "") {
exit();
return;
}
if (props.test === "printscreen-empty" && input === "") {
exit();
return;
}
if (props.test === "space" && input === " ") {
exit();
return;
}
if (props.test === "return" && input === "\r") {
exit();
return;
}
if (props.test === "ctrlLetter" && input === "a" && key.ctrl) {
exit();
return;
}
if (props.test === "queryResponse") {
throw new Error("Query response should not reach handler");
}
if (props.test === "queryThenKey") {
if (input === "a") {
exit();
return;
}
throw new Error(`queryThenKey: expected input="a", got input="${input}"`);
}
throw new Error(`Unexpected input for test "${props.test}": input="${input}"`);
});
return () => null;
},
});
const testName = process.argv[2];
if (testName === "kittyCtrlCExit" || testName === "ctrlShiftC") {
const app = createApp(KittyInput, { test: testName });
app.mount({ exitOnCtrlC: true });
await app.waitUntilExit();
console.log("exited");
} else {
const app = createApp(KittyInput, { test: testName });
app.mount({ exitOnCtrlC: false });
await app.waitUntilExit();
console.log("exited");
}