refactor: 架构变更:Webview → VSCode 内置 Diff + 浮动标签

This commit is contained in:
2026-06-18 08:05:24 +08:00
parent f06914994d
commit fcda651760
7 changed files with 548 additions and 98 deletions
+122
View File
@@ -0,0 +1,122 @@
/**
* 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';
}
}