Files
ai-diff-preview/PROJECT_INIT.md
T
2026-06-18 07:58:54 +08:00

628 lines
28 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AI Code Diff Preview - VSCode 插件架构文档
## 一、项目概述
### 1.1 项目名称
**AI Code Diff Preview** — 类 Cursor AI 代码差异预览 VSCode 插件
### 1.2 项目目标
在 VSCode 中实现类似 Cursor 的 Accept/Reject 差异预览功能:
- AI 代码编辑自动收集到变更集
- 调用 **VSCode 内置 Diff 编辑器** 展示差异(不自定义 Webview)
- 红绿对应的 **浮动 Accept/Reject 标签**,固定在每次变更和每个文件变更上方
- 逐文件 / 逐次编辑的精细化 Accept/Reject 操作
- 多文件修改统一管理
### 1.3 核心价值
- **原生体验**:直接使用 VSCode 内置 `vscode.diff` 命令,风格统一、交互一致
- **安全隔离**:AI 编辑期间可选择不弹窗打断,对话结束后统一审查
- **浮动操作**:Accept/Reject 标签固定在编辑器顶部/变更行上方,不干扰阅读
- **精细控制**:支持单次编辑粒度(每次 Edit/Write 独立审核)、文件粒度 Accept/Reject
- **灵活触发**:可配置「每次编辑自动弹 Diff」「对话结束后统一弹完整多文件预览」
---
## 二、技术选型
### 2.1 核心技术栈
| 技术领域 | 选型方案 | 理由 |
|---------|---------|------|
| **插件框架** | VSCode Extension API | 官方原生支持,API 完善 |
| **开发语言** | TypeScript (ES2022) | 类型安全,VSCode 插件官方推荐 |
| **Diff 渲染** | `vscode.commands.executeCommand('vscode.diff', ...)` | **VSCode 内置 Diff 编辑器**,原生交互,无需自建 Webview |
| **浮动 UI** | `vscode.window.createTextEditorDecorationType` + `after` | 编辑器内 Accept/Reject 标签(非 Webview) |
| **Diff 算法** | `diff` 库 (Myers 算法) | 行级差异计算,用于数据层变更追踪 |
| **构建工具** | esbuild | 快速打包,VSCode 插件社区标准 |
| **测试框架** | Vitest | 现代化单元测试 |
| **代码规范** | ESLint + Prettier | 代码质量保证 |
### 2.2 关键依赖
```json
{
"dependencies": {
"diff": "^5.0.0", // Diff 算法核心(ChangeSetManager 中行级对比)
"uuid": "^9.0.0" // 变更块/变更集唯一标识
},
"devDependencies": {
"@types/vscode": "^1.85.0",
"@types/diff": "^5.0.0",
"@types/uuid": "^9.0.0",
"esbuild": "^0.19.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0"
}
}
```
### 2.3 VSCode API 关键模块
| API 模块 | 用途 |
|---------|------|
| `vscode.commands.executeCommand('vscode.diff', uri1, uri2, title)` | **VSCode 内置 Diff 编辑器**,核心展示方案 |
| `vscode.workspace.createFileSystemWatcher` | 监听 `.claude/hooks/pending.json` 触发文件 |
| `vscode.window.createTextEditorDecorationType` | 红绿色高亮 + 浮动 Accept/Reject 标签 |
| `vscode.window.showInformationMessage` | 对话结束后弹出通知 |
| `vscode.commands.registerCommand` | 命令注册(快捷键绑定的操作) |
| `vscode.workspace.applyEdit` | 原子化文件写入 |
| `vscode.Uri.parse('untitled:...')` | 生成临时虚拟文档用于 Diff 对比 |
---
## 三、架构设计
### 3.1 工作流程
```
┌─────────────────────────────────────────────────────────────────┐
│ Claude 对话中 │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Edit 工具 │ │ Write 工具 │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ 写入 .claude/hooks/pending.json │ ← PostToolUse 触发 │
│ │ { type, filePath, oldString, │ │
│ │ newString, toolName, timestamp } │ │
│ └────────────────┬─────────────────────┘ │
│ │ │
├───────────────────┼──────────────────────────────────────────────┤
│ VSCode 插件 │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ │
│ │ FileSystemWatcher 监听到变更 │ ← extension.ts │
│ │ 读取 pending.json → 分发事件 │ │
│ └────────────────┬─────────────────────┘ │
│ │ │
│ ┌─────────┴─────────┐ │
│ ▼ ▼ │
│ toolName="Edit/Write" toolName="Stop" │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ HookHandler │ │ HookHandler │ │
│ │ .handleEdit() │ │ .handleStop() │ │
│ │ │ │ │ │
│ │ → recordEdit() │ │ → markReady() │ │
│ │ 收集变更到 │ │ 标记变更集就绪 │ │
│ │ ChangeSet │ │ │ │
│ │ │ │ → 根据配置决定行为: │ │
│ │ → 根据配置: │ │ showAllDiffsOnStop │ │
│ │ autoShowDiff- │ │ ? 弹出完整Diff预览 │ │
│ │ PerEdit │ │ : 仅显示通知 │ │
│ │ ? 立即弹出 │ └───────────┬──────────┘ │
│ │ 内置Diff │ │ │
│ │ : 静默收集 │ ▼ │
│ └─────────────────┘ ┌──────────────────────────────────────┐ │
│ │ 打开完整 Diff 文件列表 (QuickPick │ │
│ │ 或 TreeView) │ │
│ │ ┌────────────────────────────────┐ │ │
│ │ │ 📄 src/file1.ts [+3 -2] [✓][✗]│ │ │
│ │ │ 📄 src/file2.ts [+10 -0] [✓][✗]│ │ │
│ │ │ 📄 src/file3.ts [+1 -5] [✓][✗]│ │ │
│ │ └────────────────────────────────┘ │ │
│ │ 点击某文件 → 打开 VSCode 内置 Diff │ │
│ └───────────────────┬──────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ VSCode 原生 Diff 编辑器 │ │
│ │ ┌─────────────────────────────────────────────────────┐ │ │
│ │ │ [浮动标签] ✓ Accept本次更改 ✗ Reject本次更改 │ │ │
│ │ │ ┌──────────────────┬──────────────────────────┐ │ │
│ │ │ │ 原始代码 (左侧) │ AI 修改后 (右侧) │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ - 红色删除行 │ + 绿色新增行 │ │ │
│ │ │ │ │ │ │ │
│ │ │ └──────────────────┴──────────────────────────┘ │ │
│ │ └─────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ Accept: oldContent → newContent 写入磁盘 │
│ Reject: newContent → oldContent 恢复 / 新建文件删除 │
└──────────────────────────────────────────────────────────────────┘
```
### 3.2 核心模块划分
```
src/
├── extension.ts # 插件入口
│ ├── 初始化 ChangeSetManager、HookHandler
│ ├── 注册所有命令(acceptChunk/rejectChunk/acceptAll/rejectAll/showDiff)
│ ├── 注册 FileSystemWatcher 监听 pending.json
│ └── 检查启动时已有 pending 文件
│
├── trigger/
│ └── hookHandler.ts # Hook 事件处理器
│ ├── handleEdit() # PostToolUse → 收集变更 + 可选自动弹 Diff
│ └── handleStop() # 对话结束 → 可选弹出完整文件列表
│
├── snapshot/
│ └── snapshotManager.ts # 变更集管理器 (ChangeSetManager)
│ ├── recordEdit() # 记录每次编辑(独立 EditRecord,不合并)
│ ├── markReady() # 标记变更集就绪
│ ├── applyEditRecord() # 接受:还原 oldString→newString 到磁盘
│ ├── rejectEditRecord() # 拒绝:还原 newString→oldString 到磁盘
│ ├── applyFileChange() # 接受整个文件变更
│ ├── rejectFileChange() # 拒绝整个文件变更
│ └── computeDiffLines() # 行级 Diff 计算
│
├── diff/
│ └── diffEngine.ts # Diff 计算引擎
│ ├── computeChunks() # 计算 DiffChunk 列表
│ ├── detectConflicts() # 冲突检测
│ └── applyChunks() # 批量应用(倒序避免行号偏移)
│
├── render/
│ ├── inlineDecorator.ts # 内联装饰器 + 浮动标签 ★ 核心 UI
│ │ ├── 新增行:绿色背景 + ✓ Accept 浮动标签
│ │ ├── 删除行:红色背景 + 删除线
│ │ ├── 修改区域:黄色背景高亮
│ │ ├── 浮动 Accept/Reject 标签(固定在编辑器视口顶部或变更行上方)
│ │ └── 冲突行:橙色边框
│ │
│ ├── diffViewer.ts # VSCode 内置 Diff 查看器 ★ 新增
│ │ ├── openDiff() # 调用 vscode.diff 命令展示单文件差异
│ │ ├── openDiffForEdit() # 展示单次 Edit 的差异
│ │ └── openAllDiffs() # 依次/并排展示所有文件差异
│ │
│ ├── fileListProvider.ts # 变更文件列表提供器 ★ 新增
│ │ └── QuickPick / TreeView 展示文件列表(含 Accept/Reject 按钮)
│ │
│ └── statusBar.ts # 状态栏管理器
│ └── 显示待处理变更数量 + 快速入口
│
├── transaction/
│ ├── acceptHandler.ts # Accept 处理器
│ │ ├── acceptEdit() # 接受单次编辑
│ │ ├── acceptFile() # 接受整个文件所有编辑
│ │ ├── acceptAll() # 接受全部
│ │ └── writeFileAtomic() # 原子化写入
│ │
│ └── rejectHandler.ts # Reject 处理器
│ ├── rejectEdit() # 拒绝单次编辑
│ ├── rejectFile() # 拒绝整个文件
│ ├── rejectAll() # 拒绝全部
│ └── cleanupUI() # 清理装饰器和状态
│
├── models/
│ ├── types.ts # 核心类型定义
│ └── constants.ts # 常量定义
│
└── __tests__/
└── diffEngine.test.ts # Diff 引擎单元测试
```
---
## 四、核心数据模型
### 4.1 变更集模型(含编辑粒度)
```typescript
// 一轮对话的变更集
interface ChangeSet {
id: string;
edits: EditRecord[]; // ★ 每次 Edit/Write 独立记录
createdAt: number;
status: 'collecting' | 'ready' | 'reviewed';
}
// ★ 单次编辑记录(不合并!每次 Edit/Write 都是一个独立 EditRecord)
interface EditRecord {
id: string;
filePath: string;
toolName: 'Edit' | 'Write';
timestamp: number;
/** 编辑前的文件快照 */
beforeContent: string;
/** 编辑后的文件快照 */
afterContent: string;
/** oldString → newString 的具体替换内容 */
oldString: string;
newString: string;
/** 本次编辑的 Diff 行 */
diffLines: DiffLine[];
/** 状态 */
status: 'pending' | 'accepted' | 'rejected';
}
// 文件维度的聚合变更(由多个 EditRecord 聚合而来)
interface FileChange {
id: string;
filePath: string;
type: 'create' | 'modify' | 'delete';
edits: EditRecord[]; // ★ 包含的所有编辑记录
/** 原始内容 = 所有编辑前的文件状态 */
originalContent: string;
/** 最新内容 = 所有编辑后的文件状态 */
latestContent: string;
/** 聚合 Diff */
diffLines: DiffLine[];
status: 'pending' | 'accepted' | 'rejected';
}
// 单行 Diff
interface DiffLine {
type: 'context' | 'add' | 'delete';
oldLineNum: number;
newLineNum: number;
content: string;
}
```
### 4.2 关键设计:编辑不合并
> **重要**:同一文件的多次 Edit/Write **不合并**为一个 FileChange。每次 Edit 都保留独立的 `EditRecord`,用户可以:
> - 按单次编辑粒度 Accept/Reject(精确控制每次 AI 修改)
> - 按文件粒度 Accept/Reject(接受/拒绝该文件的所有编辑)
> - 全局 Accept All / Reject All
### 4.3 Hook 触发事件
```typescript
// pending.json 文件格式
interface ClaudeHookEvent {
type: string;
filePath: string;
content: string;
oldString: string; // Edit: 替换前的文本片段
newString: string; // Edit: 替换后的文本片段 / Write: 完整内容
timestamp: number;
toolName: 'Edit' | 'Write' | 'Stop';
}
```
### 4.4 原始内容捕获策略
`ChangeSetManager.recordEdit()` 中的内容反推机制:
- **Edit**(首次编辑该文件):从当前文件中找到 `newString`,替换回 `oldString`,得到编辑前内容
- **Edit**(非首次):基于上一次 `EditRecord.afterContent` 作为本次的 `beforeContent`
- **Write**:无法获取原始内容,标记 `beforeContent = ''`,类型为 `create`
- **Write 后 Edit**:`beforeContent` 为 Write 写入后的文件内容
---
## 五、触发机制
### 5.1 文件监听模式
插件通过 `FileSystemWatcher` 监听 `.claude/hooks/pending.json` 文件的变更:
```
.claude/hooks/pending.json
│
▼ (create / change 事件)
extension.ts:
1. 读取文件内容
2. 删除 pending.json(防止重复处理)
3. 解析为 ClaudeHookEvent
4. 检查时间戳(10秒内有效)
5. 根据 toolName 分发:
- Edit/Write → hookHandler.handleEdit() → 收集 + 可选弹 Diff
- Stop → hookHandler.handleStop() → 可选弹完整文件列表
```
### 5.2 启动恢复
`extension.activate()` 时检查是否已有 pending 文件(上次对话遗留),时间窗口 15 秒内有效。
---
## 六、Diff 展示方案
### 6.1 使用 VSCode 内置 Diff 编辑器
不再自定义 Webview,直接调用 VSCode 原生命令:
```typescript
// diffViewer.ts
import * as vscode from 'vscode';
class DiffViewer {
/**
* 打开 VSCode 内置 Diff 编辑器展示单次编辑的差异
*/
async openDiffForEdit(editRecord: EditRecord): Promise<void> {
// 创建临时虚拟文档(不写入磁盘)
const beforeUri = vscode.Uri.parse(`untitled:before-${editRecord.id}.tmp`);
const afterUri = vscode.Uri.parse(`untitled:after-${editRecord.id}.tmp`);
// 写入临时内容
await this.writeTempContent(beforeUri, editRecord.beforeContent);
await this.writeTempContent(afterUri, editRecord.afterContent);
// 打开 VSCode 原生 Diff
await vscode.commands.executeCommand(
'vscode.diff',
beforeUri,
afterUri,
`${editRecord.filePath} (AI修改) — 原始 ↔ 变更后`
);
}
/**
* 展示整个文件的聚合变更
*/
async openDiffForFile(fileChange: FileChange): Promise<void> {
const beforeUri = vscode.Uri.parse(`untitled:before-file-${fileChange.id}.tmp`);
const afterUri = vscode.Uri.parse(`untitled:after-file-${fileChange.id}.tmp`);
await this.writeTempContent(beforeUri, fileChange.originalContent);
await this.writeTempContent(afterUri, fileChange.latestContent);
await vscode.commands.executeCommand(
'vscode.diff',
beforeUri,
afterUri,
`${fileChange.filePath} — 原始 ↔ 变更后`
);
}
}
```
### 6.2 浮动 Accept/Reject 标签
在 Diff 编辑器或当前编辑器中,使用 `TextEditorDecorationType` 的 `after` 属性在变更行上方渲染固定操作标签:
```typescript
// 浮动标签结构
// ┌─────────────────────────────────────────┐
// │ ✓ Accept本次更改 ✗ Reject本次更改 │ ← 固定在视口顶部
// │ [3行新增, 2行删除] │ ← 变更摘要
// │─────────────────────────────────────────│
// │ (Diff 编辑器内容区域) │
// │ + 绿色高亮新增行 │
// │ - 红色高亮删除行 │
// └─────────────────────────────────────────┘
```
标签实现方式:
- **编辑器中**:`createTextEditorDecorationType({ after: { contentText: ' ✓ Accept' } })` 在变更区域末尾添加
- **Diff 编辑器中**:利用 VSCode 原生 Diff 视图,在标题栏区域叠加操作按钮(通过 `vscode.window.onDidChangeActiveTextEditor` 跟踪)
- **替代方案**:使用 `vscode.window.createStatusBarItem` 在状态栏固定 Accept/Reject 按钮(更简单可靠)
### 6.3 文件列表 UI
对话结束后,通过 **QuickPick** 或 **TreeView** 展示所有变更文件:
```
┌──────────────────────────────────────────┐
│ AI Diff Review — 3 个文件有变更 │
│ │
│ 📄 src/utils.ts [+5 -2] [✓] [✗] │
│ 📄 src/index.ts [+10 -0] [✓] [✗] │
│ 📄 src/types.ts [+0 -3] [✓] [✗] │
│ │
│ ─────────────────────────────────────── │
│ [Accept All (3)] [Reject All (3)] │
└──────────────────────────────────────────┘
```
点击某文件 → 打开内置 Diff 编辑器展示该文件的差异。
---
## 七、配置项
```json
{
"aiDiffPreview.enableAutoTrigger": {
"type": "boolean",
"default": true,
"description": "启用自动触发(监听 pending.json)"
},
"aiDiffPreview.autoShowDiffPerEdit": {
"type": "boolean",
"default": false,
"description": "每次 Edit/Write 后自动弹出 VSCode 内置 Diff 编辑器。关闭时静默收集,等待 Stop 后统一展示"
},
"aiDiffPreview.showAllDiffsOnStop": {
"type": "boolean",
"default": true,
"description": "对话结束后弹出完整文件列表/QuickPick,展示所有变更文件。关闭时仅显示状态栏通知"
},
"aiDiffPreview.floatingLabelMode": {
"type": "string",
"default": "statusBar",
"enum": ["inline", "statusBar", "both"],
"description": "浮动 Accept/Reject 标签显示位置:inline=编辑器内文本后, statusBar=状态栏, both=两者都显示"
},
"aiDiffPreview.maxFileSize": {
"type": "number",
"default": 100000,
"description": "最大处理文件大小(字符数)"
}
}
```
### 配置组合行为矩阵
| autoShowDiffPerEdit | showAllDiffsOnStop | 行为 |
|:---:|:---:|------|
| false | true | **默认推荐**:编辑时静默收集,Stop 后弹出文件列表统一审查 |
| true | false | 每次编辑立即弹 Diff,Stop 后无额外操作 |
| true | true | 每次编辑弹 Diff + Stop 后再弹完整列表 |
| false | false | 仅状态栏显示待处理数量,用户手动触发 |
---
## 八、命令与快捷键
### 8.1 注册命令
| 命令 ID | 标题 | 说明 |
|---------|------|------|
| `aiDiffPreview.showDiffPanel` | AI Diff: 显示所有变更文件列表 | QuickPick 文件列表 |
| `aiDiffPreview.showCurrentFileDiff` | AI Diff: 查看当前文件 Diff | 打开当前活动文件的内置 Diff |
| `aiDiffPreview.acceptCurrentEdit` | AI Diff: 接受当前编辑 | 接受光标所在位置的编辑记录 |
| `aiDiffPreview.rejectCurrentEdit` | AI Diff: 拒绝当前编辑 | 拒绝光标所在位置的编辑记录 |
| `aiDiffPreview.acceptCurrentFile` | AI Diff: 接受当前文件所有编辑 | 接受当前文件全部编辑 |
| `aiDiffPreview.rejectCurrentFile` | AI Diff: 拒绝当前文件所有编辑 | 拒绝当前文件全部编辑 |
| `aiDiffPreview.acceptAll` | AI Diff: 接受所有变更 | 批量接受 |
| `aiDiffPreview.rejectAll` | AI Diff: 拒绝所有变更 | 批量拒绝 |
| `aiDiffPreview.nextDiff` | AI Diff: 下一个变更 | 跳转到下一个待处理的编辑 |
| `aiDiffPreview.prevDiff` | AI Diff: 上一个变更 | 跳转到上一个待处理的编辑 |
### 8.2 快捷键
| 快捷键 | 条件 | 功能 |
|--------|------|------|
| `Tab` | `aiDiffPreview.isActive` | 接受当前编辑块 |
| `Esc` | `aiDiffPreview.isActive` | 拒绝当前编辑块 |
| `Ctrl+Shift+A` | `aiDiffPreview.isActive` | Accept All |
| `Ctrl+Shift+R` | `aiDiffPreview.isActive` | Reject All |
| `Alt+↓` | `aiDiffPreview.isActive` | 下一个变更 |
| `Alt+↑` | `aiDiffPreview.isActive` | 上一个变更 |
| `Ctrl+Shift+D` | — | 显示所有变更文件列表 |
---
## 九、已知 Bug & 修复计划
### 9.1 Bug: Accept 后同文件再编辑不触发选项且 Diff 合并
**现象**:
1. 对文件 `A.ts` 进行修改 → 弹出 Accept/Reject 选项
2. 用户点击 Accept,修改写入磁盘
3. 对同一文件 `A.ts` 再次修改 → **不再弹出 Accept/Reject 选项**
4. 第二次修改的 Diff 内容被**合并到了第一次的 Diff 中**,无法独立审核
**根因分析**:
当前 `ChangeSetManager.recordEdit()` 的逻辑中,首次编辑该文件时捕获 `originalContent` 并创建 `FileChange`,但后续对同一文件的编辑会**更新同一个 `FileChange` 对象**(更新 `newContent` 和 `diffLines`),而不是创建独立的编辑记录。当用户 Accept 后,`FileChange.status` 变为 `accepted`,后续编辑找不到正确的状态机入口。
```typescript
// 当前代码 snapshotManager.ts 中的问题逻辑:
const existing = this.currentSet.changes.find(c => norm(c.filePath) === filePath);
if (existing) {
// ★ 问题:更新同一个 FileChange,导致多次编辑合并
existing.newContent = currentContent;
existing.diffLines = this.computeDiffLines(originalContent, currentContent);
}
```
**修复方案**:
1. **引入 `EditRecord` 粒度**:每次 Edit/Write 创建独立的 `EditRecord`(见 §4.1 数据模型)
2. **FileChange 作为聚合视图**:`FileChange.edits[]` 持有所有 `EditRecord`,支持按编辑粒度或文件粒度操作
3. **Accept 后状态重置**:Accept 某次编辑后,后续编辑创建新的 `EditRecord` 追加到 `FileChange.edits[]`
4. **独立 Diff 计算**:每个 `EditRecord` 计算自己的 `diffLines`,不再与之前的编辑合并
---
## 十、构建与开发
### 10.1 构建配置
```bash
# 构建:esbuild 打包 src/extension.ts → dist/extension.js
npm run build # esbuild --bundle --external:vscode --format=cjs --platform=node --minify
# 监听模式
npm run watch # 同上 + --sourcemap --watch
# 测试
npm run test # vitest run
npm run test:watch # vitest (watch 模式)
# 代码检查
npm run lint # eslint src --ext ts
# 打包
npm run package # vsce package
```
### 10.2 TypeScript 配置
- **Target**: ES2022
- **Module**: CommonJS
- **Strict**: true
- **输出**: `dist/` (含 declaration + sourceMap)
---
## 十一、测试策略
### 11.1 当前测试覆盖
| 测试 | 文件 | 覆盖内容 |
|------|------|---------|
| Diff 引擎 | `src/__tests__/diffEngine.test.ts` | `computeChunks`(新增/删除/修改/空文本)、`applyChunk`(新增块/删除块) |
### 11.2 待扩展测试
- `ChangeSetManager`:EditRecord 独立记录、多次编辑不合并、Accept 后再次编辑
- `HookHandler`:Edit/Stop 事件处理、autoShowDiffPerEdit 配置分支
- `DiffViewer`:vscode.diff 调用、虚拟文档生成
- **Bug 回归测试**:Accept → 再编辑 → 验证独立 EditRecord 生成
- 集成测试:完整 Edit → Stop → Diff → Accept/Reject 流程
---
## 十二、版本历史
| 版本 | 日期 | 说明 |
|------|------|------|
| **0.1.0** | 2026-06-17 | 项目初始化,Webview Review 面板架构 |
| **0.2.0** | 计划中 | ★ 重构为 VSCode 内置 Diff + 浮动标签 + EditRecord 粒度 + Bug 修复 |
### Git 提交记录
```
a7c992a refactor: 重构为 Webview Review 面板架构
3168e81 feat: 初始化 AI Code Diff Preview 插件项目
```
---
## 十三、待完成事项
### 高优先级(v0.2.0)
- [ ] **Bug 修复**:Accept 后同文件再编辑不触发选项、Diff 合并的问题(§9.1)
- [ ] **EditRecord 粒度重构**:ChangeSetManager 改为 `EditRecord[]` 独立记录模式
- [ ] **VSCode 内置 Diff 集成**:实现 `DiffViewer` 调用 `vscode.diff`
- [ ] **浮动 Accept/Reject 标签**:状态栏或编辑器内固定操作按钮
- [ ] **配置项实现**:`autoShowDiffPerEdit`、`showAllDiffsOnStop`、`floatingLabelMode`
### 中优先级
- [ ] **文件列表 UI**:QuickPick 文件列表 + Accept/Reject 按钮
- [ ] **冲突检测**:用户手动修改与 AI 建议冲突检测
- [ ] **状态栏集成**:显示待处理编辑数量
- [ ] 完善单元测试 + Bug 回归测试
### 低优先级
- [ ] Git 集成(生成 Commit 建议)
- [ ] 发布到 VSCode Marketplace
- [ ] CI/CD 配置(GitHub Actions)
---
## 十四、参考资料
- [VSCode Extension API](https://code.visualstudio.com/api)
- [VSCode 内置 Diff 命令](https://code.visualstudio.com/api/references/commands) (`vscode.diff`)
- [VSCode TextEditorDecorationType](https://code.visualstudio.com/api/references/vscode-api#TextEditorDecorationType)
- [Myers Diff Algorithm](https://blog.jcoglan.com/2017/02/12/the-myers-diff-algorithm-part-1/)
- [diff 库文档](https://github.com/kpdecker/jsdiff)
- [Cursor 官方文档](https://cursor.sh/docs)