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

19 KiB

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 关键依赖库

{
  "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 快照管理器

// 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 引擎

// 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 内联装饰器

// 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 处理器

// 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 插件配置项

{
  "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 命令注册

{
  "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 配置)
  • 性能优化(大文件分块处理)

十、参考资料