feat:代码变更diff可视化功能实现
This commit is contained in:
196
src/services/changeTracker.ts
Normal file
196
src/services/changeTracker.ts
Normal 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();
|
||||
@ -8,6 +8,8 @@ import * as os from 'os';
|
||||
import * as fs from 'fs';
|
||||
import { readFileContent, readDirectory } from '../utils/readFiles';
|
||||
import { createOrOverwriteFile } from '../utils/createFiles';
|
||||
import { resolveWorkspaceFilePath, showFileDiff } from '../utils/fileDiff';
|
||||
import { changeTracker } from './changeTracker';
|
||||
import { generateVCD, checkIverilogAvailable, generateMultiVCD, DumpModule } from '../utils/iverilogRunner';
|
||||
import { analyzeVcdFile } from '../utils/vcdParser';
|
||||
import { executeWaveformTrace, WaveformTraceArgs } from '../utils/waveformTracer';
|
||||
@ -125,8 +127,19 @@ async function executeFileRead(args: FileReadArgs): Promise<string> {
|
||||
* 执行 file_write 工具
|
||||
*/
|
||||
async function executeFileWrite(args: FileWriteArgs): Promise<string> {
|
||||
const absolutePath = resolveWorkspaceFilePath(args.path);
|
||||
const existedBeforeWrite = fs.existsSync(absolutePath);
|
||||
const oldContent = existedBeforeWrite ? await readFileContent(args.path) : '';
|
||||
|
||||
await createOrOverwriteFile(args.path, args.content);
|
||||
|
||||
// 记录文件变更
|
||||
try {
|
||||
changeTracker.trackChange(args.path, oldContent, args.content);
|
||||
} catch (error) {
|
||||
console.warn('[ToolExecutor] 记录文件变更失败:', error);
|
||||
}
|
||||
|
||||
// Verilog 文件添加知识图谱提示
|
||||
const isVerilogFile = args.path.endsWith('.v') || args.path.endsWith('.sv');
|
||||
if (isVerilogFile) {
|
||||
|
||||
Reference in New Issue
Block a user