# 代码编辑器

<cite>
**本文引用的文件**
- [src/components/ui/code-editor.tsx](file://src/components/ui/code-editor.tsx)
- [src/hooks/use-word-wrap.ts](file://src/hooks/use-word-wrap.ts)
- [src/components/layout/forge-file-editor.tsx](file://src/components/layout/forge-file-editor.tsx)
- [src/components/manage/skill-file-editor.tsx](file://src/components/manage/skill-file-editor.tsx)
- [src/components/marketplace/template-editor-panel.tsx](file://src/components/marketplace/template-editor-panel.tsx)
- [src/lib/utils.ts](file://src/lib/utils.ts)
- [src/app/globals.css](file://src/app/globals.css)
- [src/lib/types.ts](file://src/lib/types.ts)
</cite>

## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)

## 简介
本文件系统性解析代码编辑器组件 CodeEditor 的实现原理与使用方式，覆盖以下关键能力：
- 多行文本编辑与只读模式
- 行号显示与自适应宽度
- 自动换行测量与滚动同步
- 隐藏镜像 div 的测量技术
- ResizeObserver 与 requestAnimationFrame 的优化策略
- Tab 键缩进处理与光标位置保持
- 组件属性接口与事件处理逻辑
- 使用示例、定制选项与性能优化建议

## 项目结构
CodeEditor 组件位于 UI 层，配合全局样式与工具函数使用；在多个业务页面中被复用，如文件编辑器面板、技能文件编辑器、模板编辑器等。

```mermaid
graph TB
subgraph "UI 层"
CE["CodeEditor<br/>src/components/ui/code-editor.tsx"]
UWW["useWordWrap Hook<br/>src/hooks/use-word-wrap.ts"]
end
subgraph "业务页面"
FFE["Forge 文件编辑器<br/>src/components/layout/forge-file-editor.tsx"]
SFE["技能文件编辑器<br/>src/components/manage/skill-file-editor.tsx"]
TEP["模板编辑器面板<br/>src/components/marketplace/template-editor-panel.tsx"]
end
subgraph "通用资源"
UTIL["工具函数 cn<br/>src/lib/utils.ts"]
CSS["全局样式<br/>src/app/globals.css"]
TYPES["类型定义<br/>src/lib/types.ts"]
end
FFE --> CE
SFE --> CE
TEP --> CE
CE --> UWW
CE --> UTIL
CE --> CSS
CE --> TYPES
```

图表来源
- [src/components/ui/code-editor.tsx:1-170](file://src/components/ui/code-editor.tsx#L1-L170)
- [src/hooks/use-word-wrap.ts:1-26](file://src/hooks/use-word-wrap.ts#L1-L26)
- [src/components/layout/forge-file-editor.tsx:1-205](file://src/components/layout/forge-file-editor.tsx#L1-L205)
- [src/components/manage/skill-file-editor.tsx:1-134](file://src/components/manage/skill-file-editor.tsx#L1-L134)
- [src/components/marketplace/template-editor-panel.tsx:600-623](file://src/components/marketplace/template-editor-panel.tsx#L600-L623)
- [src/lib/utils.ts:1-7](file://src/lib/utils.ts#L1-L7)
- [src/app/globals.css:1-176](file://src/app/globals.css#L1-L176)
- [src/lib/types.ts:1-221](file://src/lib/types.ts#L1-L221)

章节来源
- [src/components/ui/code-editor.tsx:1-170](file://src/components/ui/code-editor.tsx#L1-L170)
- [src/hooks/use-word-wrap.ts:1-26](file://src/hooks/use-word-wrap.ts#L1-L26)
- [src/components/layout/forge-file-editor.tsx:1-205](file://src/components/layout/forge-file-editor.tsx#L1-L205)
- [src/components/manage/skill-file-editor.tsx:1-134](file://src/components/manage/skill-file-editor.tsx#L1-L134)
- [src/components/marketplace/template-editor-panel.tsx:600-623](file://src/components/marketplace/template-editor-panel.tsx#L600-L623)
- [src/lib/utils.ts:1-7](file://src/lib/utils.ts#L1-L7)
- [src/app/globals.css:1-176](file://src/app/globals.css#L1-L176)
- [src/lib/types.ts:1-221](file://src/lib/types.ts#L1-L221)

## 核心组件
CodeEditor 是一个基于原生 textarea 的高性能编辑器，具备以下特性：
- 基于 React Hooks 的状态管理与副作用控制
- 行号区域与主编辑区的同步滚动
- 可选的自动换行与精确行高测量
- 隐藏镜像 div 技术用于测量换行后的实际高度
- ResizeObserver + requestAnimationFrame 的节流测量
- Tab 键插入两空格缩进并保持光标位置
- 只读模式禁用输入并改变光标样式

章节来源
- [src/components/ui/code-editor.tsx:6-13](file://src/components/ui/code-editor.tsx#L6-L13)
- [src/components/ui/code-editor.tsx:15-169](file://src/components/ui/code-editor.tsx#L15-L169)

## 架构总览
CodeEditor 采用“主编辑区 + 行号区 + 隐藏镜像”的布局，通过同步滚动与测量机制保证视觉一致性与性能。

```mermaid
graph TB
TA["textareaRef<br/>主编辑区"] --> LN["lineNumbersRef<br/>行号区"]
TA --> MIR["mirrorRef<br/>隐藏镜像 div"]
MIR --> MEA["测量逻辑<br/>计算每行真实高度"]
TA --> OBS["ResizeObserver<br/>监听尺寸变化"]
OBS --> RAF["requestAnimationFrame<br/>批量测量"]
TA --> SCROLL["onScroll 同步滚动"]
LN --> SCROLL
TA --> KEY["onKeyDown 处理 Tab 缩进"]
KEY --> CUR["光标位置恢复"]
```

图表来源
- [src/components/ui/code-editor.tsx:150-165](file://src/components/ui/code-editor.tsx#L150-L165)
- [src/components/ui/code-editor.tsx:119-143](file://src/components/ui/code-editor.tsx#L119-L143)
- [src/components/ui/code-editor.tsx:104-116](file://src/components/ui/code-editor.tsx#L104-L116)
- [src/components/ui/code-editor.tsx:76-80](file://src/components/ui/code-editor.tsx#L76-L80)
- [src/components/ui/code-editor.tsx:82-96](file://src/components/ui/code-editor.tsx#L82-L96)
- [src/components/ui/code-editor.tsx:67-74](file://src/components/ui/code-editor.tsx#L67-L74)
- [src/components/ui/code-editor.tsx:64-64](file://src/components/ui/code-editor.tsx#L64-L64)

## 详细组件分析

### 属性接口与事件处理
- 属性
  - value: 当前编辑内容
  - onChange: 内容变更回调
  - readOnly: 是否只读
  - className: 容器类名
  - language: 语言提示（用于 UI 语义）
  - wordWrap: 是否启用自动换行
- 事件
  - onScroll: 同步滚动到行号区
  - onKeyDown: 处理 Tab 缩进与光标恢复
  - onChange: 透传原生 textarea 的值更新

章节来源
- [src/components/ui/code-editor.tsx:6-13](file://src/components/ui/code-editor.tsx#L6-L13)
- [src/components/ui/code-editor.tsx:150-165](file://src/components/ui/code-editor.tsx#L150-L165)
- [src/components/ui/code-editor.tsx:82-96](file://src/components/ui/code-editor.tsx#L82-L96)

### 行号显示与自适应宽度
- 行号容器根据行数动态调整宽度，避免过宽或过窄
- 在未启用自动换行时，行高为固定值；启用自动换行时，按测量结果逐行设置高度

章节来源
- [src/components/ui/code-editor.tsx:119-143](file://src/components/ui/code-editor.tsx#L119-L143)
- [src/components/ui/code-editor.tsx:98-99](file://src/components/ui/code-editor.tsx#L98-L99)

### 自动换行测量与隐藏镜像 div 技术
- 隐藏镜像 div 复制 textarea 的字体、字号、行高与宽度，确保逻辑行与渲染行一致
- 测量每个逻辑行的高度，用于行号区逐行高度适配
- 使用 ResizeObserver 监听主编辑区尺寸变化，结合 requestAnimationFrame 批量执行测量，避免频繁重排

```mermaid
flowchart TD
Start(["开始"]) --> CheckWrap{"是否启用自动换行？"}
CheckWrap --> |否| ResetHeights["清空行高数组"]
CheckWrap --> |是| GetStyle["获取 textarea 计算样式"]
GetStyle --> CalcWidth["计算内容宽度含内边距"]
CalcWidth --> WidthOK{"宽度有效？"}
WidthOK --> |否| End(["结束"])
WidthOK --> |是| MirrorSet["设置镜像宽度"]
MirrorSet --> SplitLines["按换行符拆分内容"]
SplitLines --> Loop["遍历每行创建临时 div 并测量高度"]
Loop --> PushHeights["收集行高数组"]
PushHeights --> SetHeights["更新行高状态"]
SetHeights --> End
```

图表来源
- [src/components/ui/code-editor.tsx:29-74](file://src/components/ui/code-editor.tsx#L29-L74)
- [src/components/ui/code-editor.tsx:104-116](file://src/components/ui/code-editor.tsx#L104-L116)

章节来源
- [src/components/ui/code-editor.tsx:29-74](file://src/components/ui/code-editor.tsx#L29-L74)
- [src/components/ui/code-editor.tsx:104-116](file://src/components/ui/code-editor.tsx#L104-L116)

### 滚动同步机制
- 主编辑区滚动时，同步设置行号区的 scrollTop，保持视觉对齐
- 使用 useCallback 缓存回调以减少重渲染

章节来源
- [src/components/ui/code-editor.tsx:76-80](file://src/components/ui/code-editor.tsx#L76-L80)

### Tab 键缩进处理与光标位置保持
- 拦截 Tab 键，阻止默认行为，改为插入两个空格
- 通过 requestAnimationFrame 在下一次渲染后恢复光标位置，确保用户感知连续输入

章节来源
- [src/components/ui/code-editor.tsx:82-96](file://src/components/ui/code-editor.tsx#L82-L96)

### 只读模式实现细节
- readOnly 属性直接传递给 textarea
- 样式中为只读态设置默认光标，避免误点击编辑
- 占位符在只读模式下不显示

章节来源
- [src/components/ui/code-editor.tsx:156-165](file://src/components/ui/code-editor.tsx#L156-L165)

### 组件使用示例与定制选项
- 在文件编辑器中作为源码编辑器使用，并支持预览/拆分视图
- 在技能文件编辑器中支持“编辑/预览/拆分”三种模式
- 通过 useWordWrap Hook 控制自动换行开关，并持久化到本地存储

章节来源
- [src/components/layout/forge-file-editor.tsx:190-195](file://src/components/layout/forge-file-editor.tsx#L190-L195)
- [src/components/manage/skill-file-editor.tsx:107-112](file://src/components/manage/skill-file-editor.tsx#L107-L112)
- [src/components/marketplace/template-editor-panel.tsx:608-613](file://src/components/marketplace/template-editor-panel.tsx#L608-L613)
- [src/hooks/use-word-wrap.ts:13-25](file://src/hooks/use-word-wrap.ts#L13-L25)

## 依赖关系分析
- 组件依赖
  - React Hooks：useRef、useState、useEffect、useCallback
  - 工具函数 cn：用于合并 Tailwind 类名
  - 全局样式：提供主题色、滚动条样式与基础字体设置
  - 类型定义：用于全局工作空间 ID 等上下文信息
- 业务集成
  - 文件编辑器、技能文件编辑器、模板编辑器均以相同接口调用 CodeEditor
  - useWordWrap 提供跨页面的换行偏好状态与切换

```mermaid
graph LR
CE["CodeEditor"] --> REACT["React Hooks"]
CE --> UTIL["cn 工具函数"]
CE --> CSS["全局样式"]
CE --> TYPES["类型定义"]
FFE["Forge 文件编辑器"] --> CE
SFE["技能文件编辑器"] --> CE
TEP["模板编辑器面板"] --> CE
UWW["useWordWrap"] --> FFE
UWW --> SFE
UWW --> TEP
```

图表来源
- [src/components/ui/code-editor.tsx:1-7](file://src/components/ui/code-editor.tsx#L1-L7)
- [src/lib/utils.ts:4-6](file://src/lib/utils.ts#L4-L6)
- [src/app/globals.css:136-175](file://src/app/globals.css#L136-L175)
- [src/lib/types.ts:1-3](file://src/lib/types.ts#L1-L3)
- [src/components/layout/forge-file-editor.tsx:47-47](file://src/components/layout/forge-file-editor.tsx#L47-L47)
- [src/components/manage/skill-file-editor.tsx:25-25](file://src/components/manage/skill-file-editor.tsx#L25-L25)
- [src/components/marketplace/template-editor-panel.tsx:612-612](file://src/components/marketplace/template-editor-panel.tsx#L612-L612)

章节来源
- [src/components/ui/code-editor.tsx:1-7](file://src/components/ui/code-editor.tsx#L1-L7)
- [src/lib/utils.ts:4-6](file://src/lib/utils.ts#L4-L6)
- [src/app/globals.css:136-175](file://src/app/globals.css#L136-L175)
- [src/lib/types.ts:1-3](file://src/lib/types.ts#L1-L3)
- [src/components/layout/forge-file-editor.tsx:47-47](file://src/components/layout/forge-file-editor.tsx#L47-L47)
- [src/components/manage/skill-file-editor.tsx:25-25](file://src/components/manage/skill-file-editor.tsx#L25-L25)
- [src/components/marketplace/template-editor-panel.tsx:612-612](file://src/components/marketplace/template-editor-panel.tsx#L612-L612)

## 性能考量
- 测量节流
  - 使用 requestAnimationFrame 将多次测量合并到下一帧，避免频繁布局抖动
  - ResizeObserver 监听尺寸变化，仅在必要时触发测量
- DOM 操作最小化
  - 镜像 div 仅复制必要的样式与宽度，内容按行动态创建与销毁
  - 行高数组长度与当前行数一致时才启用可变高度，避免不必要的样式计算
- 输入体验优化
  - Tab 缩进通过 requestAnimationFrame 恢复光标，减少闪烁感
  - 只读模式禁用输入，降低事件处理开销

章节来源
- [src/components/ui/code-editor.tsx:64-74](file://src/components/ui/code-editor.tsx#L64-L74)
- [src/components/ui/code-editor.tsx:92-94](file://src/components/ui/code-editor.tsx#L92-L94)
- [src/components/ui/code-editor.tsx:98-99](file://src/components/ui/code-editor.tsx#L98-L99)

## 故障排查指南
- 行高不正确
  - 检查是否启用了自动换行；未启用时不会进行行高测量
  - 确认镜像 div 的宽度与主编辑区内容宽度一致
- 滚动不同步
  - 确保 onScroll 回调已绑定且未被覆盖
  - 检查行号容器的 scrollTop 设置逻辑
- Tab 缩进无效
  - 确认 onKeyDown 已绑定到 textarea
  - 检查 onChange 是否正确更新 value 并在下一次渲染后恢复光标
- 自动换行测量不生效
  - 确认 ResizeObserver 支持环境
  - 检查 requestAnimationFrame 是否被取消或阻塞

章节来源
- [src/components/ui/code-editor.tsx:29-74](file://src/components/ui/code-editor.tsx#L29-L74)
- [src/components/ui/code-editor.tsx:76-80](file://src/components/ui/code-editor.tsx#L76-L80)
- [src/components/ui/code-editor.tsx:82-96](file://src/components/ui/code-editor.tsx#L82-L96)
- [src/components/ui/code-editor.tsx:67-74](file://src/components/ui/code-editor.tsx#L67-L74)

## 结论
CodeEditor 通过隐藏镜像 div 与 ResizeObserver + requestAnimationFrame 的组合，实现了高精度的自动换行测量与流畅的滚动同步；同时以最小的 DOM 操作与事件处理开销，提供了良好的用户体验。其简洁的属性接口与广泛的业务集成，使其成为项目中多场景编辑器的理想选择。

## 附录
- 使用建议
  - 在需要精确行高与滚动对齐的场景启用自动换行
  - 对长文档建议开启自动换行以提升可读性
  - 在只读场景中禁用输入并隐藏占位符，提升可用性
- 定制选项
  - 通过 className 自定义容器样式
  - 通过 language 属性提供语言提示（不影响功能）
  - 通过 useWordWrap 控制换行偏好并在多页面间共享

章节来源
- [src/components/ui/code-editor.tsx:6-13](file://src/components/ui/code-editor.tsx#L6-L13)
- [src/hooks/use-word-wrap.ts:13-25](file://src/hooks/use-word-wrap.ts#L13-L25)
- [src/components/layout/forge-file-editor.tsx:160-167](file://src/components/layout/forge-file-editor.tsx#L160-L167)
- [src/components/manage/skill-file-editor.tsx:89-95](file://src/components/manage/skill-file-editor.tsx#L89-L95)