diff --git a/README.md b/README.md index 269b7a5..1554cd8 100644 --- a/README.md +++ b/README.md @@ -1,67 +1,28 @@ # AI Code Diff Preview -> 类 Cursor 的 AI 代码差异预览 VSCode 插件 +类 Cursor 风格的 AI 代码差异预览 VSCode 插件 — Accept/Reject 审查变更。 -[![Version](https://img.shields.io/badge/version-0.2.0-blue)](https://github.com/YonHaoGuo/ai-diff-preview/blob/master/CHANGELOG.md) -[![VSCode](https://img.shields.io/badge/vscode-%5E1.85.0-brightgreen)](https://code.visualstudio.com/) -[![License](https://img.shields.io/badge/license-MIT-green)](LICENSE) +## 使用方法 -AI Code Diff Preview 为 VSCode 注入 Cursor 风格的 Accept/Reject 差异审查体验。AI 对话中的每一次代码编辑自动收集,对话结束后弹出完整的变更文件列表。点击任意文件即可在 **VSCode 内置 Diff 编辑器**中逐文件审查,状态栏实时显示 Accept/Reject 按钮,操作完成后自动推进到下一个待处理文件。 +1. 安装 `.vsix` 并重启 VSCode +2. 将下面配置复制到项目 `.claude/settings.json`(或 `~/.claude/settings.json` 全局生效) +3. 用 Claude Code 编辑项目 → Stop 后自动弹出 QuickPick 变更列表 → VSCode 内置 Diff 逐文件审查 -## 特性 +## Claude Code Hooks 必须配置 -- **VSCode 原生 Diff 编辑器** — 使用 `vscode.diff` 命令,100% 原生交互体验,无自定义 Webview -- **EditRecord 独立追踪** — 每次 `Edit`/`Write` 操作作为独立编辑记录,支持逐次编辑 Accept/Reject -- **QuickPick 总览** — 对话结束后弹出原生文件列表,含 Accept All / Reject All 一键操作 -- **智能 Auto-Advance** — Accept/Reject 后自动推进到下一个待处理文件,同文件多编辑自动停留 -- **状态栏常驻** — 插件激活后状态栏始终可见 `$(diff) AI Diff`,有变更时黄底高亮 -- **零保存提示** — Diff 编辑器使用临时文件,关闭时自动删除,不会弹出"是否保存"对话框 -- **多轮对话追踪** — 支持同一轮对话中对同文件的多次编辑合并、跨轮对话的独立追踪 - -## 安装 - -### 方式 A:直接安装 .vsix 文件(推荐) - -1. 从 [Releases](https://github.com/YonHaoGuo/ai-diff-preview/releases) 下载最新 `ai-diff-preview-*.vsix` -2. VSCode 中 `Ctrl+Shift+P` → **Extensions: Install from VSIX** → 选择下载的文件 -3. 重启 VSCode - -### 方式 B:源码编译安装 - -```bash -git clone https://github.com/YonHaoGuo/ai-diff-preview.git -cd ai-diff-preview -npm install -npm run build -npm run package # 生成 ai-diff-preview-*.vsix -# 然后按方式 A 的第2步安装 -``` - ---- - -## ⚠️ 必须配置:启用 Claude Code Hooks - -**插件依赖 Claude Code 的 Hooks 机制才能工作,安装后必须配置,否则不会触发!** - -打开 Claude Code 配置文件(**任选其一**): - -| 作用范围 | 配置文件路径 | -|----------|-------------| -| **当前项目**(推荐) | 项目根目录 `.claude/settings.json` | -| **所有项目** | Windows: `C:\Users\<用户名>\.claude\settings.json`
macOS/Linux: `~/.claude/settings.json` | - -将以下内容**合并**到你已有的配置文件中(`hooks` 是顶层字段,和现有字段平级): +将以下内容写入 `.claude/settings.json`: ```json { "hooks": { "PostToolUse": [ { - "matcher": "tool_name === 'Edit' || tool_name === 'Write'", + "matcher": "Write|Edit", "hooks": [ { "type": "command", - "command": "node -e \"const fs=require('fs');const p=process.cwd()+'/.claude/hooks/pending.json';try{fs.mkdirSync(require('path').dirname(p),{recursive:true});fs.writeFileSync(p,JSON.stringify({toolName:process.env.CLAUDE_TOOL_NAME,filePath:process.env.CLAUDE_TOOL_INPUT_FILE_PATH,oldString:process.env.CLAUDE_TOOL_INPUT_OLD_STRING||'',newString:process.env.CLAUDE_TOOL_INPUT_NEW_STRING||'',content:process.env.CLAUDE_TOOL_INPUT_CONTENT||'',timestamp:Date.now(),type:'edit'}));}catch(e){console.error(e)}\"" + "command": "node -e \"const fs=require('fs'),p=require('path'),d=p.join(process.env.CLAUDE_PLUGIN_ROOT||'.','.claude','hooks');let b='';process.stdin.on('data',c=>b+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(b);const e={type:'edit',filePath:j.tool_input?.file_path||j.tool_response?.filePath||'',content:j.tool_input?.content||'',oldString:j.tool_input?.old_string||'',newString:j.tool_input?.new_string||'',timestamp:Date.now(),toolName:j.tool_name};fs.mkdirSync(d,{recursive:true});const t=p.join(d,'pending.json'),tmp=t+'.tmp';fs.writeFileSync(tmp,JSON.stringify(e));fs.renameSync(tmp,t)}catch{}})\"", + "timeout": 5 } ] } @@ -71,7 +32,8 @@ npm run package # 生成 ai-diff-preview-*.vsix "hooks": [ { "type": "command", - "command": "node -e \"const fs=require('fs');const p=process.cwd()+'/.claude/hooks/pending.json';try{fs.mkdirSync(require('path').dirname(p),{recursive:true});fs.writeFileSync(p,JSON.stringify({toolName:'Stop',filePath:'',oldString:'',newString:'',content:'',timestamp:Date.now(),type:'stop'}));}catch(e){console.error(e)}\"" + "command": "node -e \"const fs=require('fs'),p=require('path'),d=p.join(process.env.CLAUDE_PLUGIN_ROOT||'.','.claude','hooks');try{fs.mkdirSync(d,{recursive:true});const t=p.join(d,'pending.json'),tmp=t+'.tmp';fs.writeFileSync(tmp,JSON.stringify({type:'stop',filePath:'',content:'',oldString:'',newString:'',timestamp:Date.now(),toolName:'Stop'}));fs.renameSync(tmp,t)}catch{}\"", + "timeout": 5 } ] } @@ -80,136 +42,27 @@ npm run package # 生成 ai-diff-preview-*.vsix } ``` -配置完成后,**重启 Claude Code 对话**即可生效。 +## 命令 -## 使用方法 +| 命令 | 快捷键 | 功能 | +|------|--------|------| +| `AI Diff: 显示所有变更文件列表` | `Ctrl+Shift+D` | QuickPick 总览 + Accept All / Reject All | +| `AI Diff: 下一个变更` | `Alt+↓` | 跳转下一个 pending 文件 | +| `AI Diff: 上一个变更` | `Alt+↑` | 跳转上一个 pending 文件 | +| `AI Diff: 接受所有变更` | `Ctrl+Shift+A` | Accept All | +| `AI Diff: 拒绝所有变更` | `Ctrl+Shift+R` | Reject All | -### 快速开始 - -1. **安装插件** → 重启 VSCode → 状态栏左侧出现 `$(diff) AI Diff` -2. **用 Claude Code / Cursor 对话** → 每次 Edit/Write **自动静默收集**,不打断工作流 -3. **对话结束 (Stop)** → 自动弹出 **QuickPick 总览**,列出所有变更文件 -4. **↑↓ 键选择文件** → 回车打开 **VSCode 内置 Diff 编辑器**,左侧原始代码、右侧变更后 -5. **查看状态栏右侧** → 三个彩色按钮:`✓ Accept` `✗ Reject` `✓✓ 接受此文件全部` -6. **点击 Accept / Reject** → 变更立即应用到磁盘 → **自动推进**到下一个文件 -7. **全部处理完** → 🎉 通知,状态栏回到常态 - -### 一键操作速查 - -| 操作 | 方法 | -|------|------| -| 接受**全部文件的所有变更** | QuickPick 底部选 `✅ Accept All`,或 `Ctrl+Shift+A` | -| 拒绝**全部文件的所有变更** | QuickPick 底部选 `❌ Reject All`,或 `Ctrl+Shift+R` | -| 手动打开总览 | 点击状态栏 `$(diff) AI Diff`,或 `Ctrl+Shift+D` | -| 查看当前文件 Diff | `Ctrl+Shift+F` | -| 在变更文件间导航 | `Alt+↑` / `Alt+↓` | -| 接受当前编辑 | `Tab`(需要在 Diff 编辑器内) | - -### 逐文件审查流程 - -``` -AI 对话进行中(静默收集 EditRecord) - │ -Stop → QuickPick 弹窗 - │ - ├─ 📄 file1.ts +3 -2 · 2次编辑 ← 选中回车 - │ │ - │ └→ VSCode 内置 Diff 打开 - │ 状态栏右侧: [✓ Accept] [✗ Reject] [✓✓ 接受全部] - │ 点击 ✓ → 应用变更 - │ ├→ 同文件还有待处理编辑? → 重新打开此文件 Diff - │ └→ 文件处理完毕? → 自动打开下一个文件 Diff - │ - ├─ 📄 file2.ts +9 -1 · 1次编辑 ← 自动推进 - │ └→ ... (重复) - │ - └─ 🎉 所有变更已处理完毕 -``` - -### 快捷键 - -| 快捷键 | 条件 | 功能 | -| ---------------- | -------------------------- | ----------------------- | -| `Alt+↓` | `aiDiffPreview.isActive` | 下一个变更文件 | -| `Alt+↑` | `aiDiffPreview.isActive` | 上一个变更文件 | -| `Ctrl+Shift+D` | `aiDiffPreview.isActive` | 显示 QuickPick 文件列表 | -| `Ctrl+Shift+F` | `aiDiffPreview.isActive` | 查看当前文件 Diff | -| `Ctrl+Shift+A` | `aiDiffPreview.isActive` | Accept All | -| `Ctrl+Shift+R` | `aiDiffPreview.isActive` | Reject All | - -### 命令 - -| 命令 ID | 标题 | -| ------------------------------------- | ----------------------------- | -| `aiDiffPreview.showDiffPanel` | AI Diff: 显示所有变更文件列表 | -| `aiDiffPreview.showCurrentFileDiff` | AI Diff: 查看当前文件 Diff | -| `aiDiffPreview.acceptCurrentEdit` | AI Diff: 接受当前编辑 | -| `aiDiffPreview.rejectCurrentEdit` | AI Diff: 拒绝当前编辑 | -| `aiDiffPreview.acceptAll` | AI Diff: 接受所有变更 | -| `aiDiffPreview.rejectAll` | AI Diff: 拒绝所有变更 | -| `aiDiffPreview.nextDiff` | AI Diff: 下一个变更 | -| `aiDiffPreview.prevDiff` | AI Diff: 上一个变更 | - -### 配置项 - -| 配置项 | 类型 | 默认值 | 说明 | -| ------------------------------------- | ------- | --------------- | ------------------------------------------------ | -| `aiDiffPreview.enableAutoTrigger` | boolean | `true` | 启用自动触发(监听 pending.json) | -| `aiDiffPreview.autoShowDiffPerEdit` | boolean | `false` | 每次 Edit/Write 后自动弹出 Diff | -| `aiDiffPreview.showAllDiffsOnStop` | boolean | `true` | Stop 后自动弹出 QuickPick 总览 | -| `aiDiffPreview.floatingLabelMode` | string | `"statusBar"` | 浮动标签位置(statusBar / inline / both / none) | -| `aiDiffPreview.maxFileSize` | number | `100000` | 最大处理文件大小(字符数) | - -## 项目结构 - -``` -src/ -├── extension.ts # 插件入口 -├── trigger/ -│ └── hookHandler.ts # Hook 处理器 + QuickPick 总览 -├── snapshot/ -│ └── snapshotManager.ts # 变更集管理器 (ChangeSetManager) -├── diff/ -│ └── diffEngine.ts # Diff 计算引擎 -├── render/ -│ ├── diffViewer.ts # VSCode 内置 Diff 查看器 + 自动推进 -│ ├── reviewPanel.ts # Webview 面板 (实验性/备用) -│ ├── inlineDecorator.ts # 内联装饰器 (备用) -│ ├── codeLensProvider.ts # CodeLens 提供器 (备用) -│ └── statusBar.ts # 状态栏管理器 (常驻 + Diff 模式) -├── transaction/ -│ ├── acceptHandler.ts # Accept 处理器 (旧版) -│ └── rejectHandler.ts # Reject 处理器 (旧版) -├── models/ -│ ├── types.ts # 类型定义 -│ └── constants.ts # 常量定义 -└── __tests__/ - ├── diffEngine.test.ts # Diff 引擎测试 - └── changeSetManager.test.ts # 变更集管理器回归测试 -``` +Diff 编辑器打开后,状态栏右侧自动出现 **Accept / Reject / 接受全部** 按钮。 ## 开发 ```bash -npm run watch # esbuild 监听模式 + sourcemap -npm run build # 生产构建 (minify) -npm run test # 运行测试 (13 tests) -npm run test:watch # 测试监听模式 -npm run lint # ESLint -npm run package # vsce package → .vsix +npm install +npm run build # 编译 +npm run test # 测试 +npm run package # 生成 ai-diff-preview-*.vsix ``` -## 技术栈 - -| 领域 | 选型 | -| --------- | -------------------- | -| 框架 | VSCode Extension API | -| 语言 | TypeScript (ES2022) | -| Diff 算法 | `diff` (Myers) | -| 绑定 | esbuild | -| 测试 | Vitest | -| 规范 | ESLint + Prettier | - ## 许可证 -MIT © 2026 [Gmaker689](https://github.com/Gmaker689) +MIT © 2026 [Gmaker689](https://github.com/Gmaker689) \ No newline at end of file