键盘输入

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 包含解析后的按键标识、生成的输入序列和原始终端输入:

属性类型说明
namestring规范化的按键标识,如 "a""space""return""escape""f1"
sequencestring解码后的按键输入序列/文本。对于可打印键,这是文本,如 "a"" "
rawstring此按键事件的精确终端输入序列,解码为字符串
source"raw" | "kitty"产生该事件的解析器路径
ctrlboolean是否按住 Ctrl
shiftboolean是否按住 Shift
metaboolean是否按住 Alt/Meta
optionboolean是否按住 Option(macOS Alt/Option 路径)
superboolean?是否按住 Super/Cmd/Windows(当报告时)
hyperboolean?是否按住 Hyper(当报告时)
eventType"press" | "repeat" | "release"按键事件类型。Kitty 重复事件作为 "press" 发出,并带有 repeated: true
repeatedboolean?Kitty 重复事件为 true
numberboolean当解析的按键是来自遗留输入的数字时为 true
codestring?已识别的函数风格转义序列的终端按键代码
capsLockboolean?Kitty Caps Lock 修饰键状态(当报告时)
numLockboolean?Kitty Num Lock 修饰键状态(当报告时)
baseCodenumber?Kitty 基础布局码位,用于布局稳定的快捷键匹配

sequence 并不总是原始终端字节。例如,Kitty Ctrl+Space 以原始 "\x1b[32;5u" 到达,但发出 name: "space"ctrl: truesequence: " "。当你需要精确的终端序列时,请使用 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 选项的可编辑组件(InputRenderableTextareaRenderableSelectRenderable 等)会将这些默认值与你自己的映射合并:

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 })
选项默认值标志说明
disambiguatetrueDISAMBIGUATE_ESCAPE_CODES修复 ESC 时序和 alt+键歧义;将 ctrl+c 报告为正确的按键事件
alternateKeystrueREPORT_ALTERNATE_KEYS报告移位/基础布局键,用于跨布局快捷键
eventsfalseREPORT_EVENT_TYPES报告按下、重复和释放事件
allKeysAsEscapesfalseREPORT_ALL_KEYS_AS_ESCAPE_CODES将每个键编码为转义序列
reportTextfalseREPORT_ASSOCIATED_TEXT包含按键生成的文本

events: truerenderer.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)