# ink-terminal

**高性能 React 终端渲染引擎，内含 27 项渲染优化技术。**

从 [Claude Code](https://claude.ai/code) 内部渲染引擎提取而来。提供完整的 React 终端 UI 框架，包含双缓冲、硬件滚动、损伤追踪差分、原子输出 —— 对应用层完全透明。

[**English Documentation**](./README.md)

---

## 核心特性

- **60fps 节流渲染** — `lodash.throttle` + `queueMicrotask`，高频状态更新自动合并
- **双缓冲** — frontFrame/backFrame 交换，每帧零内存分配
- **Blit 快速路径** — 未变化的子树通过 `TypedArray.set()` 批量复制 (memcpy 级别)
- **打包 Int32Array 屏幕缓冲区** — 每个 cell 2 words，`CharPool`/`StylePool` 池化
- **损伤追踪差分** — 只扫描变化的屏幕区域，而非整个缓冲区
- **DECSTBM 硬件滚动** — 终端侧行移位，只绘制新增行
- **BSU/ESU 原子输出** — DEC 2026 同步更新，消除画面撕裂
- **增量 Markdown** — O(delta) 解析 + Token LRU 缓存 (配合 StreamingMarkdown 使用)
- **虚拟滚动** — 1000+ 消息只渲染可视区域
- **纯 TypeScript Yoga 布局** — Flexbox 布局，零 native/WASM 依赖
- **完整事件系统** — 键盘、鼠标点击、焦点、粘贴、窗口大小变化
- **React 19 + 自定义 Reconciler** — 终端中使用完整 React 组件模型

## 架构

```
ink-terminal/
├── core/          # 第 1 层: 屏幕缓冲区, Diff, 终端 I/O (无 React 依赖)
├── react/         # 第 2 层: React reconciler, 组件, Hooks
├── events/        # 事件系统 (输入, 点击, 焦点, 键盘, 粘贴, 窗口变化)
├── termio/        # 终端 I/O 原语 (ANSI, CSI, DEC, ESC, OSC, SGR)
├── layout/        # Flexbox 布局引擎 (纯 TS Yoga 实现)
├── providers/     # 插件/回调接口，用于应用层集成
└── internal/      # 内置工具 (intl, stringWidth, sliceAnsi)
```

### 三层架构

| 层级 | 目录 | 需要 React | 用途 |
|------|------|-----------|------|
| **Core** | `core/` | 否 | 屏幕缓冲区、差分引擎、终端能力检测 |
| **React** | `react/` | 是 | Reconciler、组件、Hooks、Ink 主类 |
| **应用层** | *(你的项目)* | 是 | 主题、Markdown、消息列表、REPL |

## 安装

```bash
bun add ink-terminal react react-reconciler
```

## 快速开始

### 最小示例 (5 行核心代码)

```tsx
import { render, Box, Text } from 'ink-terminal'

const App = () => (
  <Box flexDirection="column" padding={1}>
    <Text bold color="green">Hello Terminal!</Text>
    <Text>60fps 双缓冲渲染管线。</Text>
  </Box>
)

await render(<App />)
```

### 流式 Agent 输出

```tsx
import { render, Box, Text, ScrollBox } from 'ink-terminal'
import { useState, useEffect } from 'react'

function AgentUI() {
  const [text, setText] = useState('')

  useEffect(() => {
    ;(async () => {
      for await (const chunk of myAgent.stream('hello')) {
        setText(t => t + chunk) // 自动 60fps 节流 + 双缓冲 + blit
      }
    })()
  }, [])

  return (
    <Box flexDirection="column">
      <ScrollBox overflow="scroll" stickyScroll>
        <Text>{text}</Text>
      </ScrollBox>
    </Box>
  )
}

await render(<AgentUI />)
```

仅这 5 行核心代码，你的输出就已获得:
- 60fps 节流渲染（不会因 token 频率过高而卡死终端）
- 双缓冲 + blit（只重绘变化的 cells）
- 损伤追踪（只 diff 变化区域）
- BSU/ESU 原子输出（无撕裂）

### 交互式应用

```tsx
import {
  render, Box, Text, ScrollBox,
  AlternateScreen, useInput, useApp
} from 'ink-terminal'

function App({ messages }) {
  const { exit } = useApp()
  useInput((input, key) => {
    if (key.escape) exit()
  })

  return (
    <AlternateScreen>
      <Box flexDirection="column" height="100%">
        <Box borderStyle="single" paddingX={1}>
          <Text bold>My Agent</Text>
        </Box>
        <ScrollBox overflow="scroll" stickyScroll flexGrow={1}>
          {messages.map(m => (
            <Box key={m.id} paddingX={1}>
              <Text>{m.content}</Text>
            </Box>
          ))}
        </ScrollBox>
      </Box>
    </AlternateScreen>
  )
}
```

### 仅使用 Core 层 (无 React)

```typescript
import {
  createScreen, CharPool, StylePool, HyperlinkPool,
  diffEach, blitRegion
} from 'ink-terminal/core'

// 创建双缓冲
const pools = { style: new StylePool(), char: new CharPool(), hyperlink: new HyperlinkPool() }
const screen1 = createScreen(80, 24, pools.style, pools.char, pools.hyperlink)
const screen2 = createScreen(80, 24, pools.style, pools.char, pools.hyperlink)

// 写入 cells, 差分, 输出
diffEach(screen1, screen2, (x, y, removed, added) => {
  // 处理 cell 变化
})
```

## 配置

通过 `configure()` 注入应用特定的回调。所有回调都在**冷路径**（日志、生命周期）—— 热渲染路径零外部调用。

```typescript
import { configure } from 'ink-terminal'

configure({
  logForDebugging: (msg) => console.debug(`[ink] ${msg}`),
  logError: (msg) => console.error(`[ink] ${msg}`),
  terminalName: 'my-app',
  onBeforeRender: () => { /* 刷新交互指标 */ },
  onScrollActivity: () => { /* 标记滚动活动 */ },
  onInteraction: () => { /* 更新交互时间戳 */ },
  onFirstInput: () => { /* 停止捕获早期输入 */ },
  isMouseClicksDisabled: () => false,
  isEnvTruthy: (v) => v === '1' || v === 'true',
})
```

## 组件一览

| 组件 | 说明 |
|------|------|
| `Box` | Flexbox 容器（方向、内边距、外边距、边框） |
| `Text` | 带样式的终端文本（粗体、斜体、颜色、下划线） |
| `ScrollBox` | 可滚动容器，支持硬件滚动 (DECSTBM) |
| `AlternateScreen` | 全屏备用缓冲区模式 |
| `Button` | 支持焦点的交互按钮 |
| `Link` | 可点击超链接 (OSC 8) |
| `Newline` | 换行符 |
| `Spacer` | Flex 空间填充 |
| `NoSelect` | 不可选择区域 |
| `RawAnsi` | 原始 ANSI 转义码直通 |
| `Ansi` | 解析后的 ANSI 文本 |

## Hooks 一览

| Hook | 说明 |
|------|------|
| `useInput(handler)` | 键盘/鼠标输入处理 |
| `useApp()` | 访问应用实例（退出等） |
| `useStdin()` | 原始 stdin 访问 |
| `useAnimationFrame(cb)` | 帧同步动画回调 |
| `useInterval(cb, ms)` | 定时器 |
| `useSelection()` | 文本选择状态 |
| `useTabStatus()` | 终端标签页状态栏 |
| `useTerminalFocus()` | 终端焦点/失焦事件 |
| `useTerminalTitle(title)` | 设置终端标题 |
| `useTerminalViewport()` | 获取终端尺寸 |

## 子路径导出

```typescript
import { ... } from 'ink-terminal'          // 完整 API
import { ... } from 'ink-terminal/core'     // 屏幕缓冲区, diff (无 React)
import { ... } from 'ink-terminal/react'    // React 组件, hooks, reconciler
import { ... } from 'ink-terminal/events'   // 事件类
import { ... } from 'ink-terminal/termio'   // 终端转义序列
import { ... } from 'ink-terminal/layout'   // Yoga 布局引擎
```

## 渲染管线

```
React setState
    │
    ▼
scheduleRender (lodash.throttle 16ms, leading+trailing)
    │
    ▼
queueMicrotask (延迟到 useLayoutEffect 之后)
    │
    ▼
onRender()
    ├── Yoga 布局计算
    ├── DFS 树遍历 → 屏幕缓冲区
    │   ├── Blit 快速路径 (clean 节点 → TypedArray memcpy)
    │   └── Dirty 节点 → 重渲染到 cells
    ├── Diff (损伤追踪, 每 cell 2 个 int 比较)
    ├── 优化 patches (合并光标移动, 去重超链接)
    └── 写入终端 (BSU → ANSI 转义码 → ESU)
```

## 27 项优化技术总览

| # | 层级 | 优化 | 核心原理 |
|---|------|------|----------|
| 1 | React | 60fps 节流渲染 | lodash throttle + queueMicrotask |
| 2 | React | 双缓冲 | frontFrame/backFrame 交换 |
| 3 | 树遍历 | Blit 快速路径 | clean 节点 TypedArray memcpy |
| 4 | 树遍历 | 脏标记传播 | shallowEqual 守卫 + 祖先链 |
| 5 | 缓冲区 | 打包 Int32Array | 每 cell 2 words |
| 6 | 缓冲区 | CharPool ASCII 快速路径 | Int32Array 直接索引 |
| 7 | 缓冲区 | StylePool 转换缓存 | 预序列化 ANSI diff |
| 8 | Diff | 损伤矩形追踪 | 只 diff 变化区域 |
| 9 | Diff | findNextDiff | 2 个 int 比较/cell, JIT 内联 |
| 10 | Diff | visibleCellAtIndex | 跳过不可见空格 |
| 11 | 终端 | DECSTBM 硬件滚动 | CSI 滚动区域 |
| 12 | 终端 | BSU/ESU 原子写入 | DEC 2026 |
| 13 | 终端 | Patch 优化器 | 合并光标移动/去重超链接 |
| 14 | 输出 | charCache 跨帧持久化 | 16384 上限 |
| 15 | Markdown | 流式增量解析 | stable/unstable 分割 |
| 16 | Markdown | Token LRU 缓存 | 500 条, hash 键控 |
| 17 | 虚拟滚动 | scrollTop 量化 | 40 行 bin |
| 18 | 虚拟滚动 | Slide cap | 每 commit 最多挂载 25 项 |
| 19 | 虚拟滚动 | useDeferredValue 时间切片 | React 并发特性 |
| 20 | 虚拟滚动 | Resize 高度缩放 | 非清除 |
| 21 | 滚动 | 自适应消耗 | xterm.js <=5 即时, >5 步进 |
| 22 | 滚动 | 比例消耗 | 原生终端, 3/4 剩余量 |
| 23 | 内存 | 池定期重置 | 每 5 分钟 CharPool+HyperlinkPool |
| 24 | 渲染器 | Output 复用 | charCache 跨帧持久化 |
| 25 | 渲染器 | 毒化检测 | prevScreen/absoluteRemoved |
| 26 | 监控 | 分阶段帧计时 | renderer/diff/optimize/write/yoga |
| 27 | 监控 | FPS P99 追踪 | 最坏 1% 帧 |

## 与原版 Ink 对比

| 特性 | 原版 Ink | ink-terminal |
|------|---------|--------------|
| 屏幕缓冲区 | 每帧字符串拼接 | 打包 Int32Array + CharPool/StylePool |
| Diff 算法 | 逐行字符串比较 | 逐 Cell 整数比较 + 损伤追踪 |
| 滚动 | 全量重绘 | DECSTBM 硬件滚动 + blit+shift |
| 原子输出 | 无 | BSU/ESU (DEC 2026) |
| 字符宽度 | 基础 | Unicode Grapheme Segmenter + bidi |
| 布局引擎 | Yoga WASM | 纯 TS Yoga (零安装成本) |
| 虚拟滚动 | 无 | 内置 (量化/slide cap/deferred) |
| 帧性能 | 无监控 | 分阶段计时 + FPS P99 |
| 组件数量 | ~10 | 50+ |
| 事件系统 | 基础 keyboard | keyboard + click + focus + paste + resize |

## Agent 集成指南

### 集成深度选择

| 需求 | 推荐方式 | 代码量 | 获得的优化 |
|------|----------|--------|-----------|
| 只显示流式文本 | `setText(t => t + chunk)` | 5 行 | 60fps + 双缓冲 + blit + BSU/ESU |
| 显示工具调用和结果 | Message 协议 + 事件分发 | 50 行 | 上述 + 工具渲染 + 进度显示 |
| 完整交互式终端 | REPL 级别消息循环 | 200 行 | 上述 + 虚拟滚动 + 输入处理 |

### 数据流

```
Agent 事件 → React setState → 自动渲染管线
    │                              │
    ▼                              ▼
text_delta → setText(t + d)    Ink 60fps 节流 → Yoga → Screen → Diff → Terminal
tool_use   → setTools([...])   (对 agent 完全透明)
message    → setMessages([...])
```

详细的集成文档见 `docs/ink-terminal-agent-integration-guide.md`。

## 项目统计

- **113 个 TypeScript 文件**，**22,430 行代码**
- **27 项渲染优化技术**
- **0 个 TypeScript 错误** (`tsc --noEmit`)
- 纯 TypeScript Yoga 布局引擎 (~2,578 行，无 WASM/native addon)
- 18 个 npm 依赖，2 个 peer 依赖 (react, react-reconciler)

## 参考文档

| 文档 | 说明 |
|------|------|
| `docs/terminal-rendering-guide.md` | 渲染优化技术详解 (8 章节, 40+ 行号引用) |
| `docs/terminal-rendering-exploration-report.md` | 完整探索过程记录 |
| `docs/ink-terminal-extraction-plan.md` | 提取方案设计 (三层架构, 72 文件清单) |
| `docs/ink-terminal-agent-integration-guide.md` | Agent 适配指南 (3 种集成模式) |
| `docs/ink-terminal-rust-integration.md` | Rust 项目集成方案 (3 种方案对比) |

## 许可证

本项目包含从 Anthropic 的 Claude Code 提取的代码。许可条款请参阅原始项目。
