feat:代码变更diff可视化功能实现

This commit is contained in:
Roe-xin
2026-03-02 10:00:04 +08:00
parent 3e18299099
commit 4c7ec65577
13 changed files with 1195 additions and 1 deletions

View File

@ -0,0 +1,196 @@
/**
* 文件变更追踪服务
* 功能:收集和管理 AI 修改文件的变更记录
* 依赖types/fileChanges
* 使用场景:在文件操作时记录变更,供用户审查
*/
import { FileChange, ChangeSession } from '../types/fileChanges';
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
class ChangeTrackerService {
private currentSession: ChangeSession | null = null;
private changeListeners: Array<(session: ChangeSession) => void> = [];
/**
* 开始新的变更会话
*/
startSession(sessionId: string): void {
this.currentSession = {
sessionId,
startTime: Date.now(),
changes: [],
status: 'active'
};
}
/**
* 记录文件变更
*/
trackChange(filePath: string, oldContent: string, newContent: string): string {
if (!this.currentSession) {
this.startSession(`session_${Date.now()}`);
}
const changeId = `change_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// 判断变更类型
let changeType: 'create' | 'modify' | 'delete';
if (oldContent === '' && newContent !== '') {
changeType = 'create';
} else if (oldContent !== '' && newContent === '') {
changeType = 'delete';
} else {
changeType = 'modify';
}
const change: FileChange = {
filePath,
oldContent,
newContent,
timestamp: Date.now(),
changeType,
changeId
};
this.currentSession!.changes.push(change);
this.notifyListeners();
return changeId;
}
/**
* 结束当前会话
*/
endSession(): ChangeSession | null {
if (this.currentSession && this.currentSession.changes.length > 0) {
this.currentSession.status = 'completed';
const session = this.currentSession;
this.notifyListeners();
return session;
}
this.currentSession = null;
return null;
}
/**
* 获取当前会话
*/
getCurrentSession(): ChangeSession | null {
return this.currentSession;
}
/**
* 清空当前会话
*/
clearSession(): void {
this.currentSession = null;
this.notifyListeners();
}
/**
* 移除指定的变更
*/
removeChange(changeId: string): boolean {
if (!this.currentSession) {
return false;
}
const index = this.currentSession.changes.findIndex(c => c.changeId === changeId);
if (index !== -1) {
this.currentSession.changes.splice(index, 1);
this.notifyListeners();
return true;
}
return false;
}
/**
* 监听变更
*/
onChangeUpdate(listener: (session: ChangeSession) => void): void {
this.changeListeners.push(listener);
}
/**
* 通知所有监听器
*/
private notifyListeners(): void {
if (this.currentSession) {
this.changeListeners.forEach(listener => listener(this.currentSession!));
}
}
/**
* 采纳变更(保存文件)
*/
async acceptChange(changeId: string): Promise<boolean> {
if (!this.currentSession) {
return false;
}
const change = this.currentSession.changes.find(c => c.changeId === changeId);
if (!change) {
return false;
}
try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
return false;
}
const absolutePath = path.join(workspaceFolder.uri.fsPath, change.filePath);
await fs.promises.writeFile(absolutePath, change.newContent, 'utf-8');
this.removeChange(changeId);
return true;
} catch (error) {
console.error('[ChangeTracker] 采纳变更失败:', error);
return false;
}
}
/**
* 拒绝变更(恢复旧内容)
*/
async rejectChange(changeId: string): Promise<boolean> {
if (!this.currentSession) {
return false;
}
const change = this.currentSession.changes.find(c => c.changeId === changeId);
if (!change) {
return false;
}
try {
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
if (!workspaceFolder) {
return false;
}
const absolutePath = path.join(workspaceFolder.uri.fsPath, change.filePath);
// 如果是新建文件,删除它
if (change.changeType === 'create') {
if (fs.existsSync(absolutePath)) {
await fs.promises.unlink(absolutePath);
}
} else {
// 恢复旧内容
await fs.promises.writeFile(absolutePath, change.oldContent, 'utf-8');
}
this.removeChange(changeId);
return true;
} catch (error) {
console.error('[ChangeTracker] 拒绝变更失败:', error);
return false;
}
}
}
export const changeTracker = new ChangeTrackerService();