键盘输入
OpenTUI 解析终端输入并提供结构化的按键事件。renderer.keyInput EventEmitter 发出 keypress 事件以及带有可选元数据的原始 paste 事件。
基本按键处理
import { createCliRenderer, type KeyEvent } from "@opentui/core"
const renderer = await createCliRenderer()
const keyHandler = renderer.keyInput
keyHandler.on("keypress", (key: KeyEvent) => {
console.log("Key name:", key.name)
console.log("Input sequence:", key.sequence)
console.log("Raw input:", key.raw)
console.log("Ctrl pressed:", key.ctrl)
console.log("Shift pressed:", key.shift)
console.log("Alt pressed:", key.meta)
console.log("Option pressed:", key.option)
})
KeyEvent 属性
每个 KeyEvent 包含解析后的按键标识、生成的输入序列和原始终端输入:
| 属性 | 类型 | 说明 |
|---|---|---|
name | string | 规范化的按键标识,如 "a"、"space"、"return"、"escape" 或 "f1" |
sequence | string | 解码后的按键输入序列/文本。对于可打印键,这是文本,如 "a" 或 " " |
raw | string | 此按键事件的精确终端输入序列,解码为字符串 |
source | "raw" | "kitty" | 产生该事件的解析器路径 |
ctrl | boolean | 是否按住 Ctrl |
shift | boolean | 是否按住 Shift |
meta | boolean | 是否按住 Alt/Meta |
option | boolean | 是否按住 Option(macOS Alt/Option 路径) |
super | boolean? | 是否按住 Super/Cmd/Windows(当报告时) |
hyper | boolean? | 是否按住 Hyper(当报告时) |
eventType | "press" | "repeat" | "release" | 按键事件类型。Kitty 重复事件作为 "press" 发出,并带有 repeated: true |
repeated | boolean? | Kitty 重复事件为 true |
number | boolean | 当解析的按键是来自遗留输入的数字时为 true |
code | string? | 已识别的函数风格转义序列的终端按键代码 |
capsLock | boolean? | Kitty Caps Lock 修饰键状态(当报告时) |
numLock | boolean? | Kitty Num Lock 修饰键状态(当报告时) |
baseCode | number? | Kitty 基础布局码位,用于布局稳定的快捷键匹配 |
sequence 并不总是原始终端字节。例如,Kitty Ctrl+Space 以原始 "\x1b[32;5u" 到达,但发出 name: "space"、ctrl: true 和 sequence: " "。当你需要精确的终端序列时,请使用 raw。
按键绑定别名
一些核心 Renderable 和内置控制台支持已配置按键绑定的别名。这些别名在构建组件的按键绑定查找映射时应用;它们不会重写发出的 KeyEvent.name 值。
默认的按键绑定别名:
// enter -> return
// esc -> escape
//
// 数字键盘键别名到主键盘等效键:
// kp0..kp9 -> "0".."9"
// kpenter -> enter
// kpleft/kpright/kpup/kpdown -> 方向键
// kphome/kpend/kppageup/kppagedown/kpinsert/kpdelete -> 导航键
// kpdecimal/kpdivide/kpmultiply/kpminus/kpplus/kpequal/kpseparator -> 符号键
别名是从已配置绑定名称到额外查找名称的一步映射。例如,配置为 { name: "enter", action: "submit" } 的组件绑定也会匹配 name 为 "return" 的 KeyEvent。这种别名是组件特定的;直接的 renderer.keyInput 处理器接收解析器的规范名称,应与 "return"、"escape" 和 "space" 等名称进行比较。
接受 keyAliasMap 选项的可编辑组件(InputRenderable、TextareaRenderable、SelectRenderable 等)会将这些默认值与你自己的映射合并:
import { InputRenderable } from "@opentui/core"
const input = new InputRenderable(renderer, {
id: "field",
keyAliasMap: {
// 将数字键盘小数键视为向前删除
kpdecimal: "delete",
},
})
常用按键模式
单个按键
keyHandler.on("keypress", (key: KeyEvent) => {
if (key.name === "escape") {
console.log("Escape pressed!")
}
if (key.name === "return") {
console.log("Enter pressed!")
}
if (key.name === "space") {
console.log("Space pressed!")
}
})
修饰键组合
keyHandler.on("keypress", (key: KeyEvent) => {
// Ctrl+C
if (key.ctrl && key.name === "c") {
console.log("Ctrl+C pressed!")
}
// Ctrl+S
if (key.ctrl && key.name === "s") {
console.log("Save shortcut!")
}
// Shift+F1
if (key.shift && key.name === "f1") {
console.log("Shift+F1 pressed!")
}
// Alt+Enter
if (key.meta && key.name === "return") {
console.log("Alt+Enter pressed!")
}
})
功能键
keyHandler.on("keypress", (key: KeyEvent) => {
// F1-F12
if (key.name === "f1") {
showHelp()
}
if (key.name === "f5") {
refresh()
}
})
方向键
keyHandler.on("keypress", (key: KeyEvent) => {
switch (key.name) {
case "up":
moveCursorUp()
break
case "down":
moveCursorDown()
break
case "left":
moveCursorLeft()
break
case "right":
moveCursorRight()
break
}
})
粘贴事件
将粘贴的字节与单个按键分开处理。当你期望文本时,显式解码它们:
import { type PasteEvent } from "@opentui/core"
const textDecoder = new TextDecoder()
keyHandler.on("paste", (event: PasteEvent) => {
console.log("Pasted bytes:", event.bytes)
console.log("Decoded text:", textDecoder.decode(event.bytes))
console.log("Metadata:", event.metadata)
})
Ctrl+C 退出
配置渲染器在 Ctrl+C 时自动退出:
const renderer = await createCliRenderer({
exitOnCtrlC: true, // 默认行为
})
// 或手动处理
const renderer = await createCliRenderer({
exitOnCtrlC: false,
})
renderer.keyInput.on("keypress", (key: KeyEvent) => {
if (key.ctrl && key.name === "c") {
// 关闭前的自定义清理
cleanup()
renderer.destroy()
}
})
Kitty 键盘协议
当终端支持时,OpenTUI 会启用 kitty 键盘协议。该协议提供比遗留输入编码更可靠的按键名称和修饰键状态,包括释放事件、消除歧义的转义、alt+键序列以及用于跨布局快捷键的替代键。使用 useKittyKeyboard 选项进行配置:
const renderer = await createCliRenderer({
useKittyKeyboard: {
disambiguate: true,
alternateKeys: true,
events: true, // press/repeat/release
allKeysAsEscapes: false,
reportText: false,
},
})
// 完全禁用:
await createCliRenderer({ useKittyKeyboard: null })
| 选项 | 默认值 | 标志 | 说明 |
|---|---|---|---|
disambiguate | true | DISAMBIGUATE_ESCAPE_CODES | 修复 ESC 时序和 alt+键歧义;将 ctrl+c 报告为正确的按键事件 |
alternateKeys | true | REPORT_ALTERNATE_KEYS | 报告移位/基础布局键,用于跨布局快捷键 |
events | false | REPORT_EVENT_TYPES | 报告按下、重复和释放事件 |
allKeysAsEscapes | false | REPORT_ALL_KEYS_AS_ESCAPE_CODES | 将每个键编码为转义序列 |
reportText | false | REPORT_ASSOCIATED_TEXT | 包含按键生成的文本 |
events: true 在 renderer.keyInput 上启用 keyrelease 事件,以及 KeyEvent 上的 eventType。
useKittyKeyboard: {} 应用默认值(disambiguate + alternateKeys)。传递 null 会完全禁用该协议。
Kitty 转义序列在可能的情况下仍发出与遗留输入相同的规范按键名称。例如,Kitty Ctrl+Space 发出 name: "space" 和 ctrl: true;输入的文本作为 sequence: " " 可用,原始转义序列作为 raw 可用。
焦点和按键路由
聚焦组件以接收键盘输入。OpenTUI 将事件路由到聚焦的组件:
import { InputRenderable } from "@opentui/core"
const input = new InputRenderable(renderer, {
id: "my-input",
placeholder: "Type here...",
})
// 聚焦 input 以接收按键事件
input.focus()
// 或使用 constructs
import { Input } from "@opentui/core"
const inputNode = Input({ placeholder: "Type here..." })
inputNode.focus() // 排队等待实例化时执行
renderer.root.add(inputNode)