mirror of
https://github.com/Gmaker689/ai-diff-preview.git
synced 2026-10-05 08:21:53 +08:00
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
|
|
/**
|
|||
|
|
* 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';
|
|||
|
|
}
|
|||
|
|
}
|