Files
ai-diff-preview/PROJECT_INIT.md
T
Gmarker689 3168e81aa6 feat: 初始化 AI Code Diff Preview 插件项目
- 核心架构搭建:四层架构设计(触发层、隔离层、Diff引擎、UI层)
- 快照管理器:管理 Base/Suggest 双版本,隔离 AI 生成代码
- Diff 引擎:基于 Myers 算法计算差异,生成变更块
- 内联装饰器:红绿高亮显示删除/新增行
- CodeLens 提供器:提供 Accept/Reject 按钮
- 事务处理:Accept/Reject 原子化操作
- Claude Hooks 集成:支持 after_code_edit/after_write_file 事件
- 状态栏显示:实时显示变更状态
- 基础测试用例:7 个测试全部通过

技术栈:TypeScript + VSCode Extension API + diff 库 + esbuild
2026-06-17 21:11:33 +08:00

625 lines
19 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 代码差异预览插件
### 1.2 项目目标
实现类似 Cursor 的 Accept/Reject 差异预览功能,通过 Claude Hooks 触发,在 VSCode 中提供:
- AI 生成代码的临时隔离层
- 实时 Diff 差分预览
- 逐行/整块 Accept/Reject 操作
- 多文件修改统一管理
### 1.3 核心价值
- **安全隔离**: AI 生成的代码不直接覆盖本地文件
- **可视化对比**: 红绿高亮显示删除/新增内容
- **灵活控制**: 支持单行接受、整块拒绝等精细化操作
- **无缝集成**: 通过 Claude Hooks 自动触发,无需手动操作
---
## 二、技术选型
### 2.1 核心技术栈
| 技术领域 | 选型方案 | 理由 |
|---------|---------|------|
| **插件框架** | VSCode Extension API | 官方原生支持,API 完善 |
| **开发语言** | TypeScript | 类型安全,VSCode 插件官方推荐 |
| **Diff 算法** | `diff` 库 (Myers 算法) | 成熟稳定,支持行级/字符级差异 |
| **构建工具** | esbuild | 快速打包,VSCode 插件社区标准 |
| **测试框架** | Vitest + @vscode/test-electron | 单元测试 + 集成测试 |
| **代码规范** | ESLint + Prettier | 代码质量保证 |
### 2.2 关键依赖库
```json
{
"dependencies": {
"diff": "^5.0.0", // Diff 算法核心
"vscode-languageclient": "^9.0.0", // LSP 客户端(可选)
"uuid": "^9.0.0" // 变更块唯一标识
},
"devDependencies": {
"@types/vscode": "^1.85.0",
"@types/diff": "^5.0.0",
"esbuild": "^0.19.0",
"typescript": "^5.3.0",
"vitest": "^1.0.0"
}
}
```
### 2.3 VSCode API 关键模块
| API 模块 | 用途 |
|---------|------|
| `vscode.workspace.applyEdit` | 原子化文件编辑 |
| `vscode.window.createTextEditorDecorationType` | Diff 高亮装饰器 |
| `vscode.languages.registerCodeLensProvider` | Accept/Reject 按钮 |
| `vscode.workspace.onDidChangeTextDocument` | 文档变更监听 |
| `vscode.commands.registerCommand` | 命令注册 |
| `WebviewViewProvider` | 侧边栏 Diff 面板(可选) |
---
## 三、架构设计
### 3.1 分层架构
```
┌─────────────────────────────────────────────────────────────┐
│ 触发层 (Trigger Layer) │
│ Claude Hooks: after_code_edit / after_write_file │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 临时变更隔离层 (Suggestion Layer) │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Base 快照 │ │ Suggest 临时 │ │
│ │ (只读原始) │ │ (AI 生成) │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Diff 差分引擎 (Diff Engine) │
│ Myers 算法 → 变更块切割 → 冲突检测 → Chunk 管理 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 渲染 UI 层 (Render Layer) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ 内联装饰器 │ │ CodeLens │ │ Diff 面板 │ │
│ │ (行高亮) │ │ (Accept/ │ │ (多文件) │ │
│ │ │ │ Reject) │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ 提交/回写事务层 (Transaction Layer) │
│ Accept: 校验 → 原子写入 → LSP 刷新 → 清理临时层 │
│ Reject: 丢弃快照 → 清理 UI → 恢复原始视图 │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 核心模块划分
```
src/
├── extension.ts # 插件入口
├── trigger/
│ ├── hookHandler.ts # Claude Hooks 处理器
│ └── commandHandler.ts # 手动命令处理器
├── snapshot/
│ ├── snapshotManager.ts # 快照管理器
│ └── snapshotStore.ts # 快照存储
├── diff/
│ ├── diffEngine.ts # Diff 计算引擎
│ ├── chunkManager.ts # 变更块管理
│ └── conflictDetector.ts # 冲突检测
├── render/
│ ├── inlineDecorator.ts # 内联装饰器
│ ├── codeLensProvider.ts # CodeLens 提供器
│ ├── diffPanel.ts # Diff 预览面板
│ └── statusBar.ts # 状态栏
├── transaction/
│ ├── acceptHandler.ts # Accept 处理器
│ ├── rejectHandler.ts # Reject 处理器
│ └── transactionManager.ts # 事务管理器
├── models/
│ ├── types.ts # 类型定义
│ └── constants.ts # 常量定义
└── utils/
├── diffUtils.ts # Diff 工具函数
├── fileUtils.ts # 文件操作工具
└── logger.ts # 日志工具
```
---
## 四、功能需求
### 4.1 核心功能
#### 4.1.1 临时变更隔离层
- [ ] 捕获编辑前文件快照(Base 版本)
- [ ] 接收 AI 生成的 Patch 并存储为临时层
- [ ] 维护 Base 与 Suggest 双版本并行状态
- [ ] 支持会话级别的快照持久化
#### 4.1.2 Diff 差分引擎
- [ ] 实现 Myers 差分算法封装
- [ ] 将 Patch 切分为独立变更块(Chunk)
- [ ] 为每个 Chunk 生成唯一 ID、起止行号
- [ ] 支持行级和字符级差异对比
- [ ] 检测用户手动修改导致的冲突
#### 4.1.3 渲染 UI 层
- [ ] **内联装饰器**
- 新增行:绿色背景 + 左侧 ✓ 按钮
- 删除行:红色背景 + 删除线
- 修改行:黄色背景高亮
- [ ] **CodeLens**
- 单块 Accept 按钮
- 单块 Reject 按钮
- 整块 Accept All / Reject All
- [ ] **Diff 面板**(多文件场景)
- 左右分栏对比视图
- 文件树导航
- 全局操作按钮
#### 4.1.4 提交/回写事务层
- [ ] **Accept 流程**
- 校验无冲突
- 原子事务写入文件
- 触发 LSP 重新解析
- 清理临时层和 UI
- 记录变更日志
- [ ] **Reject 流程**
- 丢弃临时快照
- 清理装饰 UI
- 恢复原始视图
- 记录拒绝历史
### 4.2 辅助功能
#### 4.2.1 Claude Hooks 集成
- [ ] 监听 `after_code_edit` 事件
- [ ] 监听 `after_write_file` 事件
- [ ] 解析 Hook 传入的 Patch 数据
- [ ] 自动触发 Diff 预览流程
#### 4.2.2 快捷键支持
- [ ] `Tab` / `Enter`: 接受当前块
- [ ] `Esc`: 拒绝本次所有修改
- [ ] `Ctrl+Shift+A`: Accept All
- [ ] `Ctrl+Shift+R`: Reject All
#### 4.2.3 状态管理
- [ ] 状态栏显示当前 Diff 状态
- [ ] 显示待处理变更数量
- [ ] 提供快速操作入口
---
## 五、实现流程
### 5.1 阶段一:基础框架搭建 (Week 1)
```
Day 1-2: 项目初始化
├── 使用 yo code 脚手架创建项目
├── 配置 TypeScript、ESLint、Prettier
├── 配置 esbuild 构建流程
└── 设置测试环境
Day 3-5: 核心模型定义
├── 定义 TypeScript 接口和类型
├── 实现快照管理器基础结构
├── 实现 Diff 引擎基础封装
└── 编写单元测试
```
### 5.2 阶段二:Diff 引擎实现 (Week 2)
```
Day 1-3: Myers 算法集成
├── 集成 diff 库
├── 实现行级差异计算
├── 实现变更块切割逻辑
└── 测试各种代码变更场景
Day 4-5: 冲突检测
├── 实现 Base 与当前文件对比
├── 检测用户手动修改
├── 标记冲突变更块
└── 测试冲突场景
```
### 5.3 阶段三:UI 渲染层 (Week 3)
```
Day 1-3: 内联装饰器
├── 实现 DecorationType 定义
├── 实现行高亮渲染
├── 实现 Accept/Reject 按钮
└── 测试不同 Diff 场景
Day 4-5: CodeLens 集成
├── 实现 CodeLens Provider
├── 绑定 Accept/Reject 命令
├── 处理多块场景
└── 测试交互流程
```
### 5.4 阶段四:事务处理层 (Week 4)
```
Day 1-3: Accept 流程
├── 实现冲突校验
├── 实现原子化文件写入
├── 触发 LSP 刷新
├── 清理临时层和 UI
└── 测试 Accept 全流程
Day 4-5: Reject 流程
├── 实现快照丢弃
├── 实现 UI 清理
├── 实现视图恢复
└── 测试 Reject 全流程
```
### 5.5 阶段五:Hooks 集成与优化 (Week 5)
```
Day 1-3: Claude Hooks 集成
├── 实现 Hook 监听器
├── 解析 Patch 数据格式
├── 自动触发 Diff 预览
└── 测试端到端流程
Day 4-5: 优化与完善
├── 性能优化(大文件处理)
├── 快捷键绑定
├── 状态栏集成
├── 文档编写
└── 发布准备
```
---
## 六、关键技术实现
### 6.1 快照管理器
```typescript
// snapshotManager.ts
interface Snapshot {
id: string;
filePath: string;
baseContent: string;
suggestContent: string;
timestamp: number;
chunks: DiffChunk[];
}
class SnapshotManager {
private snapshots: Map<string, Snapshot> = new Map();
// 创建 Base 快照
async createBaseSnapshot(filePath: string): Promise<string> {
const content = await fs.readFile(filePath, 'utf-8');
const id = uuid();
this.snapshots.set(id, {
id,
filePath,
baseContent: content,
suggestContent: '',
timestamp: Date.now(),
chunks: []
});
return id;
}
// 存储 AI 生成的 Patch
async applyPatch(snapshotId: string, patch: string): Promise<void> {
const snapshot = this.snapshots.get(snapshotId);
if (!snapshot) throw new Error('Snapshot not found');
snapshot.suggestContent = patch;
snapshot.chunks = this.diffEngine.computeChunks(
snapshot.baseContent,
patch
);
}
}
```
### 6.2 Diff 引擎
```typescript
// diffEngine.ts
import { diffLines, Change } from 'diff';
interface DiffChunk {
id: string;
startLine: number;
endLine: number;
type: 'add' | 'delete' | 'modify';
content: string;
accepted?: boolean;
}
class DiffEngine {
computeChunks(base: string, suggest: string): DiffChunk[] {
const changes = diffLines(base, suggest);
const chunks: DiffChunk[] = [];
let lineOffset = 0;
changes.forEach((change, index) => {
if (change.added || change.removed) {
chunks.push({
id: `chunk-${index}`,
startLine: lineOffset,
endLine: lineOffset + (change.count || 0),
type: change.added ? 'add' : 'delete',
content: change.value
});
}
if (!change.removed) {
lineOffset += change.count || 0;
}
});
return chunks;
}
detectConflicts(base: string, current: string, suggest: string): DiffChunk[] {
// 检测用户手动修改与 AI 建议的冲突
const baseToCurrent = this.computeChunks(base, current);
const baseToSuggest = this.computeChunks(base, suggest);
return baseToSuggest.filter(suggestChunk => {
return baseToCurrent.some(currentChunk =>
this.isOverlapping(suggestChunk, currentChunk)
);
});
}
}
```
### 6.3 内联装饰器
```typescript
// inlineDecorator.ts
import * as vscode from 'vscode';
class InlineDecorator {
private addDecorationType: vscode.TextEditorDecorationType;
private deleteDecorationType: vscode.TextEditorDecorationType;
private modifyDecorationType: vscode.TextEditorDecorationType;
constructor() {
this.addDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(46, 160, 67, 0.15)',
isWholeLine: true,
gutterIconPath: new vscode.ThemeIcon('check').id,
gutterIconSize: 'contain'
});
this.deleteDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(248, 81, 73, 0.15)',
isWholeLine: true,
textDecoration: 'line-through'
});
this.modifyDecorationType = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(210, 153, 34, 0.15)',
isWholeLine: true
});
}
renderChunks(editor: vscode.TextEditor, chunks: DiffChunk[]) {
const addRanges: vscode.Range[] = [];
const deleteRanges: vscode.Range[] = [];
const modifyRanges: vscode.Range[] = [];
chunks.forEach(chunk => {
const range = new vscode.Range(
new vscode.Position(chunk.startLine, 0),
new vscode.Position(chunk.endLine, 0)
);
switch (chunk.type) {
case 'add':
addRanges.push(range);
break;
case 'delete':
deleteRanges.push(range);
break;
case 'modify':
modifyRanges.push(range);
break;
}
});
editor.setDecorations(this.addDecorationType, addRanges);
editor.setDecorations(this.deleteDecorationType, deleteRanges);
editor.setDecorations(this.modifyDecorationType, modifyRanges);
}
clearDecorations(editor: vscode.TextEditor) {
editor.setDecorations(this.addDecorationType, []);
editor.setDecorations(this.deleteDecorationType, []);
editor.setDecorations(this.modifyDecorationType, []);
}
}
```
### 6.4 Claude Hooks 处理器
```typescript
// hookHandler.ts
interface ClaudeHookEvent {
type: 'after_code_edit' | 'after_write_file';
filePath: string;
patch: string;
timestamp: number;
}
class HookHandler {
private snapshotManager: SnapshotManager;
private diffEngine: DiffEngine;
private decorator: InlineDecorator;
async handleHook(event: ClaudeHookEvent): Promise<void> {
// 1. 创建 Base 快照
const snapshotId = await this.snapshotManager.createBaseSnapshot(
event.filePath
);
// 2. 应用 AI Patch
await this.snapshotManager.applyPatch(snapshotId, event.patch);
// 3. 计算 Diff
const snapshot = this.snapshotManager.getSnapshot(snapshotId);
const chunks = this.diffEngine.computeChunks(
snapshot.baseContent,
snapshot.suggestContent
);
// 4. 渲染 Diff UI
const editor = vscode.window.activeTextEditor;
if (editor && editor.document.uri.fsPath === event.filePath) {
this.decorator.renderChunks(editor, chunks);
this.showDiffPanel(snapshot);
}
}
}
```
---
## 七、配置与扩展点
### 7.1 插件配置项
```json
{
"aiDiffPreview.enableAutoTrigger": {
"type": "boolean",
"default": true,
"description": "启用 Claude Hooks 自动触发"
},
"aiDiffPreview.showInlineButtons": {
"type": "boolean",
"default": true,
"description": "显示内联 Accept/Reject 按钮"
},
"aiDiffPreview.autoLint": {
"type": "boolean",
"default": true,
"description": "Diff 预览时自动运行 Lint 检查"
},
"aiDiffPreview.maxFileSize": {
"type": "number",
"default": 100000,
"description": "最大处理文件大小(字符数)"
}
}
```
### 7.2 命令注册
```json
{
"contributes": {
"commands": [
{
"command": "aiDiffPreview.acceptChunk",
"title": "AI Diff: 接受当前变更块"
},
{
"command": "aiDiffPreview.rejectChunk",
"title": "AI Diff: 拒绝当前变更块"
},
{
"command": "aiDiffPreview.acceptAll",
"title": "AI Diff: 接受所有变更"
},
{
"command": "aiDiffPreview.rejectAll",
"title": "AI Diff: 拒绝所有变更"
},
{
"command": "aiDiffPreview.showDiffPanel",
"title": "AI Diff: 显示 Diff 面板"
}
],
"keybindings": [
{
"command": "aiDiffPreview.acceptChunk",
"key": "tab",
"when": "aiDiffPreview.isActive"
},
{
"command": "aiDiffPreview.rejectAll",
"key": "escape",
"when": "aiDiffPreview.isActive"
}
]
}
}
```
---
## 八、测试策略
### 8.1 单元测试
- 快照管理器:创建、更新、删除快照
- Diff 引擎:各种代码变更场景
- 冲突检测:用户手动修改检测
### 8.2 集成测试
- 完整 Accept/Reject 流程
- 多文件 Diff 预览
- Claude Hooks 触发测试
### 8.3 E2E 测试
- 使用 @vscode/test-electron
- 模拟用户交互
- 验证 UI 渲染效果
---
## 九、发布与维护
### 9.1 发布清单
- [ ] 完善 README 文档
- [ ] 添加 CHANGELOG
- [ ] 配置 GitHub Actions CI/CD
- [ ] 发布到 VSCode Marketplace
- [ ] 收集用户反馈
### 9.2 后续迭代
- 支持更多 AI 模型(GPT-4、Gemini 等)
- 支持 Git 集成(生成 Commit 建议)
- 支持团队协作(共享 Diff 配置)
- 性能优化(大文件分块处理)
---
## 十、参考资料
- [VSCode Extension API](https://code.visualstudio.com/api)
- [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)