Skip to content

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-ansi

DOMElement 类型

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 }

下一步