mirror of
https://github.com/Gmaker689/ai-diff-preview.git
synced 2026-09-27 15:31:51 +08:00
fix:修复diff范围问题
This commit is contained in:
+120
-18
@@ -1,12 +1,15 @@
|
||||
/**
|
||||
* Review 面板 - 内嵌 Webview 悬浮窗
|
||||
*
|
||||
* 双栏 Diff + Accept/Reject 按钮,不弹系统窗口
|
||||
* ★ v0.2.0: 支持 per-edit 和 per-file 两级 Accept/Reject
|
||||
* - 每个文件卡片展开后显示各次编辑记录
|
||||
* - 每次编辑有独立的 ✓/✗ 按钮
|
||||
* - 文件级 Accept/Reject 一键处理所有编辑
|
||||
*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { ChangeSetManager } from '../snapshot/snapshotManager';
|
||||
import { ChangeSet, FileChange, FileChangeStatus } from '../models/types';
|
||||
import { ChangeSet, FileChange, EditRecord, ChangeStatus } from '../models/types';
|
||||
|
||||
export class ReviewPanel {
|
||||
private panel: vscode.WebviewPanel | undefined;
|
||||
@@ -41,23 +44,33 @@ export class ReviewPanel {
|
||||
case 'ready':
|
||||
this.sendData(changeSet);
|
||||
break;
|
||||
|
||||
// 单次编辑 Accept/Reject
|
||||
case 'acceptEdit':
|
||||
this.changeSetManager.acceptEditRecord(msg.fileId, msg.editId);
|
||||
this.refresh();
|
||||
break;
|
||||
case 'rejectEdit':
|
||||
this.changeSetManager.rejectEditRecord(msg.fileId, msg.editId);
|
||||
this.refresh();
|
||||
break;
|
||||
|
||||
// 整个文件 Accept/Reject
|
||||
case 'acceptFile':
|
||||
this.changeSetManager.updateFileStatus(msg.fileId, 'accepted');
|
||||
this.changeSetManager.applyFileChange(msg.fileId);
|
||||
this.changeSetManager.acceptFile(msg.fileId);
|
||||
this.refresh();
|
||||
break;
|
||||
case 'rejectFile':
|
||||
this.changeSetManager.updateFileStatus(msg.fileId, 'rejected');
|
||||
this.changeSetManager.rejectFileChange(msg.fileId);
|
||||
this.changeSetManager.rejectFile(msg.fileId);
|
||||
this.refresh();
|
||||
break;
|
||||
|
||||
// 全局操作
|
||||
case 'acceptAll':
|
||||
this.changeSetManager.updateAllStatus('accepted');
|
||||
this.changeSetManager.applyAllAccepted();
|
||||
this.changeSetManager.acceptAll();
|
||||
this.refresh();
|
||||
break;
|
||||
case 'rejectAll':
|
||||
this.changeSetManager.updateAllStatus('rejected');
|
||||
this.changeSetManager.rejectAll();
|
||||
this.refresh();
|
||||
break;
|
||||
@@ -124,12 +137,12 @@ export class ReviewPanel {
|
||||
}
|
||||
.badge-modify { background:rgba(210,153,34,.2); color:#d29922; }
|
||||
.badge-create { background:rgba(46,160,67,.2); color:#3fb950; }
|
||||
.badge-delete { background:rgba(248,81,73,.2); color:#f85149; }
|
||||
.badge-accepted { background:rgba(46,160,67,.3); color:#3fb950; }
|
||||
.badge-rejected { background:rgba(248,81,73,.3); color:#f85149; text-decoration:line-through; }
|
||||
.file-actions { display:flex; gap:6px; }
|
||||
.btn-sm { padding:3px 10px; border:none; border-radius:3px; cursor:pointer; font-size:11px; }
|
||||
|
||||
/* Diff 双栏 */
|
||||
.diff-wrap { display:grid; grid-template-columns:1fr 1fr; font-family:'Cascadia Code','Fira Code',Consolas,monospace; font-size:12px; line-height:20px; }
|
||||
.diff-side { overflow-x:auto; }
|
||||
.diff-side.left { border-right:1px solid var(--vscode-panel-border); }
|
||||
@@ -148,6 +161,28 @@ export class ReviewPanel {
|
||||
.diff-side.left .diff-line.add { background:transparent; }
|
||||
.diff-side.right .diff-line.del { background:transparent; }
|
||||
.empty-line { min-height:20px; }
|
||||
|
||||
/* ★ 编辑记录列表 */
|
||||
.edits-list { padding:0; }
|
||||
.edit-item {
|
||||
border-bottom:1px solid var(--vscode-panel-border);
|
||||
}
|
||||
.edit-item:last-child { border-bottom:none; }
|
||||
.edit-header {
|
||||
display:flex; align-items:center; justify-content:space-between;
|
||||
padding:6px 12px; background:var(--vscode-editor-background); cursor:pointer;
|
||||
font-size:12px;
|
||||
}
|
||||
.edit-header:hover { background:var(--vscode-list-hoverBackground); }
|
||||
.edit-label { display:flex; align-items:center; gap:6px; color:var(--vscode-descriptionForeground); }
|
||||
.edit-label .edit-num { font-weight:600; color:var(--vscode-foreground); }
|
||||
.edit-status { font-size:10px; }
|
||||
.edit-actions { display:flex; gap:4px; }
|
||||
.btn-xs { padding:2px 8px; border:none; border-radius:3px; cursor:pointer; font-size:10px; }
|
||||
|
||||
.summary-stats { font-size:11px; color:var(--vscode-descriptionForeground); padding:4px 12px; }
|
||||
.stat-add { color:#3fb950; }
|
||||
.stat-del { color:#f85149; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -175,6 +210,8 @@ function acceptAll(){ vscode.postMessage({type:'acceptAll'}); }
|
||||
function rejectAll(){ vscode.postMessage({type:'rejectAll'}); }
|
||||
function acceptFile(id){ vscode.postMessage({type:'acceptFile',fileId:id}); }
|
||||
function rejectFile(id){ vscode.postMessage({type:'rejectFile',fileId:id}); }
|
||||
function acceptEdit(fid, eid){ vscode.postMessage({type:'acceptEdit',fileId:fid,editId:eid}); }
|
||||
function rejectEdit(fid, eid){ vscode.postMessage({type:'rejectEdit',fileId:fid,editId:eid}); }
|
||||
|
||||
function render(){
|
||||
if(!cs) return;
|
||||
@@ -189,34 +226,99 @@ function card(c){
|
||||
const name = c.filePath.split(/[/\\\\]/).pop();
|
||||
const typeMap = {modify:'修改',create:'新增',delete:'删除'};
|
||||
const badgeCls = 'badge badge-' + c.type;
|
||||
|
||||
let statusBadge = '';
|
||||
if(c.status==='accepted') statusBadge = '<span class="badge badge-accepted">已接受</span>';
|
||||
if(c.status==='rejected') statusBadge = '<span class="badge badge-rejected">已拒绝</span>';
|
||||
|
||||
// 统计增减行数
|
||||
let adds = 0, dels = 0;
|
||||
c.diffLines.forEach(l => { if(l.type==='add') adds++; if(l.type==='delete') dels++; });
|
||||
|
||||
d.innerHTML =
|
||||
'<div class="file-header" onclick="this.nextElementSibling.classList.toggle(' + "'" + 'collapsed' + "'" + ')">' +
|
||||
'<span class="file-path">📄 ' + esc(name) + ' <span class="' + badgeCls + '">' + typeMap[c.type] + '</span>' + statusBadge + '</span>' +
|
||||
'<span class="summary-stats"><span class="stat-add">+' + adds + '</span> <span class="stat-del">-' + dels + '</span></span>' +
|
||||
(c.status==='pending' ?
|
||||
'<span class="file-actions">' +
|
||||
'<button class="btn-sm btn-accept" onclick="event.stopPropagation();acceptFile(' + "'" + c.id + "'" + ')">✓</button>' +
|
||||
'<button class="btn-sm btn-reject" onclick="event.stopPropagation();rejectFile(' + "'" + c.id + "'" + ')">✗</button>' +
|
||||
'<button class="btn-sm btn-accept" onclick="event.stopPropagation();acceptFile(' + "'" + c.id + "'" + ')">✓ 全部接受</button>' +
|
||||
'<button class="btn-sm btn-reject" onclick="event.stopPropagation();rejectFile(' + "'" + c.id + "'" + ')">✗ 全部拒绝</button>' +
|
||||
'</span>' : ''
|
||||
) +
|
||||
'</div>' +
|
||||
'<div class="diff-body">' + buildDiff(c) + '</div>';
|
||||
'<div class="diff-body">' +
|
||||
buildEdits(c) +
|
||||
'<div style="padding:6px 12px;font-size:11px;color:var(--vscode-descriptionForeground);border-top:1px solid var(--vscode-panel-border);">📊 聚合 Diff(原始 → 最新)</div>' +
|
||||
buildDiff(c) +
|
||||
'</div>';
|
||||
return d;
|
||||
}
|
||||
|
||||
/** ★ 构建编辑记录列表 */
|
||||
function buildEdits(c){
|
||||
if(!c.edits || c.edits.length === 0) return '';
|
||||
const toolMap = {Edit:'✏️ 编辑', Write:'📝 写入'};
|
||||
let h = '<div class="edits-list">';
|
||||
c.edits.forEach((edit, i) => {
|
||||
let statusCls = '', statusText = '';
|
||||
if(edit.status==='accepted'){ statusCls='badge-accepted'; statusText='已接受'; }
|
||||
if(edit.status==='rejected'){ statusCls='badge-rejected'; statusText='已拒绝'; }
|
||||
|
||||
let editAdds = 0, editDels = 0;
|
||||
(edit.diffLines||[]).forEach(l => { if(l.type==='add') editAdds++; if(l.type==='delete') editDels++; });
|
||||
|
||||
h += '<div class="edit-item">' +
|
||||
'<div class="edit-header" onclick="event.stopPropagation();this.nextElementSibling.classList.toggle(' + "'" + 'collapsed' + "'" + ')">' +
|
||||
'<span class="edit-label">' +
|
||||
'<span class="edit-num">#' + (i+1) + '</span> ' + (toolMap[edit.toolName]||edit.toolName) +
|
||||
' <span class="stat-add">+' + editAdds + '</span> <span class="stat-del">-' + editDels + '</span>' +
|
||||
(statusText ? ' <span class="badge ' + statusCls + ' edit-status">' + statusText + '</span>' : '') +
|
||||
'</span>' +
|
||||
(edit.status==='pending' ?
|
||||
'<span class="edit-actions">' +
|
||||
'<button class="btn-xs btn-accept" onclick="event.stopPropagation();acceptEdit(' + "'" + c.id + "','" + edit.id + "'" + ')">✓</button>' +
|
||||
'<button class="btn-xs btn-reject" onclick="event.stopPropagation();rejectEdit(' + "'" + c.id + "','" + edit.id + "'" + ')">✗</button>' +
|
||||
'</span>' : ''
|
||||
) +
|
||||
'</div>' +
|
||||
'<div class="">' + buildEditDiff(edit) + '</div>' +
|
||||
'</div>';
|
||||
});
|
||||
h += '</div>';
|
||||
return h;
|
||||
}
|
||||
|
||||
/** 单次编辑的 Diff */
|
||||
function buildEditDiff(edit){
|
||||
let lh='', rh='';
|
||||
(edit.diffLines||[]).forEach(l => {
|
||||
if(l.type==='context'){
|
||||
lh += '<div class="diff-line ctx"><span class="ln">'+l.oldLineNum+'</span><span class="lc">'+esc(l.content)+'</span></div>';
|
||||
rh += '<div class="diff-line ctx"><span class="ln">'+l.newLineNum+'</span><span class="lc">'+esc(l.content)+'</span></div>';
|
||||
} else if(l.type==='delete'){
|
||||
lh += '<div class="diff-line del"><span class="ln">'+l.oldLineNum+'</span><span class="lc">'+esc(l.content)+'</span></div>';
|
||||
rh += '<div class="diff-line empty-line"></div>';
|
||||
} else if(l.type==='add'){
|
||||
lh += '<div class="diff-line empty-line"></div>';
|
||||
rh += '<div class="diff-line add"><span class="ln">'+l.newLineNum+'</span><span class="lc">'+esc(l.content)+'</span></div>';
|
||||
}
|
||||
});
|
||||
return '<div class="diff-wrap">' +
|
||||
'<div class="diff-side left"><div class="diff-title">编辑前</div>'+lh+'</div>' +
|
||||
'<div class="diff-side right"><div class="diff-title">编辑后</div>'+rh+'</div>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
function buildDiff(c){
|
||||
if(c.type==='create') return buildSingle(c.newLines||[],'right','新文件');
|
||||
if(c.type==='delete') return buildSingle(c.oldLines||[],'left','已删除');
|
||||
if(c.type==='create') return buildSingle(c.latestContent.split('\\n'),'right','新文件');
|
||||
if(c.type==='delete') return buildSingle(c.originalContent.split('\\n'),'left','已删除');
|
||||
return buildDual(c.diffLines||[]);
|
||||
}
|
||||
|
||||
function buildSingle(lines,side,title){
|
||||
let h = '<div class="diff-wrap"><div class="diff-side ' + side + '"><div class="diff-title">' + title + '</div>';
|
||||
lines.forEach(l => {
|
||||
h += '<div class="diff-line add"><span class="ln">' + (l.num||'') + '</span><span class="lc">' + esc(l.content||l) + '</span></div>';
|
||||
lines.forEach((l,i) => {
|
||||
h += '<div class="diff-line add"><span class="ln">' + (i+1) + '</span><span class="lc">' + esc(l) + '</span></div>';
|
||||
});
|
||||
return h + '</div></div>';
|
||||
}
|
||||
@@ -243,7 +345,7 @@ function buildDual(dlines){
|
||||
|
||||
function esc(s){ return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||
</script>
|
||||
<style>.collapsed .diff-body{display:none;}</style>
|
||||
<style>.collapsed .diff-body{display:none;} .collapsed{display:none;}</style>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user