Files
ai-diff-preview/src/render/diffViewer.ts
T

123 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Diff 查看器 - 调用 VSCode 内置 Diff 编辑器
*
* 使用 vscode.diff 命令展示原始内容 vs 变更后内容,
* 完全不使用自定义 Webview,保持 VSCode 原生交互体验。
*/
import * as vscode from 'vscode';
import * as path from 'path';
import { EditRecord, FileChange } from '../models/types';
import { ChangeSetManager } from '../snapshot/snapshotManager';
export class DiffViewer {
constructor(private changeSetManager: ChangeSetManager) {}
/**
* 打开单次编辑的 Diff(VSCode 内置 Diff 编辑器)
*/
async showEditDiff(editRecord: EditRecord): Promise<void> {
const beforeDoc = await vscode.workspace.openTextDocument({
content: editRecord.beforeContent,
language: this.getLanguageId(editRecord.filePath),
});
const afterDoc = await vscode.workspace.openTextDocument({
content: editRecord.afterContent,
language: this.getLanguageId(editRecord.filePath),
});
const fileName = path.basename(editRecord.filePath);
const toolLabel = editRecord.toolName === 'Write' ? '写入' : '编辑';
await vscode.commands.executeCommand(
'vscode.diff',
beforeDoc.uri,
afterDoc.uri,
`${fileName} — AI ${toolLabel} #${editRecord.id.slice(0, 6)} (原始 ↔ 变更后)`
);
}
/**
* 打开整个文件的聚合 Diff
*/
async showFileDiff(fileChange: FileChange): Promise<void> {
const beforeDoc = await vscode.workspace.openTextDocument({
content: fileChange.originalContent,
language: this.getLanguageId(fileChange.filePath),
});
const afterDoc = await vscode.workspace.openTextDocument({
content: fileChange.latestContent,
language: this.getLanguageId(fileChange.filePath),
});
const fileName = path.basename(fileChange.filePath);
await vscode.commands.executeCommand(
'vscode.diff',
beforeDoc.uri,
afterDoc.uri,
`${fileName} — 原始 ↔ 变更后 (${fileChange.edits.length} 次编辑)`
);
}
/**
* 打开当前活动文件在编辑器中的 Diff
*/
async showCurrentFileDiff(): Promise<void> {
const changeSet = this.changeSetManager.getCurrentSet();
if (!changeSet) return;
const editor = vscode.window.activeTextEditor;
if (!editor) return;
const normalizedPath = path.normalize(editor.document.uri.fsPath).replace(/\\/g, '/');
const fileChange = changeSet.changes.find(
c => path.normalize(c.filePath).replace(/\\/g, '/') === normalizedPath && c.status === 'pending'
);
if (!fileChange) {
vscode.window.showInformationMessage('AI Diff: 当前文件无待处理变更');
return;
}
await this.showFileDiff(fileChange);
}
/**
* 聚焦文件到编辑器(跳转到该文件并定位到第一个变更行)
*/
async focusFileInEditor(fileChange: FileChange): Promise<void> {
const uri = vscode.Uri.file(fileChange.filePath);
const doc = await vscode.workspace.openTextDocument(uri);
const editor = await vscode.window.showTextDocument(doc, { preview: false });
// 滚动到第一个变更行
if (fileChange.diffLines.length > 0) {
const firstChange = fileChange.diffLines.find(
d => d.type === 'add' || d.type === 'delete'
);
if (firstChange) {
const line = (firstChange.newLineNum || firstChange.oldLineNum) - 1;
const pos = new vscode.Position(Math.max(0, line), 0);
editor.selection = new vscode.Selection(pos, pos);
editor.revealRange(new vscode.Range(pos, pos), vscode.TextEditorRevealType.InCenter);
}
}
}
private getLanguageId(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
const map: Record<string, string> = {
'.ts': 'typescript', '.tsx': 'typescriptreact',
'.js': 'javascript', '.jsx': 'javascriptreact',
'.json': 'json', '.md': 'markdown',
'.css': 'css', '.scss': 'scss', '.less': 'less',
'.html': 'html', '.htm': 'html',
'.py': 'python', '.rs': 'rust', '.go': 'go',
'.java': 'java', '.cpp': 'cpp', '.c': 'c',
'.yaml': 'yaml', '.yml': 'yaml',
'.xml': 'xml', '.sql': 'sql', '.sh': 'shell',
};
return map[ext] || 'plaintext';
}
}