codev / docs /ink-ui /overview.md
chenbhao's picture
chore: rename VersperClaw to Codev, update org to chenbhao
96f34e3
|
Raw
History Blame Contribute Delete
25.4 kB
# Ink 终端 UI 框架
## 概述
Codev 的 Ink 是基于 [vadimdemedes/ink](https://github.com/vadimdemedes/ink) v6 的深度定制分叉(fork)。Ink 使用 React 组件模型渲染终端用户界面。它将 React 的虚拟 DOM 映射到终端字符网格,支持 Yoga Flexbox 布局、样式继承、事件处理、文本选择、搜索高亮等丰富的交互能力。
> 与上游相比,Codev 的 Ink 新增/修改了以下核心能力:自定义 DOM 节点树、压缩屏幕缓冲区(Int32Array)、基于 Yoga WASM 的布局引擎、Kitty 键盘协议支持、SGR 鼠标跟踪、文本选择模式、搜索高亮、双向文本处理、scrollable 容器、声明式光标位置等。
---
## 核心架构
Ink 的渲染管线分为以下几个层次:
```
React 组件树
React Reconciler (自定义 Fiber 协调器)
DOM 节点树 (ink-root, ink-box, ink-text, ...)
Yoga 布局引擎 (WASM Flexbox)
Renderer → Output (screen buffer)
LogUpdate (差异比较)
Patch[] → ANSI 序列 → stdout
```
### Ink 主类
文件:`src/ink/ink.tsx`
`Ink` 类(约 1723 行)管理整个渲染生命周期:
- **构造函数**:设置 `LogUpdate`、节流渲染调度器(`scheduleRender`,使用 `FRAME_INTERVAL_MS=16ms`)、`FocusManager`、Yoga `onComputeLayout` 回调、React reconciler 容器
- **`onRender()`**:核心帧循环,每个渲染周期执行以下步骤:
1. 调用 `createRenderer` 执行 DOM → Yoga 布局 → 屏幕缓冲区的渲染
2. 处理选择滚动追踪
3. 应用选择/搜索覆盖层
4. 全损坏回退(full-damage backstop)
5. 计算差异(diff)
6. 缓冲池交换
7. 补丁优化与序列化
8. 光标定位(原生光标或声明式光标)
9. 写入 stdout
- **选择管理**`startSelection`, `extendSelection`, `wordSelect`, `lineSelect`, `dragScrollCapture`, `shiftSelection`
- **stdin 挂起/恢复**:用于外部编辑器集成
- **控制台/stderr 补丁**:拦截 console.log 等输出重定向
- **drainStdin**:清理时清空 stdin 缓冲区
### React Reconciler(协调器)
文件:`src/ink/reconciler.ts`
使用 `react-reconciler` 包创建自定义 Fiber 协调器,将 React 组件映射到 Ink 的 DOM 节点。
**关键宿主配置**
| 钩子 | 说明 |
|------|------|
| `createInstance` | 创建 `DOMElement``ink-box`, `ink-text` 等),在 `<Text>` 内部嵌套时将 `ink-text` 转换为 `ink-virtual-text` |
| `createTextInstance` | 创建 `TextNode`,要求必须在 `<Text>` 内使用 |
| `commitUpdate` | React 19 使用新旧 props 直接比较,调用 `setStyle`, `setAttribute`, `setTextStyles`, `setEventHandler` |
| `commitTextUpdate` | 更新文本节点 |
| `appendChild` / `insertBefore` / `removeChild` | DOM 树操作,同步更新 Yoga 节点树 |
| `resetAfterCommit` | 提交后触发 `onComputeLayout`(Yoga 布局)和 `onRender`(渲染帧) |
| `hideInstance` / `unhideInstance` | 设置/取消 Yoga `display: none` |
**调试工具**
- `getOwnerChain()`:从 React Fiber `_debugOwner` 提取组件名链,用于识别重绘来源
- `CLAUDE_CODE_DEBUG_REPAINTS` 环境变量:启用后记录组件树归属
- `CLAUDE_CODE_COMMIT_LOG`:记录提交/渲染性能日志
### DOM 节点树
文件:`src/ink/dom.ts`
自定义 DOM 树,与浏览器 DOM 不同,专为终端渲染优化。
**节点类型**
| 节点名 | 说明 |
|--------|------|
| `ink-root` | 根节点,持有 `FocusManager` |
| `ink-box` | 容器(类似 `<div>`),有 Yoga 节点 |
| `ink-text` | 文本容器,有测量函数 |
| `ink-virtual-text` | `<Text>` 内嵌套的文本,无 Yoga 节点 |
| `ink-link` | 超链接,无 Yoga 节点 |
| `ink-progress` | 进度条,无 Yoga 节点 |
| `ink-raw-ansi` | 预渲染 ANSI 字符串,固定尺寸测量 |
| `#text` | 文本叶子节点 |
**DOM 操作**
- `createNode()` / `createTextNode()` —— 创建节点
- `appendChildNode()` / `insertBeforeNode()` / `removeChildNode()` —— DOM 树操作,同步更新 Yoga 节点
- `setStyle()` / `setAttribute()` / `setTextStyles()` —— 属性更新,含脏检测(shallow equal 跳过未变更的渲染)
- `markDirty()` —— 标记节点及其所有祖先为脏
- `scheduleRenderFrom()` —— 从指定节点触发渲染(用于非 React 触发的 DOM 变更)
**脏标记(Dirty Flag)**:每个 `DOMElement``dirty` 属性(boolean),`markDirty()` 从当前节点向上遍历直到根节点。渲染完成后清除。这允许增量重绘 —— 只有脏子树被重新布局和渲染。
### Screen(屏幕缓冲区)
文件:`src/ink/screen.ts`
Screen 是终端渲染的核心数据结构,使用**压缩的 Int32Array** 存储每个单元格,避免为每个单元格分配对象(200x120 屏幕可避免分配 24000 个对象)。
**单元格存储布局**
每个单元格占用 2 个 Int32(共 8 字节):
- `word0`:字符 ID(索引到 `CharPool`,32 位)
- `word1``styleId[31:17] | hyperlinkId[16:2] | width[1:0]`
**池化系统**
| 池 | 说明 |
|----|------|
| `CharPool` | 字符字符串池,共享跨所有屏幕。ASCII 字符使用 `Int32Array` 快速查找;非 ASCII 使用 `Map`。索引 0 固定为空格,索引 1 为 spacer 空字符串。 |
| `StylePool` | ANSI 样式池,位 0 标识样式是否在空格上可见(背景色、反色、下划线等)。提供 `transition(fromId, toId)` 方法生成缓存的 ANSI 过渡字符串。`withInverse()``withCurrentMatch()``withSelectionBg()` 用于选择/搜索覆盖层。 |
| `HyperlinkPool` | OSC 8 超链接池,索引 0 表示无链接。 |
**单元格宽度枚举(`CellWidth`)**
| 值 | 名称 | 说明 |
|----|------|------|
| 0 | Narrow | 单宽字符 |
| 1 | Wide | 宽字符(CJK、emoji),占用两列 |
| 2 | SpacerTail | 宽字符的第二列占位符 |
| 3 | SpacerHead | 软换行时在行尾标记宽字符延续 |
**关键函数**
- `createScreen()` —— 创建屏幕,8 字节/单元格的 ArrayBuffer
- `resetScreen()` —— 重用屏幕,使用 `BigInt64Array.fill()` 快速清空
- `setCellAt()` —— 写入单元格,自动创建宽字符的 SpacerTail
- `setCellStyleId()` —— 仅更新样式 ID,用于选择/搜索覆盖层
- `cellAt()` / `cellAtIndex()` —— 读取单元格
- `diff()` / `diffEach()` —— 比较两个屏幕,使用 `findNextDiff()` 快速跳过相同区域
- `blitRegion()` —— 批量复制矩形区域(`TypedArray.set()`
- `clearRegion()` —— 快速清空矩形区域
- `shiftRows()` —— 行位移(`copyWithin`),用于 DECSTBM 滚动优化
- `markNoSelectRegion()` —— 标记选择排除区域
- `migrateScreenPools()` —— 跨代池重置时重新 intern
### Output(输出缓冲)
文件:`src/ink/output.ts`
收集渲染操作(Operations),在 `get()` 中统一应用到 Screen 缓冲区。
**操作类型**
| 类型 | 说明 |
|------|------|
| `write` | 写入 ANSI 文本到指定位置 |
| `blit` | 复制源屏幕的矩形区域 |
| `clear` | 清空矩形区域 |
| `clip` / `unclip` | 裁剪区域(嵌套 `overflow:hidden`) |
| `shift` | 行位移 |
| `noSelect` | 标记选择排除区域(最后应用) |
**字符缓存**`charCache`):
每行文本经过 tokenize(ANSI 解析)、grapheme 聚类、双向文本重排、样式 ID + 超链接预计算后缓存。大多数行在帧间不变,缓存命中后只需读取属性并调用 `setCellAt()`
**`writeLineToScreen()`**
核心写入函数,处理:
- C0 控制字符(tab → 空格展开,ESC → CSI/single-char 序列跳过)
- 零宽字符(组合标记等)静默跳过
- 宽字符在行尾时放置 SpacerHead
- 写时损坏区域追踪(`screen.damage`
### LogUpdate(差异比较与补丁生成)
文件:`src/ink/log-update.ts`
比较前一帧和当前帧的 Screen 缓冲区,生成最小补丁序列(`Diff = Patch[]`)。
**补丁类型**
| 类型 | 说明 |
|------|------|
| `stdout` | 原始 ANSI 字符串输出 |
| `clear` | 清空 N 行 |
| `clearTerminal` | 完全清屏(全损坏,引发闪烁) |
| `cursorHide` / `cursorShow` | 光标显隐 |
| `cursorMove` | 光标相对移动 |
| `cursorTo` | 光标绝对定位到列 |
| `carriageReturn` | CR |
| `hyperlink` | OSC 8 超链接 |
| `styleStr` | 预序列化的 ANSI 样式过渡字符串 |
**DECSTBM 滚动优化**
当 ScrollBox 的 `scrollTop` 变化时,使用硬件滚动(CSI `top;bottom r` + CSI `n S/T`)替代重写整个滚动区域,大幅减少输出字节。
**全损坏重置**`fullResetSequence_CAUSES_FLICKER`):
在以下情况触发终端完全清屏:
- 终端尺寸变化(resize)
- 内容高度超出视口后又缩小到视口内
- 需要更新的行在滚动缓冲区中(不可达)
- 这些重置会引发可见的闪烁
**VirtualScreen**
内部辅助类,追踪虚拟光标位置和差异补丁列表,支持事务性操作(`txn()`)批量提交补丁。
### Frame(帧结构)
文件:`src/ink/frame.ts`
```typescript
type Frame = {
screen: Screen // 屏幕缓冲区
viewport: Size // 视口尺寸
cursor: Cursor // 光标状态
scrollHint?: ScrollHint // DECSTBM 滚动提示
scrollDrainPending?: boolean // 是否需要继续帧
}
```
辅助函数:
- `shouldClearScreen()` —— 判断是否需要清屏(resize 或 offscreen)
- `emptyFrame()` —— 创建空帧
---
## 布局引擎(Yoga)
文件:`src/ink/layout/` 目录
使用 [Yoga](https://yogalayout.com/) 的 WASM 实现进行 Flexbox 布局计算。
**在渲染管线中的位置**
1. React commit 后,`resetAfterCommit` 调用 `rootNode.onComputeLayout()`
2. `ink.tsx` 中的 `onComputeLayout` 调用 `root.yogaNode.calculateLayout()`
3. Yoga 执行完整的 Flexbox 布局计算(含 `measureFunc` 回调用于文本测量)
4. Renderer 读取 `getComputedWidth/Height/Top/Left` 获取布局结果
**布局节点**:文件 `src/ink/layout/node.ts`,定义 `LayoutNode` 接口和所有布局常量(`LayoutEdge`, `LayoutDisplay`, `LayoutFlexDirection`, `LayoutJustify`, `LayoutAlign`, `LayoutOverflow`, `LayoutPositionType`, `LayoutWrap`, `LayoutGutter`)。
**布局引擎**:文件 `src/ink/layout/engine.ts`,创建和管理 Yoga WASM 实例。提供 `createLayoutNode()``getYogaCounters()` 等函数。
**测量函数**
- `ink-text``measureTextNode()` —— 处理文本换行、tab 展开、bi-di 重排
- `ink-raw-ansi``measureRawAnsiNode()` —— 使用预设的 rawWidth/rawHeight
---
## 样式系统
文件:`src/ink/styles.ts`
样式类型定义(`Styles`)涵盖所有 CSS Flexbox 属性:
**布局**`display`, `position`, `overflow`, `flexDirection`, `flexWrap`, `flexGrow`, `flexShrink`, `flexBasis`, `alignItems`, `alignSelf`, `justifyContent`
**尺寸**`width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`(支持数值和百分比字符串)
**间距**`margin`, `padding`, `gap`(含 X/Y 方向缩写)
**边框**`borderStyle`(圆角/单线/双线等), `borderColor`, `borderTop/Right/Bottom/Left`(控制各边显隐)
**文本**`textWrap`(8 种模式:`wrap`, `wrap-trim`, `end`, `middle`, `truncate-end`, `truncate`, `truncate-middle`, `truncate-start`
**其他**`backgroundColor`, `opaque`, `noSelect`(选择排除), `borderText`(边框内文本)
`applyStyles()` 函数将 `Styles` 对象映射到 Yoga 节点属性。每个样式类别有独立的函数(`applyPositionStyles`, `applyOverflowStyles`, `applyFlexStyles` 等)。
### TextStyles
文件:`src/ink/styles.ts`
```typescript
type TextStyles = {
color?, backgroundColor? // 颜色
dim?, bold?, italic? // 字体样式
underline?, strikethrough? // 装饰
inverse? // 反色
}
```
`TextStyles``<Text>` 组件中声明,通过 `ink-text` 节点的 `textStyles` 属性传递。渲染时由 `render-node-to-output.ts``buildTextStyles()` 转换为 ANSI SGR 序列。
---
## 事件系统
### 键盘事件
**Kitty 键盘协议**:在 raw mode 中启用 CSI u 扩展键编码(`ENABLE_KITTY_KEYBOARD` + `ENABLE_MODIFY_OTHER_KEYS`),使 Ctrl+Shift+字母 与 Ctrl+字母 可区分。终端识别通过在 XTVERSION 查询后设置。
**输入解析**:文件 `src/ink/parse-keypress.ts`,状态机解析 stdin 的原始字节流。支持:
- 普通字符(UTF-8)
- CSI 序列(方向键、功能键等)
- Kitty CSI u 序列(带修饰键编码)
- SGR 鼠标编码(`<row;col;button M/m`)
- DEC 私有模式响应(DCS 序列)
- OSC 序列(操作系统命令)
- 粘贴模式(`IN_PASTE` 状态)
**`App` 组件中的处理**(文件 `src/ink/components/App.tsx`):
- `handleReadable()` —— stdin `readable` 事件回调
- `processInput()` —— 调用 `parseMultipleKeypresses()` 解析多按键
- `processKeysInBatch()` —— 在 `reconciler.discreteUpdates` 中批量处理按键,避免"最大更新深度超出"错误
- 处理 `Ctrl+C`(退出)、`Ctrl+Z`(挂起)、终端焦点事件(DECSET 1004)
### 鼠标事件
通过 DECSET 1003(SGR 鼠标跟踪)启用/禁用。
**`processKeysInBatch` 中的鼠标处理**`handleMouseEvent()`):
- **press**:开始选择、多击检测(双击选中单词、三击选中行)
- **drag**:扩展选择(字符/单词/行模式)
- **release**:结束选择
- **no-button motion**(模式 1003):悬停事件分发(`onMouseEnter`/`onMouseLeave`
- **超链接**:单点击时延迟打开,双击取消
**`handleMouseEvent()` 中的选择逻辑**
- `startSelection()` / `finishSelection()` / `hasSelection()`
- 多击跟踪:`clickCount` 在 500ms 内递增,支持双击单词选择和三击行选择
- `lastPressHadAlt`:记录 Alt 修饰键,用于终端判定
### 焦点管理
文件:`src/ink/focus.ts`
`FocusManager` 存储在每个根节点,类似于浏览器 `document.activeElement`
**功能**
- `focus(node)` / `blur()` —— 获取/失去焦点
- `handleNodeRemoved()` —— 节点移除时自动恢复焦点到栈中上一个
- `handleAutoFocus()` —— 自动聚焦(`autoFocus` prop)
- `handleClickFocus()` —— 点击聚焦(`tabIndex` prop)
- `focusNext()` / `focusPrevious()` —— Tab / Shift+Tab 导航
**焦点栈**:最多 32 层,用于节点移除后的焦点恢复。
**事件分发**:事件系统使用 `Dispatcher` 类(`src/ink/events/dispatcher.ts`)处理捕获(Capture)和冒泡(Bubble)阶段。`EVENT_HANDLER_PROPS` 集合定义哪些 props 是事件处理器(`onClick`, `onKeyDown` 等)。
---
## 组件库
文件:`src/ink/components/`
| 组件 | 文件 | 说明 |
|------|------|------|
| `App` | `App.tsx` | 根组件,管理 stdin/stdout、raw mode、光标、挂起/恢复、事件分发、多击选择 |
| `Box` | `Box.tsx` | 布局容器(类似 `<div>`),支持完整 Flexbox 样式和事件处理器(onClick, onKeyDown, onMouseEnter, onMouseLeave, onFocus, onBlur) |
| `Text` | `Text.tsx` | 文本组件,支持颜色(color, backgroundColor)、加粗(bold)、斜体(italic)、下划线(underline)、删除线(strikethrough)、反色(inverse)、换行模式(wrap) |
| `Button` | `Button.tsx` | 按钮组件(继承 Box 的点击能力) |
| `Link` | `Link.tsx` | 超链接(使用 OSC 8 协议),终端不支持时回退到纯文本 |
| `Spacer` | `Spacer.tsx` | 弹性空间填充(`flexGrow: 1`) |
| `Newline` | `Newline.tsx` | 插入空白行 |
| `ScrollBox` | `ScrollBox.tsx` | 滚动容器(`overflow: scroll`),支持 `scrollTo()`, `scrollToElement()` 和粘性滚动 |
| `AlternateScreen` | `AlternateScreen.tsx` | 替代屏幕(alt-screen)管理 |
| `RawAnsi` | `RawAnsi.tsx` | 原始 ANSI 字符串渲染(预计算的宽度和高度) |
| `NoSelect` | `NoSelect.tsx` | 选择排除区域(如行号、diff 标记) |
| `Ansi` | (在 ink.ts 中 re-export) | ANSI 文本渲染 |
| `ErrorOverview` | `ErrorOverview.tsx` | 错误概览组件 |
**Context 提供者**
| Context | 文件 | 提供内容 |
|---------|------|----------|
| `AppContext` | `AppContext.ts` | `exit()` 退出函数 |
| `StdinContext` | `StdinContext.ts` | `stdin`, `setRawMode`, `internal_eventEmitter`, `internal_querier` |
| `TerminalSizeContext` | `TerminalSizeContext.tsx` | `columns`, `rows` 终端尺寸 |
| `TerminalFocusContext` | `TerminalFocusContext.tsx` | `isTerminalFocused` 终端焦点状态 |
| `ClockContext` | `ClockContext.tsx` | 时钟/定时器(根据焦点状态调整间隔) |
| `CursorDeclarationContext` | `CursorDeclarationContext.ts` | 声明式光标位置 |
---
## Hooks
文件:`src/ink/hooks/`
| Hook | 文件 | 说明 |
|------|------|------|
| `useInput` | `use-input.ts` | 处理用户键盘输入(`useLayoutEffect` 同步启用 raw mode),注册 `input` 事件监听器 |
| `useStdin` | `use-stdin.ts` | 访问 stdin/setRawMode/EventEmitter |
| `useApp` | `use-app.ts` | 访问 `exit()` 函数 |
| `useAnimationFrame` | `use-animation-frame.ts` | 动画帧钩子(使用 `FRAME_INTERVAL_MS=16ms` 节流渲染) |
| `useInterval` | `use-interval.ts` | 定时器(终端失焦时可选减速) |
| `useSelection` | `use-selection.ts` | 文本选择状态管理(anchor, focus, word/line 模式) |
| `useSearchHighlight` | `use-search-highlight.ts` | 搜索高亮状态(匹配位置、当前匹配导航) |
| `useDeclaredCursor` | `use-declared-cursor.ts` | 声明式光标位置(用于 IME 输入和屏幕阅读器) |
| `useTabStatus` | `use-tab-status.ts` | Tab 状态管理 |
| `useTerminalFocus` | `use-terminal-focus.ts` | 终端焦点变化监听 |
| `useTerminalTitle` | `use-terminal-title.ts` | 终端标题设置 |
| `useTerminalViewport` | `use-terminal-viewport.ts` | 终端视口信息 |
---
## 文本选择
文件:`src/ink/selection.ts`
`SelectionState` 管理全屏模式下的文本选择:
- **anchor**:选择起点(鼠标按下处)
- **focus**:选择终点(鼠标拖动处)
- **anchorSpan**:多击(双击/三击)时初始单词/行的范围
- **scrolledOffAbove/Below**:选择过程中滚动出视口的文本
- **softWrap 标记**:跟踪软换行位置
**选择操作**
- `startSelection()` / `updateSelection()` / `finishSelection()`
- `extendSelection()` —— 字符/单词/行模式扩展
- `getSelectedText()` —— 获取选中文本(拼接跨行的选择,含滚动出视口的文本)
- `applySelectionOverlay()` —— 在屏幕覆盖层中高亮选中区域
---
## 搜索高亮
文件:`src/ink/render-to-screen.ts``src/ink/components/App.tsx`
搜索高亮系统分为两个层次:
1. **`renderToScreen()`** —— 在独立屏幕缓冲区中渲染单个消息,扫描查询字符串的位置
2. **`applySearchHighlight()`** / **`applyPositionedHighlight()`** —— 将搜索匹配覆盖层应用到实际屏幕
**匹配视觉风格**
- 普通匹配:反色(SGR 7,白色底,从主题继承)
- 当前匹配(用户导航到的位置):黄色背景(通过黄色前景 + 反色实现)+ 加粗 + 下划线
---
## 渲染管线完整流程
从 React 组件到终端输出的完整流程:
```
1. React 更新 (setState / 新 props)
2. Reconciler commit (resetAfterCommit)
├── onComputeLayout: Yoga calculateLayout()
└── onRender: scheduleRender (节流, 16ms)
3. createRenderer → renderNodeToOutput
├── 递归遍历 DOM 树
├── 应用 scrollTop 偏移
├── 写操作 -> Output buffer
└── blit 操作 -> Output buffer
4. Output.get()
├── 应用所有操作到 Screen
├── 处理裁剪区域
├── 处理 absolute 清除
└── 更新 softWrap 标记
5. LogUpdate.render()
├── 检查是否要全损坏重置
├── DECSTBM 滚动优化
├── diffEach 比较前后帧
├── 处理增长/收缩
└── 生成 Patch[]
6. optimize() — 补丁合并与去重
7. writeDiffToTerminal() — 补丁序列化为 ANSI → stdout
8. 屏幕选择/搜索覆盖层更新
9. 光标定位
└── 下一帧
```
---
## 特殊功能
### 双缓冲(Double Buffering)
使用前后帧(`frontFrame` / `backFrame`)实现双缓冲:
- `backFrame.screen` 是当前渲染目标
- `frontFrame.screen` 是前一帧的屏幕,用于差异比较和 blit
- 渲染帧切换后进行缓冲池交换(`swapFrames()`
### 屏幕伤害追踪(Damage Tracking)
每个 `Screen` 有一个 `damage` 矩形区域,记录自上次重置以来发生写操作的区域。`diffEach()` 只扫描损伤区域,避免遍历整个屏幕。
### 双向文本(BiDi)
文件:`src/ink/bidi.ts`
使用 Unicode Bidirectional Algorithm 对文本行进行重排。在 `writeLineToScreen()` 中,每行文本在 tokenize 和 grapheme 聚类后执行 `reorderBidi()`
### ANSI 解析与 Grapheme 聚类
使用 `@alcalzone/ansi-tokenize` 解析 ANSI 序列,然后通过 `Intl.Segmenter` 进行 grapheme 聚类(支持家族 emoji 等多码点字符)。结果缓存到 `charCache`,帧间重用。
### 终端查询(Terminal Querier)
文件:`src/ink/terminal-querier.ts`
`TerminalQuerier` 通过 DEC 请求序列(如 DA1、XTVERSION、DECRPM)与终端通信。它维护一个待处理请求的 Promise 队列,在收到终端响应时解析相应的 Promise。
### DEC 私有模式
文件:`src/ink/termio/dec.ts`
管理 DEC 私有模式:
- 鼠标跟踪:`ENABLE_MOUSE_TRACKING(SGR, 1003)` / `DISABLE_MOUSE_TRACKING`
- 光标显隐:`HIDE_CURSOR` / `SHOW_CURSOR`
- 焦点报告:`EFE` / `DFE`(DECSET 1004)
- 滚轮支持:通过 `DECRPM` 查询终端能力
### Kitty 键盘协议
文件:`src/ink/termio/ansi.ts``src/ink/termio/csi.ts`
通过 CSI `>1u`(Kitty 协议)和 CSI `>4;2m`(xterm modifyOtherKeys)启用扩展键盘报告,使修饰键组合(Ctrl+Shift+A 等)可区分。
### 替代屏幕(Alt Screen)
`<AlternateScreen>` 组件管理终端的替代屏幕缓冲区。启用时:
- 内容始终恰好占满终端行数
- 光标隐藏
- 启用 DECSET 1000/1002/1003 鼠标跟踪
- 支持 DECSTBM 滚动优化
- 退出时恢复主屏幕内容
### 声明式光标(Declared Cursor)
`useDeclaredCursor` 钩子和 `CursorDeclarationContext` 允许组件声明当前光标位置(如输入框),使 Ink 在每一帧后将终端光标放置在该位置,支持 IME 输入和屏幕阅读器。
### 滚动容器(ScrollBox)
`<ScrollBox>` 组件(`overflow: scroll`)实现:
- `scrollTop`:可编程滚动位置
- `pendingScrollDelta`:累积的滚动增量(限制 SCROLL_MAX_PER_FRAME 行/帧,防止大幅跳动)
- `stickyScroll`:粘性滚动(内容增长时自动保持在底部)
- `scrollTo(y)` / `scrollToElement(el, offset)`:可编程滚动
- `scrollHeight` / `scrollViewportHeight`:滚动信息查询
- `scrollClampMin` / `scrollClampMax`:虚拟滚动边界保护
- 渲染时通过 `output.shift()` 实现 DECSTBM 硬件滚动
### 选择排除(NoSelect)
`<NoSelect>` 组件标记区域为选择排除:
- `noSelect` 模式:仅排除该盒子的精确区域
- `from-left-edge` 模式:从第 0 列到盒子右边缘的整行区域
- `screen.noSelect` 位图每帧重置,`blitRegion` 复制时连带复制
### 调试与性能分析
- `CLAUDE_CODE_DEBUG_REPAINTS`:追踪重绘来源组件
- `CLAUDE_CODE_COMMIT_LOG`:记录提交/渲染时间日志
- YOGA 计数器:`getYogaCounters()` 报告每个框架的节点访问、测量、缓存命中
- `FRAME_INTERVAL_MS = 16`:60fps 帧率
- Blit/Write 比率日志:高频写入(而非 blit)时发出警告
---
## 文件索引
按功能类别整理的核心文件:
| 类别 | 文件 |
|------|------|
| 主入口 | `src/ink.ts`, `src/ink/ink.tsx` |
| DOM | `src/ink/dom.ts` |
| Reconciler | `src/ink/reconciler.ts` |
| Renderer | `src/ink/renderer.ts`, `src/ink/render-node-to-output.ts` |
| Screen | `src/ink/screen.ts` |
| Output | `src/ink/output.ts` |
| LogUpdate | `src/ink/log-update.ts` |
| Frame | `src/ink/frame.ts` |
| Styles | `src/ink/styles.ts` |
| Focus | `src/ink/focus.ts` |
| Selection | `src/ink/selection.ts` |
| Layout | `src/ink/layout/engine.ts`, `node.ts` |
| Event Dispatcher | `src/ink/events/dispatcher.ts` |
| Event Handlers | `src/ink/events/event-handlers.ts` |
| Components | `src/ink/components/` (App, Box, Text, Button, Link, Spacer, Newline, ScrollBox, AlternateScreen, RawAnsi, NoSelect, ErrorOverview) |
| Hooks | `src/ink/hooks/` (use-input, use-stdin, use-app, use-animation-frame, use-interval, use-selection, use-search-highlight, use-declared-cursor, use-tab-status, use-terminal-focus, use-terminal-title, use-terminal-viewport) |
| Termio | `src/ink/termio/ansi.ts`, `csi.ts`, `dec.ts`, `osc.ts` |
| Bidi | `src/ink/bidi.ts` |
| Constants | `src/ink/constants.ts` |
| Contexts | `src/ink/components/AppContext.ts`, `StdinContext.ts`, `TerminalSizeContext.tsx`, `TerminalFocusContext.tsx`, `ClockContext.tsx`, `CursorDeclarationContext.ts` |