Ink 框架概述
概述
Claude Code 使用 Ink 框架在终端中渲染 React 组件。Ink 是一个基于 React 的终端 UI 框架,将 React 的声明式编程模型带入命令行界面。
Claude Code 使用的是一个内部分支版本的 Ink,包含自定义的调和器、虚拟滚动、选择和搜索高亮等功能。
Source:
src/ink/(自定义 Ink 框架),src/ink.ts(公共 API)
渲染管线
双缓冲
typescript
// Source: src/ink/ink.tsx:99-100
frontFrame // 上一帧 (已显示)
backFrame // 当前帧 (正在渲染)每帧是一个 2D 网格,每个单元格包含 { char, style, hyperlink }。渲染时只写入变化的单元格,实现无闪烁更新。
核心类:Ink
Source:
src/ink/ink.tsx(2000+ 行)
typescript
class Ink {
// 双缓冲
private frontFrame: Screen
private backFrame: Screen
// 对象池(减少 GC 压力)
private stylePool: Pool
private charPool: Pool
// 备用屏幕(全屏模式)
private altScreenActive: boolean
// 文本选择
readonly selection: SelectionState
// 搜索高亮
private searchHighlightQuery: string
private searchPositions: MatchPosition[]
// 鼠标追踪
private hoveredNodes: Set<DOMElement>
}关键方法
| 方法 | 功能 |
|---|---|
render(node) | 调和 React 树,绘制帧 |
onRender() | 主帧渲染回调(损伤计算、位块传送、选择覆盖) |
handleResize() | 终端 SIGWINCH 处理 |
setSearchHighlight(query) | 更新搜索覆盖 |
scanElementSubtree(el) | 将子树绘制到新 Screen,提取匹配位置 |
DOM 模型
Source:
src/ink/dom.ts
Ink 维护一个轻量级 DOM 树,元素类型包括:
ink-root, ink-box, ink-text, ink-virtual-text,
ink-link, ink-progress, ink-raw-ansiDOMElement 类型
typescript
type DOMElement = {
nodeName: ElementNames
attributes: Record<string, DOMNodeAttribute>
childNodes: DOMNode[]
yogaNode?: LayoutNode // Yoga 布局节点
dirty: boolean // 脏标记
// 滚动状态
scrollTop?: number
scrollHeight?: number
scrollViewportHeight?: number
stickyScroll?: boolean // 自动锚定到底部
}Yoga 布局
每个 DOM 元素关联一个 Yoga 布局节点(CSS Flexbox 的 WASM 实现):
typescript
// 创建布局节点
const yogaNode = createLayoutNode();
// 文本节点需要测量函数
yogaNode.setMeasureFunc(measureTextNode);
// 计算布局后获取位置
const top = yogaNode.getComputedTop();
const height = yogaNode.getComputedHeight();自定义调和器
Source:
src/ink/reconciler.ts(300+ 行)
使用 react-reconciler 创建自定义调和器,将 React 操作映射到 DOM 模型:
typescript
createReconciler({
createInstance(type, props) {
return dom.createNode(type); // 创建 DOM 节点
},
createTextInstance(text) {
return dom.createTextNode(text);
},
appendChildNode(parent, child) {
dom.appendChild(parent, child);
},
// ... 插入、删除、更新属性等
});自定义 Diff 算法
typescript
function diff(before, after) {
// 只比较对象中变化的键
// 最小化调和开销
}核心 Hooks
useInput
Source:
src/ink/hooks/use-input.ts(92 行)
捕获键盘输入:
typescript
useInput((input, key) => {
if (key.ctrl && input === 'c') {
// Ctrl+C 处理
}
if (key.return) {
// 回车键处理
}
}, { isActive: true });- 使用
useLayoutEffect同步启用 raw 模式 setRawMode(true)捕获每个按键- 通过
internal_eventEmitter分发输入事件
useTerminalViewport
Source:
src/ink/hooks/use-terminal-viewport.ts(96 行)
检测组件是否在终端视口内可见:
typescript
const [ref, entry] = useTerminalViewport();
// entry.isVisible — 组件是否在可视区域
return <Box ref={ref}>...</Box>;- 遍历 DOM 祖先链计算绝对位置
- 考虑 ScrollBox 嵌套的
scrollTop偏移
useSearchHighlight
Source:
src/ink/hooks/use-search-highlight.ts(54 行)
设置搜索高亮覆盖:
typescript
const { setQuery, scanElement, setPositions } = useSearchHighlight();
// 设置搜索词 → 反转匹配单元格
setQuery('error');
// 扫描元素提取匹配位置
const positions = scanElement(domElement);
// 设置"当前"高亮(黄色覆盖)
setPositions({ current: 3, total: 10 });事件系统
| 事件类型 | 说明 |
|---|---|
| 键盘事件 | 通过 parseKeypress.ts 解析原始 stdin |
| 鼠标事件 | dispatchClick, dispatchHover + 命中测试 |
| 终端焦点 | TerminalFocusEvent |
| 自定义输入 | InputEvent via useInput |
事件支持捕获/冒泡阶段和 stopPropagation()。
公共 API
Source:
src/ink.ts(86 行)
typescript
// 渲染封装(自动注入 ThemeProvider)
export async function render(node, options?) { ... }
export function createRoot(options?) { ... }
// 重导出的组件和 Hooks
export { Box, Text, useInput, useStdin, useTheme,
useTerminalViewport, useTerminalFocus, useTerminalTitle }
// 工具函数
export { measureElement, wrapText }下一步
- REPL 交互界面 — 交互式对话界面的实现