211 lines
5.0 KiB
TypeScript
211 lines
5.0 KiB
TypeScript
/**
|
||
* 文件变更追踪服务
|
||
* 功能:收集和管理 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 {
|
||
// 如果已有 session(无论状态),重用并重置为 active
|
||
if (this.currentSession) {
|
||
this.currentSession.status = 'active';
|
||
return;
|
||
}
|
||
|
||
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;
|
||
}
|
||
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);
|
||
|
||
// 如果是删除操作,删除文件
|
||
if (change.changeType === 'delete') {
|
||
if (fs.existsSync(absolutePath)) {
|
||
await fs.promises.unlink(absolutePath);
|
||
}
|
||
} else {
|
||
// 创建或修改文件
|
||
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();
|