2026-06-18 07:58:54 +08:00
|
|
|
|
# AI Code Diff Preview - VSCode 插件架构文档
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
## 一、项目概述
|
|
|
|
|
|
|
|
|
|
|
|
### 1.1 项目名称
|
2026-06-18 07:58:54 +08:00
|
|
|
|
**AI Code Diff Preview** — 类 Cursor AI 代码差异预览 VSCode 插件
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
### 1.2 项目目标
|
2026-06-18 07:58:54 +08:00
|
|
|
|
在 VSCode 中实现类似 Cursor 的 Accept/Reject 差异预览功能:
|
|
|
|
|
|
- AI 代码编辑自动收集到变更集
|
|
|
|
|
|
- 调用 **VSCode 内置 Diff 编辑器** 展示差异(不自定义 Webview)
|
|
|
|
|
|
- 红绿对应的 **浮动 Accept/Reject 标签**,固定在每次变更和每个文件变更上方
|
|
|
|
|
|
- 逐文件 / 逐次编辑的精细化 Accept/Reject 操作
|
2026-06-17 21:11:33 +08:00
|
|
|
|
- 多文件修改统一管理
|
|
|
|
|
|
|
|
|
|
|
|
### 1.3 核心价值
|
2026-06-18 07:58:54 +08:00
|
|
|
|
- **原生体验**:直接使用 VSCode 内置 `vscode.diff` 命令,风格统一、交互一致
|
|
|
|
|
|
- **安全隔离**:AI 编辑期间可选择不弹窗打断,对话结束后统一审查
|
|
|
|
|
|
- **浮动操作**:Accept/Reject 标签固定在编辑器顶部/变更行上方,不干扰阅读
|
|
|
|
|
|
- **精细控制**:支持单次编辑粒度(每次 Edit/Write 独立审核)、文件粒度 Accept/Reject
|
|
|
|
|
|
- **灵活触发**:可配置「每次编辑自动弹 Diff」「对话结束后统一弹完整多文件预览」
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 二、技术选型
|
|
|
|
|
|
|
|
|
|
|
|
### 2.1 核心技术栈
|
|
|
|
|
|
|
|
|
|
|
|
| 技术领域 | 选型方案 | 理由 |
|
|
|
|
|
|
|---------|---------|------|
|
|
|
|
|
|
| **插件框架** | VSCode Extension API | 官方原生支持,API 完善 |
|
2026-06-18 07:58:54 +08:00
|
|
|
|
| **开发语言** | TypeScript (ES2022) | 类型安全,VSCode 插件官方推荐 |
|
|
|
|
|
|
| **Diff 渲染** | `vscode.commands.executeCommand('vscode.diff', ...)` | **VSCode 内置 Diff 编辑器**,原生交互,无需自建 Webview |
|
|
|
|
|
|
| **浮动 UI** | `vscode.window.createTextEditorDecorationType` + `after` | 编辑器内 Accept/Reject 标签(非 Webview) |
|
|
|
|
|
|
| **Diff 算法** | `diff` 库 (Myers 算法) | 行级差异计算,用于数据层变更追踪 |
|
2026-06-17 21:11:33 +08:00
|
|
|
|
| **构建工具** | esbuild | 快速打包,VSCode 插件社区标准 |
|
2026-06-18 07:58:54 +08:00
|
|
|
|
| **测试框架** | Vitest | 现代化单元测试 |
|
2026-06-17 21:11:33 +08:00
|
|
|
|
| **代码规范** | ESLint + Prettier | 代码质量保证 |
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 2.2 关键依赖
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"dependencies": {
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"diff": "^5.0.0", // Diff 算法核心(ChangeSetManager 中行级对比)
|
|
|
|
|
|
"uuid": "^9.0.0" // 变更块/变更集唯一标识
|
2026-06-17 21:11:33 +08:00
|
|
|
|
},
|
|
|
|
|
|
"devDependencies": {
|
|
|
|
|
|
"@types/vscode": "^1.85.0",
|
|
|
|
|
|
"@types/diff": "^5.0.0",
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"@types/uuid": "^9.0.0",
|
2026-06-17 21:11:33 +08:00
|
|
|
|
"esbuild": "^0.19.0",
|
|
|
|
|
|
"typescript": "^5.3.0",
|
|
|
|
|
|
"vitest": "^1.0.0"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 2.3 VSCode API 关键模块
|
|
|
|
|
|
|
|
|
|
|
|
| API 模块 | 用途 |
|
|
|
|
|
|
|---------|------|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
| `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 对比 |
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 三、架构设计
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 3.1 工作流程
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-06-18 07:58:54 +08:00
|
|
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
|
|
|
|
│ 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 恢复 / 新建文件删除 │
|
|
|
|
|
|
└──────────────────────────────────────────────────────────────────┘
|
2026-06-17 21:11:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 3.2 核心模块划分
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
src/
|
|
|
|
|
|
├── extension.ts # 插件入口
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ ├── 初始化 ChangeSetManager、HookHandler
|
|
|
|
|
|
│ ├── 注册所有命令(acceptChunk/rejectChunk/acceptAll/rejectAll/showDiff)
|
|
|
|
|
|
│ ├── 注册 FileSystemWatcher 监听 pending.json
|
|
|
|
|
|
│ └── 检查启动时已有 pending 文件
|
|
|
|
|
|
│
|
2026-06-17 21:11:33 +08:00
|
|
|
|
├── trigger/
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ └── hookHandler.ts # Hook 事件处理器
|
|
|
|
|
|
│ ├── handleEdit() # PostToolUse → 收集变更 + 可选自动弹 Diff
|
|
|
|
|
|
│ └── handleStop() # 对话结束 → 可选弹出完整文件列表
|
|
|
|
|
|
│
|
2026-06-17 21:11:33 +08:00
|
|
|
|
├── snapshot/
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ └── snapshotManager.ts # 变更集管理器 (ChangeSetManager)
|
|
|
|
|
|
│ ├── recordEdit() # 记录每次编辑(独立 EditRecord,不合并)
|
|
|
|
|
|
│ ├── markReady() # 标记变更集就绪
|
|
|
|
|
|
│ ├── applyEditRecord() # 接受:还原 oldString→newString 到磁盘
|
|
|
|
|
|
│ ├── rejectEditRecord() # 拒绝:还原 newString→oldString 到磁盘
|
|
|
|
|
|
│ ├── applyFileChange() # 接受整个文件变更
|
|
|
|
|
|
│ ├── rejectFileChange() # 拒绝整个文件变更
|
|
|
|
|
|
│ └── computeDiffLines() # 行级 Diff 计算
|
|
|
|
|
|
│
|
2026-06-17 21:11:33 +08:00
|
|
|
|
├── diff/
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ └── diffEngine.ts # Diff 计算引擎
|
|
|
|
|
|
│ ├── computeChunks() # 计算 DiffChunk 列表
|
|
|
|
|
|
│ ├── detectConflicts() # 冲突检测
|
|
|
|
|
|
│ └── applyChunks() # 批量应用(倒序避免行号偏移)
|
|
|
|
|
|
│
|
2026-06-17 21:11:33 +08:00
|
|
|
|
├── render/
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ ├── inlineDecorator.ts # 内联装饰器 + 浮动标签 ★ 核心 UI
|
|
|
|
|
|
│ │ ├── 新增行:绿色背景 + ✓ Accept 浮动标签
|
|
|
|
|
|
│ │ ├── 删除行:红色背景 + 删除线
|
|
|
|
|
|
│ │ ├── 修改区域:黄色背景高亮
|
|
|
|
|
|
│ │ ├── 浮动 Accept/Reject 标签(固定在编辑器视口顶部或变更行上方)
|
|
|
|
|
|
│ │ └── 冲突行:橙色边框
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ├── diffViewer.ts # VSCode 内置 Diff 查看器 ★ 新增
|
|
|
|
|
|
│ │ ├── openDiff() # 调用 vscode.diff 命令展示单文件差异
|
|
|
|
|
|
│ │ ├── openDiffForEdit() # 展示单次 Edit 的差异
|
|
|
|
|
|
│ │ └── openAllDiffs() # 依次/并排展示所有文件差异
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ ├── fileListProvider.ts # 变更文件列表提供器 ★ 新增
|
|
|
|
|
|
│ │ └── QuickPick / TreeView 展示文件列表(含 Accept/Reject 按钮)
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ └── statusBar.ts # 状态栏管理器
|
|
|
|
|
|
│ └── 显示待处理变更数量 + 快速入口
|
|
|
|
|
|
│
|
2026-06-17 21:11:33 +08:00
|
|
|
|
├── transaction/
|
|
|
|
|
|
│ ├── acceptHandler.ts # Accept 处理器
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ │ ├── acceptEdit() # 接受单次编辑
|
|
|
|
|
|
│ │ ├── acceptFile() # 接受整个文件所有编辑
|
|
|
|
|
|
│ │ ├── acceptAll() # 接受全部
|
|
|
|
|
|
│ │ └── writeFileAtomic() # 原子化写入
|
|
|
|
|
|
│ │
|
|
|
|
|
|
│ └── rejectHandler.ts # Reject 处理器
|
|
|
|
|
|
│ ├── rejectEdit() # 拒绝单次编辑
|
|
|
|
|
|
│ ├── rejectFile() # 拒绝整个文件
|
|
|
|
|
|
│ ├── rejectAll() # 拒绝全部
|
|
|
|
|
|
│ └── cleanupUI() # 清理装饰器和状态
|
|
|
|
|
|
│
|
2026-06-17 21:11:33 +08:00
|
|
|
|
├── models/
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│ ├── types.ts # 核心类型定义
|
2026-06-17 21:11:33 +08:00
|
|
|
|
│ └── constants.ts # 常量定义
|
2026-06-18 07:58:54 +08:00
|
|
|
|
│
|
|
|
|
|
|
└── __tests__/
|
|
|
|
|
|
└── diffEngine.test.ts # Diff 引擎单元测试
|
2026-06-17 21:11:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 四、核心数据模型
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 4.1 变更集模型(含编辑粒度)
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
```typescript
|
|
|
|
|
|
// 一轮对话的变更集
|
|
|
|
|
|
interface ChangeSet {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
edits: EditRecord[]; // ★ 每次 Edit/Write 独立记录
|
|
|
|
|
|
createdAt: number;
|
|
|
|
|
|
status: 'collecting' | 'ready' | 'reviewed';
|
|
|
|
|
|
}
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
// ★ 单次编辑记录(不合并!每次 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';
|
|
|
|
|
|
}
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
// 文件维度的聚合变更(由多个 EditRecord 聚合而来)
|
|
|
|
|
|
interface FileChange {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
filePath: string;
|
|
|
|
|
|
type: 'create' | 'modify' | 'delete';
|
|
|
|
|
|
edits: EditRecord[]; // ★ 包含的所有编辑记录
|
|
|
|
|
|
/** 原始内容 = 所有编辑前的文件状态 */
|
|
|
|
|
|
originalContent: string;
|
|
|
|
|
|
/** 最新内容 = 所有编辑后的文件状态 */
|
|
|
|
|
|
latestContent: string;
|
|
|
|
|
|
/** 聚合 Diff */
|
|
|
|
|
|
diffLines: DiffLine[];
|
|
|
|
|
|
status: 'pending' | 'accepted' | 'rejected';
|
|
|
|
|
|
}
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
// 单行 Diff
|
|
|
|
|
|
interface DiffLine {
|
|
|
|
|
|
type: 'context' | 'add' | 'delete';
|
|
|
|
|
|
oldLineNum: number;
|
|
|
|
|
|
newLineNum: number;
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
}
|
2026-06-17 21:11:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 4.2 关键设计:编辑不合并
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
> **重要**:同一文件的多次 Edit/Write **不合并**为一个 FileChange。每次 Edit 都保留独立的 `EditRecord`,用户可以:
|
|
|
|
|
|
> - 按单次编辑粒度 Accept/Reject(精确控制每次 AI 修改)
|
|
|
|
|
|
> - 按文件粒度 Accept/Reject(接受/拒绝该文件的所有编辑)
|
|
|
|
|
|
> - 全局 Accept All / Reject All
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 4.3 Hook 触发事件
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
```typescript
|
|
|
|
|
|
// pending.json 文件格式
|
|
|
|
|
|
interface ClaudeHookEvent {
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
filePath: string;
|
|
|
|
|
|
content: string;
|
|
|
|
|
|
oldString: string; // Edit: 替换前的文本片段
|
|
|
|
|
|
newString: string; // Edit: 替换后的文本片段 / Write: 完整内容
|
|
|
|
|
|
timestamp: number;
|
|
|
|
|
|
toolName: 'Edit' | 'Write' | 'Stop';
|
|
|
|
|
|
}
|
2026-06-17 21:11:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 4.4 原始内容捕获策略
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
`ChangeSetManager.recordEdit()` 中的内容反推机制:
|
|
|
|
|
|
|
|
|
|
|
|
- **Edit**(首次编辑该文件):从当前文件中找到 `newString`,替换回 `oldString`,得到编辑前内容
|
|
|
|
|
|
- **Edit**(非首次):基于上一次 `EditRecord.afterContent` 作为本次的 `beforeContent`
|
|
|
|
|
|
- **Write**:无法获取原始内容,标记 `beforeContent = ''`,类型为 `create`
|
|
|
|
|
|
- **Write 后 Edit**:`beforeContent` 为 Write 写入后的文件内容
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 五、触发机制
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 5.1 文件监听模式
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
插件通过 `FileSystemWatcher` 监听 `.claude/hooks/pending.json` 文件的变更:
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
```
|
|
|
|
|
|
.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() → 可选弹完整文件列表
|
2026-06-17 21:11:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 5.2 启动恢复
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
`extension.activate()` 时检查是否已有 pending 文件(上次对话遗留),时间窗口 15 秒内有效。
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
---
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 六、Diff 展示方案
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 6.1 使用 VSCode 内置 Diff 编辑器
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
不再自定义 Webview,直接调用 VSCode 原生命令:
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2026-06-18 07:58:54 +08:00
|
|
|
|
// diffViewer.ts
|
2026-06-17 21:11:33 +08:00
|
|
|
|
import * as vscode from 'vscode';
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
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修改) — 原始 ↔ 变更后`
|
|
|
|
|
|
);
|
2026-06-17 21:11:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 展示整个文件的聚合变更
|
|
|
|
|
|
*/
|
|
|
|
|
|
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} — 原始 ↔ 变更后`
|
|
|
|
|
|
);
|
2026-06-17 21:11:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 6.2 浮动 Accept/Reject 标签
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
在 Diff 编辑器或当前编辑器中,使用 `TextEditorDecorationType` 的 `after` 属性在变更行上方渲染固定操作标签:
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
```typescript
|
|
|
|
|
|
// 浮动标签结构
|
|
|
|
|
|
// ┌─────────────────────────────────────────┐
|
|
|
|
|
|
// │ ✓ Accept本次更改 ✗ Reject本次更改 │ ← 固定在视口顶部
|
|
|
|
|
|
// │ [3行新增, 2行删除] │ ← 变更摘要
|
|
|
|
|
|
// │─────────────────────────────────────────│
|
|
|
|
|
|
// │ (Diff 编辑器内容区域) │
|
|
|
|
|
|
// │ + 绿色高亮新增行 │
|
|
|
|
|
|
// │ - 红色高亮删除行 │
|
|
|
|
|
|
// └─────────────────────────────────────────┘
|
|
|
|
|
|
```
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
标签实现方式:
|
|
|
|
|
|
- **编辑器中**:`createTextEditorDecorationType({ after: { contentText: ' ✓ Accept' } })` 在变更区域末尾添加
|
|
|
|
|
|
- **Diff 编辑器中**:利用 VSCode 原生 Diff 视图,在标题栏区域叠加操作按钮(通过 `vscode.window.onDidChangeActiveTextEditor` 跟踪)
|
|
|
|
|
|
- **替代方案**:使用 `vscode.window.createStatusBarItem` 在状态栏固定 Accept/Reject 按钮(更简单可靠)
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 6.3 文件列表 UI
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
对话结束后,通过 **QuickPick** 或 **TreeView** 展示所有变更文件:
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
```
|
|
|
|
|
|
┌──────────────────────────────────────────┐
|
|
|
|
|
|
│ 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)] │
|
|
|
|
|
|
└──────────────────────────────────────────┘
|
2026-06-17 21:11:33 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
点击某文件 → 打开内置 Diff 编辑器展示该文件的差异。
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
---
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 七、配置项
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
```json
|
|
|
|
|
|
{
|
|
|
|
|
|
"aiDiffPreview.enableAutoTrigger": {
|
|
|
|
|
|
"type": "boolean",
|
|
|
|
|
|
"default": true,
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"description": "启用自动触发(监听 pending.json)"
|
2026-06-17 21:11:33 +08:00
|
|
|
|
},
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"aiDiffPreview.autoShowDiffPerEdit": {
|
2026-06-17 21:11:33 +08:00
|
|
|
|
"type": "boolean",
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"default": false,
|
|
|
|
|
|
"description": "每次 Edit/Write 后自动弹出 VSCode 内置 Diff 编辑器。关闭时静默收集,等待 Stop 后统一展示"
|
2026-06-17 21:11:33 +08:00
|
|
|
|
},
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"aiDiffPreview.showAllDiffsOnStop": {
|
2026-06-17 21:11:33 +08:00
|
|
|
|
"type": "boolean",
|
|
|
|
|
|
"default": true,
|
2026-06-18 07:58:54 +08:00
|
|
|
|
"description": "对话结束后弹出完整文件列表/QuickPick,展示所有变更文件。关闭时仅显示状态栏通知"
|
|
|
|
|
|
},
|
|
|
|
|
|
"aiDiffPreview.floatingLabelMode": {
|
|
|
|
|
|
"type": "string",
|
|
|
|
|
|
"default": "statusBar",
|
|
|
|
|
|
"enum": ["inline", "statusBar", "both"],
|
|
|
|
|
|
"description": "浮动 Accept/Reject 标签显示位置:inline=编辑器内文本后, statusBar=状态栏, both=两者都显示"
|
2026-06-17 21:11:33 +08:00
|
|
|
|
},
|
|
|
|
|
|
"aiDiffPreview.maxFileSize": {
|
|
|
|
|
|
"type": "number",
|
|
|
|
|
|
"default": 100000,
|
|
|
|
|
|
"description": "最大处理文件大小(字符数)"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 配置组合行为矩阵
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
| 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);
|
2026-06-17 21:11:33 +08:00
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
**修复方案**:
|
|
|
|
|
|
1. **引入 `EditRecord` 粒度**:每次 Edit/Write 创建独立的 `EditRecord`(见 §4.1 数据模型)
|
|
|
|
|
|
2. **FileChange 作为聚合视图**:`FileChange.edits[]` 持有所有 `EditRecord`,支持按编辑粒度或文件粒度操作
|
|
|
|
|
|
3. **Accept 后状态重置**:Accept 某次编辑后,后续编辑创建新的 `EditRecord` 追加到 `FileChange.edits[]`
|
|
|
|
|
|
4. **独立 Diff 计算**:每个 `EditRecord` 计算自己的 `diffLines`,不再与之前的编辑合并
|
|
|
|
|
|
|
2026-06-17 21:11:33 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 十、构建与开发
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 10.1 构建配置
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
```bash
|
|
|
|
|
|
# 构建:esbuild 打包 src/extension.ts → dist/extension.js
|
|
|
|
|
|
npm run build # esbuild --bundle --external:vscode --format=cjs --platform=node --minify
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
# 监听模式
|
|
|
|
|
|
npm run watch # 同上 + --sourcemap --watch
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
# 测试
|
|
|
|
|
|
npm run test # vitest run
|
|
|
|
|
|
npm run test:watch # vitest (watch 模式)
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
# 代码检查
|
|
|
|
|
|
npm run lint # eslint src --ext ts
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
# 打包
|
|
|
|
|
|
npm run package # vsce package
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
### 10.2 TypeScript 配置
|
|
|
|
|
|
|
|
|
|
|
|
- **Target**: ES2022
|
|
|
|
|
|
- **Module**: CommonJS
|
|
|
|
|
|
- **Strict**: true
|
|
|
|
|
|
- **输出**: `dist/` (含 declaration + sourceMap)
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 十一、测试策略
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
### 11.1 当前测试覆盖
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
| 测试 | 文件 | 覆盖内容 |
|
|
|
|
|
|
|------|------|---------|
|
|
|
|
|
|
| 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 流程
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 十二、版本历史
|
|
|
|
|
|
|
|
|
|
|
|
| 版本 | 日期 | 说明 |
|
|
|
|
|
|
|------|------|------|
|
|
|
|
|
|
| **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 插件项目
|
|
|
|
|
|
```
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
## 十三、待完成事项
|
|
|
|
|
|
|
|
|
|
|
|
### 高优先级(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)
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-18 07:58:54 +08:00
|
|
|
|
## 十四、参考资料
|
2026-06-17 21:11:33 +08:00
|
|
|
|
|
|
|
|
|
|
- [VSCode Extension API](https://code.visualstudio.com/api)
|
2026-06-18 07:58:54 +08:00
|
|
|
|
- [VSCode 内置 Diff 命令](https://code.visualstudio.com/api/references/commands) (`vscode.diff`)
|
|
|
|
|
|
- [VSCode TextEditorDecorationType](https://code.visualstudio.com/api/references/vscode-api#TextEditorDecorationType)
|
2026-06-17 21:11:33 +08:00
|
|
|
|
- [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)
|