mirror of
https://github.com/Gmaker689/ai-diff-preview.git
synced 2026-09-27 03:21:52 +08:00
625 lines
19 KiB
Markdown
625 lines
19 KiB
Markdown
|
|
# 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)
|