mirror of
https://github.com/Gmaker689/ai-diff-preview.git
synced 2026-09-27 07:21:51 +08:00
docs: 添加更详细的使用说明
This commit is contained in:
+286
-124
@@ -1,40 +1,45 @@
|
||||
# AI Code Diff Preview — 项目文档
|
||||
# AI Code Diff Preview — 架构文档
|
||||
|
||||
> **版本 0.2.0** | VSCode 插件,为 Claude Code 提供类 Cursor 的 AI 代码变更审查体验
|
||||
|
||||
## 一、功能概述
|
||||
|
||||
AI Code Diff Preview 是一个 VSCode 插件,为 Claude Code 提供类似 Cursor 的 AI 代码变更审查体验:
|
||||
|
||||
- **自动收集**:监听 Claude 的 Edit/Write 操作,静默收集所有文件变更
|
||||
- **总览面板**:Stop 后弹出 Webview 面板,展示每个文件的完整双栏 Diff
|
||||
- **内置 Diff**:点击单个文件打开 VSCode 原生 Diff 编辑器,状态栏显示 Accept/Reject 按钮
|
||||
- **粒度控制**:逐文件 Accept/Reject + 全局 Accept All / Reject All
|
||||
- **编辑合并**:同文件连续多次编辑自动合并为一个 Diff(前次未处理时);已处理后重新编辑则独立追踪
|
||||
- **QuickPick 总览**:Stop 后弹出文件列表,含 Accept All / Reject All
|
||||
- **内置 Diff**:点击文件打开 VSCode 原生 Diff 编辑器,状态栏显示 Accept/Reject 按钮
|
||||
- **粒度控制**:逐编辑 / 逐文件 / 全局 Accept/Reject
|
||||
- **编辑合并**:同文件连续编辑(前次未处理时)自动合并为一个 FileChange;已处理后重新编辑则独立追踪
|
||||
- **自动推进**:Accept/Reject 后自动打开下一个 pending 文件
|
||||
|
||||
---
|
||||
|
||||
## 二、项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── extension.ts # 插件入口,初始化模块 + 注册命令
|
||||
├── extension.ts # 插件入口: 初始化 + 命令注册 + 文件监听
|
||||
├── trigger/
|
||||
│ └── hookHandler.ts # Hook 事件处理 (Edit/Stop 分发)
|
||||
│ └── hookHandler.ts # Hook 事件处理 (Edit → 收集, Stop → QuickPick)
|
||||
├── snapshot/
|
||||
│ └── snapshotManager.ts # ★ 核心数据层: ChangeSetManager
|
||||
│ └── snapshotManager.ts # ★ 核心数据层: ChangeSetManager
|
||||
├── diff/
|
||||
│ └── diffEngine.ts # Diff 算法 (Myers)
|
||||
│ └── diffEngine.ts # Diff 算法 (遗留模块)
|
||||
├── render/
|
||||
│ ├── reviewPanel.ts # ★ 总览 Webview 面板
|
||||
│ ├── diffViewer.ts # ★ VSCode 内置 Diff 查看器 + accept/reject + 自动推进
|
||||
│ ├── statusBar.ts # ★ 状态栏 (Diff 模式按钮 / 摘要模式)
|
||||
│ ├── inlineDecorator.ts # 内联装饰器 (保留, 未启用)
|
||||
│ └── codeLensProvider.ts # CodeLens (保留, 未启用)
|
||||
│ ├── diffViewer.ts # ★ VSCode 内置 Diff + Accept/Reject + 自动推进
|
||||
│ ├── statusBar.ts # ★ 状态栏 (常驻摘要 + Diff 模式 Accept/Reject)
|
||||
│ ├── reviewPanel.ts # Webview 总览面板 (备用, 非主流程)
|
||||
│ ├── codeLensProvider.ts # CodeLens (编辑器行内 Accept/Reject)
|
||||
│ └── inlineDecorator.ts # 内联装饰器 (红绿高亮)
|
||||
├── transaction/
|
||||
│ ├── acceptHandler.ts # Accept 处理器 (legacy)
|
||||
│ └── rejectHandler.ts # Reject 处理器 (legacy)
|
||||
│ ├── acceptHandler.ts # Accept 处理器 (legacy)
|
||||
│ └── rejectHandler.ts # Reject 处理器 (legacy)
|
||||
└── models/
|
||||
├── types.ts # 类型定义
|
||||
└── constants.ts # 常量
|
||||
├── types.ts # 类型定义
|
||||
└── constants.ts # 常量 (命令 ID, 配置键, 颜色)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、核心数据模型
|
||||
|
||||
### 层级关系
|
||||
@@ -42,14 +47,16 @@ src/
|
||||
```
|
||||
ChangeSet (一轮对话)
|
||||
└── FileChange[] (每个文件一个)
|
||||
├── originalContent (首次编辑前的文件快照)
|
||||
├── latestContent (最新文件内容)
|
||||
├── diffLines[] (originalContent → latestContent 的聚合 Diff)
|
||||
├── status (pending | accepted | rejected)
|
||||
└── EditRecord[] (每次 Edit/Write 一条)
|
||||
├── originalContent (首次编辑前的文件快照)
|
||||
├── latestContent (最新文件内容)
|
||||
├── diffLines[] (originalContent → latestContent 的聚合 Diff)
|
||||
├── type (create | modify | delete)
|
||||
├── status (pending | accepted | rejected)
|
||||
└── EditRecord[] (每次 Edit/Write 一条, 不合并)
|
||||
├── beforeContent (本次编辑前快照)
|
||||
├── afterContent (本次编辑后快照)
|
||||
├── oldString / newString (精确替换内容)
|
||||
├── oldString (Edit: 被替换文本; Write: 空)
|
||||
├── newString (Edit: 替换后文本; Write: 完整内容)
|
||||
├── diffLines[] (本次编辑的独立 Diff)
|
||||
└── status (pending | accepted | rejected)
|
||||
```
|
||||
@@ -64,16 +71,27 @@ ChangeSet (一轮对话)
|
||||
└──→ 创建新 FileChange, 独立追踪
|
||||
```
|
||||
|
||||
**关键**: 查找 FileChange 时**优先匹配 pending 的**(而非用 `.find()` 取第一个),避免同文件多轮编辑时孤儿 FileChange 的 Bug。
|
||||
**关键**: 查找 FileChange 时**优先匹配 pending 的**(`changes.find(c => c.status === 'pending')`),避免同文件多轮编辑时孤儿 FileChange 的 Bug。
|
||||
|
||||
### 原始内容捕获
|
||||
|
||||
`captureOriginalContent()` 反推机制:
|
||||
|
||||
- **Edit (首次)**: 从当前文件中找到 `newString`,替换回 `oldString` → 编辑前内容
|
||||
- **Edit (非首次)**: 使用 `pendingChange.latestContent` 作为 `beforeContent`
|
||||
- **Write**: `originalContent = ''`,类型为 `create`
|
||||
- **缓存**: `originalContents` Map 避免重复捕获
|
||||
|
||||
---
|
||||
|
||||
## 四、完整流程
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Claude 对话中"] --> B["Edit / Write 工具调用"]
|
||||
B --> C["写入 .claude/hooks/pending.json"]
|
||||
B --> C["PostToolUse Hook<br/>写入 .claude/hooks/pending.json"]
|
||||
C --> D["FileSystemWatcher 监听到"]
|
||||
D --> E["读取并删除 pending.json"]
|
||||
D --> E["读取并删除 pending.json<br/>10s 时效校验"]
|
||||
E --> F{"toolName?"}
|
||||
|
||||
F -->|"Edit / Write"| G["hookHandler.handleEdit()"]
|
||||
@@ -81,161 +99,305 @@ flowchart TD
|
||||
H --> I{"同文件已有<br/>pending FileChange?"}
|
||||
I -->|"YES"| J["★ 合并: 追加 EditRecord<br/>更新聚合 Diff"]
|
||||
I -->|"NO"| K["新建 FileChange<br/>捕获 originalContent"]
|
||||
J --> L{"autoShowDiffPerEdit<br/>配置?"}
|
||||
J --> L{"autoShowDiffPerEdit?"}
|
||||
K --> L
|
||||
L -->|"true"| M["弹出 VSCode 内置 Diff"]
|
||||
L -->|"false"| N["静默收集, 不弹 UI"]
|
||||
L -->|"true"| M["diffViewer.showEditDiff()<br/>弹出 VSCode 内置 Diff"]
|
||||
L -->|"false"| N["静默收集"]
|
||||
|
||||
F -->|"Stop"| O["hookHandler.handleStop()"]
|
||||
O --> P["ChangeSetManager.markReady()"]
|
||||
P --> Q{"有 pending 变更?"}
|
||||
Q -->|"NO"| R["无操作"]
|
||||
Q -->|"YES"| S["★ 弹出总览 Webview 面板<br/>reviewPanel.show()"]
|
||||
Q -->|"YES"| S{"showAllDiffsOnStop?"}
|
||||
S -->|"true"| T["★ 弹出 QuickPick 文件列表<br/>hookHandler.showFileListPicker()"]
|
||||
S -->|"false"| U["状态栏通知<br/>'N 个文件有变更'"]
|
||||
|
||||
S --> T["总览面板: 每个文件一张卡片"]
|
||||
T --> U["卡片内含:<br/>— 文件信息 + 增减统计<br/>— 双栏 Diff (原始 vs 最新)<br/>— ✔ 接受 / ✘ 拒绝 按钮"]
|
||||
T --> V["顶部: Accept All / Reject All"]
|
||||
T --> V["QuickPick 列表:<br/>📄 file1.ts +3 -2 · 2编辑<br/>📄 file2.ts +10 -0 · 1编辑<br/>──────────────<br/>✅ Accept All<br/>❌ Reject All"]
|
||||
V --> W["点击文件"]
|
||||
V --> X["点击 Accept All"]
|
||||
V --> Y["点击 Reject All"]
|
||||
|
||||
U --> W["用户点击 ✔ 接受"]
|
||||
U --> X["用户点击 ✘ 拒绝"]
|
||||
V --> Y["用户点击 Accept All"]
|
||||
V --> Z["用户点击 Reject All"]
|
||||
W --> Z["diffViewer.showFileDiff()<br/>打开 VSCode 内置 Diff"]
|
||||
Z --> AA["状态栏右侧:<br/>$(check) Accept | $(close) Reject"]
|
||||
AA --> AB["用户操作 Accept/Reject"]
|
||||
AB --> AC["★ 自动推进 advanceToNext()"]
|
||||
AC --> AD["全部处理完 → 🎉 通知"]
|
||||
|
||||
W --> AA["acceptFile(fileId)<br/>写入 latestContent 到磁盘"]
|
||||
X --> AB["rejectFile(fileId)<br/>恢复 originalContent"]
|
||||
Y --> AC["acceptAll()<br/>遍历所有 pending 文件"]
|
||||
Z --> AD["rejectAll()<br/>遍历所有 pending 文件"]
|
||||
|
||||
AA --> AE["刷新总览面板 + 状态栏"]
|
||||
AB --> AE
|
||||
AC --> AE
|
||||
AD --> AE
|
||||
|
||||
T --> AF["用户点击文件卡片"]
|
||||
AF --> AG["打开 VSCode 内置 Diff 编辑器<br/>diffViewer.showFileDiff()"]
|
||||
AG --> AH["Diff 界面: 状态栏切换为<br/>$(check) Accept | $(close) Reject | $(check-all) 接受全部"]
|
||||
AH --> AI["用户操作 Accept/Reject"]
|
||||
AI --> AJ["★ 自动推进到下一个 pending 文件<br/>diffViewer.advanceToNext()"]
|
||||
AJ --> AK["全部处理完 → 🎉 通知"]
|
||||
X --> AE["changeSetManager.acceptAll()"]
|
||||
Y --> AF["changeSetManager.rejectAll()"]
|
||||
```
|
||||
|
||||
## 五、三种 UI 层
|
||||
---
|
||||
|
||||
### 5.1 Webview 总览面板 (`reviewPanel.ts`)
|
||||
## 五、UI 层
|
||||
|
||||
触发时机:Claude Stop 后自动弹出 / 手动 `Ctrl+Shift+D`
|
||||
### 5.1 状态栏 (`statusBar.ts`)
|
||||
|
||||
常驻显示,双区域布局:
|
||||
|
||||
```
|
||||
┌─ AI Diff 总览 ───── [Accept All] [Reject All] ─┐
|
||||
│ │
|
||||
│ 📄 file1.ts 修改 +3 -2 · 2编辑 [✔接受][✘拒绝]│
|
||||
│ ┌──── 原始代码 ────┬──── 变更后 ────────────┐ │
|
||||
│ │ 1 line1 │ 1 line1 │ │
|
||||
│ │ 2 -old line2 │ (空) │ │
|
||||
│ │ 3 line3 │ 2 +new line2 │ │
|
||||
│ │ 4 -old line4 │ (空) │ │
|
||||
│ │ 5 line5 │ 3 +extra line │ │
|
||||
│ │ │ 4 line5 │ │
|
||||
│ └──────────────────┴────────────────────────┘ │
|
||||
│ │
|
||||
│ 📄 file2.ts 新增 +10 -0 · 1编辑 [✔接受][✘拒绝]│
|
||||
│ ┌──── 新文件内容 ──────────────────────────┐ │
|
||||
│ │ 1 +new code line 1 │ │
|
||||
│ │ 2 +new code line 2 │ │
|
||||
│ └──────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────┘
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ [$(diff) AI Diff: 2文件 5编辑] [✓ Accept] [✗ Reject]│
|
||||
│ (左侧常驻摘要, 点击打开 QuickPick) (右侧, 仅 Diff 模式) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 5.2 VSCode 内置 Diff 编辑器 (`diffViewer.ts`)
|
||||
| 模式 | 条件 | 左侧 | 右侧 |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| 空闲 | 无变更集 | `$(diff) AI Diff` (暗色) | 隐藏 |
|
||||
| 有 pending | 有待处理变更 | `$(diff) AI Diff: N文件 M编辑` (黄色) | 隐藏 |
|
||||
| Diff 活跃 | 内置 Diff 打开 | 同上 | `Accept` (黄底) + `Reject` (红底) |
|
||||
| 已完成 | 全部处理完 | `$(diff) AI Diff: 已完成` | 隐藏 |
|
||||
|
||||
触发时机:从总览面板点击文件 / `Alt+↑/↓` 导航
|
||||
### 5.2 VSCode 内置 Diff (`diffViewer.ts`)
|
||||
|
||||
- 使用 `vscode.diff` 命令打开原生 Diff 编辑器
|
||||
- 左侧:原始内容 (红色删除行),右侧:变更后内容 (绿色新增行)
|
||||
- 临时文件机制:写入 `%TEMP%/ai-diff-preview/`,关闭 Diff 后自动删除(**无保存提示**)
|
||||
- **自动推进**:Accept/Reject 后自动打开下一个 pending 文件
|
||||
- 若当前文件仍有 pending 编辑 → 重新打开此文件
|
||||
- 否则 → 打开下一个 pending 文件
|
||||
- 全部处理完 → 弹出 🎉 通知
|
||||
触发时机:QuickPick 点击文件 / `Alt+↑↓` 导航 / `autoShowDiffPerEdit` 自动弹出
|
||||
|
||||
### 5.3 状态栏 (`statusBar.ts`)
|
||||
**临时文件方案**:
|
||||
|
||||
双模式自动切换:
|
||||
- 路径:`%TEMP%/ai-diff-preview/{uuid}-{before|after}-{id}.{ext}`
|
||||
- 创建:`showEditDiff()` / `showFileDiff()` 时写入
|
||||
- 删除:Accept/Reject 时 `deleteSessionFiles()` 主动清理
|
||||
- 批量清理:`deactivate()` 时 `cleanupTmpFiles()`
|
||||
|
||||
| 模式 | 触发 | 显示 |
|
||||
|------|------|------|
|
||||
| 摘要模式 | 无活跃 Diff | `$(diff) AI Diff: 2文件 5编辑` ← 点击打开总览 |
|
||||
| Diff 模式 | 内置 Diff 活跃 | `$(check) Accept` `$(close) Reject` `$(check-all) 接受全部` |
|
||||
**Session 管理**:
|
||||
|
||||
```typescript
|
||||
interface DiffSession {
|
||||
fileId: string; // FileChange.id
|
||||
editId?: string; // EditRecord.id (单次编辑模式)
|
||||
beforeTmpPath: string;
|
||||
afterTmpPath: string;
|
||||
beforeUri: vscode.Uri;
|
||||
afterUri: vscode.Uri;
|
||||
}
|
||||
```
|
||||
|
||||
- **isOpening 锁**:Diff 打开过程中禁止 `onDidChangeVisibleTextEditors` 误清 session
|
||||
- **关闭检测**:before/after URI 都不可见时自动清除 session
|
||||
|
||||
**自动推进** `advanceToNext()`:
|
||||
|
||||
1. 当前文件还有 pending 编辑 → 重新打开该文件 Diff
|
||||
2. 否则 `findNextPending()` → 打开下一个文件 Diff
|
||||
3. 无更多 pending → "🎉 所有变更已处理完毕!"
|
||||
|
||||
### 5.3 QuickPick 文件列表 (`hookHandler.showFileListPicker()`)
|
||||
|
||||
Stop 后的主 UI(当 `showAllDiffsOnStop = true`):
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ AI Diff 总览 — 2 文件 3 编辑 (+13 -2) │
|
||||
│ │
|
||||
│ $(edit) file1.ts +3 -2 · 2 编辑 │
|
||||
│ $(new-file) file2.ts +10 -0 · 1 编辑 │
|
||||
│ ────────────────────────────────────── │
|
||||
│ $(check-all) ✅ Accept All — 接受所有变更 │
|
||||
│ $(trash) ❌ Reject All — 拒绝所有变更 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- 点击文件 → `diffViewer.showFileDiff()` 打开内置 Diff
|
||||
- 点击 Accept All / Reject All → 批量操作
|
||||
|
||||
### 5.4 Webview 总览面板 (`reviewPanel.ts`)
|
||||
|
||||
**备用方案**,非主流程。提供卡片式双栏 Diff 展示,功能与 QuickPick 重叠。
|
||||
|
||||
### 5.5 CodeLens (`codeLensProvider.ts`)
|
||||
|
||||
编辑器行内显示 Accept/Reject 按钮(当 `floatingLabelMode` 包含 inline 时):
|
||||
|
||||
- 文件标题行:`$(diff) AI Diff · N 次编辑` | `$(check-all) 接受全部` | `$(trash) 拒绝全部`
|
||||
- 每个 EditRecord 变更区域:`AI 编辑 #xxxx · +3 -2` | `Accept` | `Reject`
|
||||
|
||||
### 5.6 内联装饰器 (`inlineDecorator.ts`)
|
||||
|
||||
在编辑器中渲染红绿高亮:
|
||||
|
||||
- 绿色背景 + gutter 圆点:新增行
|
||||
- 红色背景 + 删除线:删除行
|
||||
- 行末 `← AI 变更` 标签
|
||||
|
||||
---
|
||||
|
||||
## 六、Accept/Reject 行为
|
||||
|
||||
| 操作 | Modify 文件 | Create 文件 |
|
||||
|------|------------|------------|
|
||||
| Accept (文件级) | 写入 `latestContent` 到磁盘 | 保留文件 |
|
||||
| Reject (文件级) | 恢复 `originalContent` 到磁盘 | 删除文件 |
|
||||
| Accept All | 遍历所有 pending 文件执行 Accept | |
|
||||
| Reject All | 遍历所有 pending 文件执行 Reject | |
|
||||
| 操作 | Modify 文件 | Create 文件 | Delete 文件 |
|
||||
| ---- | ---------- | ---------- | ---------- |
|
||||
| Accept (文件级) | 写入 `latestContent` | 保留文件 | 保留删除状态 |
|
||||
| Reject (文件级) | 恢复 `originalContent` | 删除文件 | 恢复原文件 |
|
||||
| Accept All | 遍历所有 pending 文件执行 Accept | | |
|
||||
| Reject All | 遍历所有 pending 文件执行 Reject | | |
|
||||
|
||||
## 七、配置项
|
||||
### 编辑级 Accept/Reject
|
||||
|
||||
| 操作 | 行为 |
|
||||
| ---- | ---- |
|
||||
| Accept Edit | 标记该 EditRecord 为 accepted;若文件所有 edit 都 accepted → 写入 `latestContent` |
|
||||
| Reject Edit | 从当前文件内容中还原 `newString → oldString`;标记为 rejected |
|
||||
|
||||
---
|
||||
|
||||
## 七、触发机制
|
||||
|
||||
### 文件监听
|
||||
|
||||
```
|
||||
.claude/hooks/pending.json
|
||||
│
|
||||
▼ (create / change 事件)
|
||||
extension.ts:
|
||||
1. FileSystemWatcher 监听 pending.json
|
||||
2. 读取 → 删除 (防重复处理)
|
||||
3. JSON.parse → ClaudeHookEvent
|
||||
4. 时间戳校验: Date.now() - timestamp > 10000 → 丢弃
|
||||
5. 分发: Edit/Write → handleEdit(), Stop → handleStop()
|
||||
```
|
||||
|
||||
### 启动恢复
|
||||
|
||||
`checkPendingHookFile()`: activate 时检查遗留 pending 文件,15 秒内有效。
|
||||
|
||||
### Claude Code Hooks 配置
|
||||
|
||||
```json
|
||||
{
|
||||
"hooks": {
|
||||
"PostToolUse": [{
|
||||
"matcher": "Write|Edit",
|
||||
"hooks": [{ "type": "command", "command": "node -e \"...(写入 pending.json)...\"", "timeout": 5 }]
|
||||
}],
|
||||
"Stop": [{
|
||||
"hooks": [{ "type": "command", "command": "node -e \"...(写入 type=stop 的 pending.json)...\"", "timeout": 5 }]
|
||||
}]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、配置项
|
||||
|
||||
| 配置 | 类型 | 默认 | 说明 |
|
||||
|------|------|------|------|
|
||||
| ---- | ---- | ---- | ---- |
|
||||
| `enableAutoTrigger` | boolean | true | 监听 pending.json |
|
||||
| `autoShowDiffPerEdit` | boolean | false | 每次 Edit 后弹内置 Diff |
|
||||
| `showAllDiffsOnStop` | boolean | true | Stop 后弹总览面板 |
|
||||
| `floatingLabelMode` | string | "statusBar" | 按钮位置 |
|
||||
| `maxFileSize` | number | 100000 | 文件大小上限 |
|
||||
| `showAllDiffsOnStop` | boolean | true | Stop 后弹 QuickPick 文件列表 |
|
||||
| `floatingLabelMode` | string | "statusBar" | inline / statusBar / both / none |
|
||||
| `maxFileSize` | number | 100000 | 文件大小上限 (字符) |
|
||||
|
||||
## 八、快捷键
|
||||
### 配置组合
|
||||
|
||||
| autoShowDiffPerEdit | showAllDiffsOnStop | 行为 |
|
||||
| :---: | :---: | ------ |
|
||||
| false | true | **默认**: 编辑时静默收集,Stop 后弹 QuickPick 统一审查 |
|
||||
| true | false | 每次编辑弹 Diff,Stop 后无额外操作 |
|
||||
| true | true | 每次编辑弹 Diff + Stop 后再弹 QuickPick |
|
||||
| false | false | 仅状态栏显示,用户手动触发 |
|
||||
|
||||
---
|
||||
|
||||
## 九、快捷键
|
||||
|
||||
| 快捷键 | 条件 | 功能 |
|
||||
|--------|------|------|
|
||||
| `Ctrl+Shift+D` | `aiDiffPreview.isActive` | 打开总览面板 |
|
||||
| `Alt+↓` | `aiDiffPreview.isActive` | 下一个变更文件 |
|
||||
| `Alt+↑` | `aiDiffPreview.isActive` | 上一个变更文件 |
|
||||
| `Tab` | Diff 活跃 | Accept 当前变更 |
|
||||
| `Esc` | Diff 活跃 | Reject 当前变更 |
|
||||
| `Ctrl+Shift+A` | `aiDiffPreview.isActive` | Accept All |
|
||||
| `Ctrl+Shift+R` | `aiDiffPreview.isActive` | Reject All |
|
||||
| ------ | ---- | ---- |
|
||||
| `Tab` | `isActive && editorTextFocus` | Accept 当前编辑 |
|
||||
| `Esc` | `isActive && editorTextFocus` | Reject 所有变更 |
|
||||
| `Ctrl+Shift+A` | `isActive` | Accept All |
|
||||
| `Ctrl+Shift+R` | `isActive` | Reject All |
|
||||
| `Alt+↓` | `isActive` | 下一个变更文件 |
|
||||
| `Alt+↑` | `isActive` | 上一个变更文件 |
|
||||
| `Ctrl+Shift+D` | `isActive` | 打开 QuickPick 文件列表 |
|
||||
| `Ctrl+Shift+F` | `isActive` | 查看当前文件 Diff |
|
||||
|
||||
## 九、模块依赖关系
|
||||
---
|
||||
|
||||
## 十、模块依赖关系
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
extension["extension.ts<br/>入口"]
|
||||
hook["hookHandler.ts<br/>Hook 处理"]
|
||||
manager["snapshotManager.ts<br/>ChangeSetManager<br/>★ 核心数据层"]
|
||||
review["reviewPanel.ts<br/>总览 Webview"]
|
||||
viewer["diffViewer.ts<br/>内置 Diff + 自动推进"]
|
||||
bar["statusBar.ts<br/>状态栏"]
|
||||
review["reviewPanel.ts<br/>Webview (备用)"]
|
||||
lens["codeLensProvider.ts<br/>CodeLens"]
|
||||
deco["inlineDecorator.ts<br/>装饰器"]
|
||||
types["models/types.ts<br/>类型"]
|
||||
|
||||
extension --> hook
|
||||
extension --> manager
|
||||
extension --> review
|
||||
extension --> viewer
|
||||
extension --> bar
|
||||
extension --> review
|
||||
extension --> lens
|
||||
extension --> deco
|
||||
|
||||
hook --> manager
|
||||
hook --> viewer
|
||||
|
||||
review --> manager
|
||||
review --> viewer
|
||||
|
||||
viewer --> manager
|
||||
|
||||
bar --> manager
|
||||
bar --> viewer
|
||||
|
||||
review --> manager
|
||||
review --> viewer
|
||||
|
||||
lens --> manager
|
||||
|
||||
manager --> types
|
||||
```
|
||||
|
||||
## 十、已修复的 Bug
|
||||
---
|
||||
|
||||
## 十一、已修复的 Bug
|
||||
|
||||
| Bug | 根因 | 修复 |
|
||||
|-----|------|------|
|
||||
| Accept 后同文件再编辑无 Accept/Reject | `recordEdit()` 更新同一 `FileChange`,Accept 后状态机断开 | 引入 `EditRecord` 粒度,Accept/Reject 后重新编辑创建新 FileChange |
|
||||
| Accept All / Reject All 不生效 | `acceptAllFromDiff()` 调用不存在的方法 | 改为调用 `acceptFile()`,All 方法改为收集列表后逐一处理 |
|
||||
| --- | ---- | ---- |
|
||||
| Accept 后同文件再编辑无 Accept/Reject | `recordEdit()` 更新同一 FileChange,Accept 后状态机断开 | 引入 EditRecord 粒度,Accept 后再编辑创建新 FileChange |
|
||||
| Accept All / Reject All 不生效 | `acceptAllFromDiff()` 调用不存在的方法 | 改为调用 `acceptFile()`,收集列表后逐一处理 |
|
||||
| Accept 后无后续 Diff | 无推进逻辑 | `advanceToNext()` 自动打开下一个 pending 文件 |
|
||||
| 临时文件保存提示 | 使用 `openTextDocument({content})` 创建 untitled 文档 | 改为写入临时目录 `%TEMP%/ai-diff-preview/` |
|
||||
| 同文件多轮编辑孤儿 FileChange | `.find()` 返回第一个 (已 accept) 而非 pending | 改为优先匹配 pending 的 FileChange |
|
||||
| 临时文件保存提示 | 使用 `openTextDocument({content})` 创建 untitled 文档 | 改为写入 `%TEMP%/ai-diff-preview/` |
|
||||
| 同文件多轮编辑孤儿 FileChange | `.find()` 返回第一个 (已 accept) 而非 pending | 优先匹配 `status === 'pending'` 的 FileChange |
|
||||
| Diff 打开时 session 被误清 | `onDidChangeVisibleTextEditors` 在 Diff 加载中触发 | `isOpening` 原子锁保护 |
|
||||
|
||||
---
|
||||
|
||||
## 十二、命令注册
|
||||
|
||||
| 命令 ID | 功能 |
|
||||
| ------- | ---- |
|
||||
| `aiDiffPreview.showDiffPanel` | QuickPick 文件列表总览 |
|
||||
| `aiDiffPreview.showCurrentFileDiff` | 查看当前文件 Diff |
|
||||
| `aiDiffPreview.acceptCurrentEdit` | 接受当前编辑 |
|
||||
| `aiDiffPreview.rejectCurrentEdit` | 拒绝当前编辑 |
|
||||
| `aiDiffPreview.acceptAll` | 接受所有变更 |
|
||||
| `aiDiffPreview.rejectAll` | 拒绝所有变更 |
|
||||
| `aiDiffPreview.nextDiff` | 下一个变更 |
|
||||
| `aiDiffPreview.prevDiff` | 上一个变更 |
|
||||
| `aiDiffPreview.acceptCurrentDiff` | Diff 内接受 (状态栏) |
|
||||
| `aiDiffPreview.rejectCurrentDiff` | Diff 内拒绝 (状态栏) |
|
||||
| `aiDiffPreview.acceptAllFromDiff` | Diff 内接受整个文件 (状态栏) |
|
||||
| `aiDiffPreview.acceptChunk` | 旧版兼容 → acceptCurrentDiff |
|
||||
| `aiDiffPreview.rejectChunk` | 旧版兼容 → rejectCurrentDiff |
|
||||
|
||||
---
|
||||
|
||||
## 十三、构建
|
||||
|
||||
```bash
|
||||
npm install # 安装依赖
|
||||
npm run build # esbuild → dist/extension.js (CJS, minify)
|
||||
npm run watch # 监听模式 (sourcemap)
|
||||
npm run test # vitest run
|
||||
npm run test:watch # vitest watch
|
||||
npm run lint # eslint
|
||||
npm run package # vsce package → .vsix
|
||||
```
|
||||
|
||||
- **esbuild**: `src/extension.ts → dist/extension.js`,external: vscode,CJS 格式
|
||||
- **TypeScript**: ES2022,CommonJS,strict mode
|
||||
|
||||
Reference in New Issue
Block a user