204 lines
4.5 KiB
TypeScript
204 lines
4.5 KiB
TypeScript
/**
|
|
* Diff 渲染工具
|
|
* 功能:生成代码差异的 HTML 展示
|
|
* 依赖:无
|
|
* 使用场景:在变更面板中展示文件修改的 diff
|
|
*/
|
|
|
|
interface DiffLine {
|
|
type: 'add' | 'remove' | 'context';
|
|
content: string;
|
|
oldLineNumber?: number;
|
|
newLineNumber?: number;
|
|
}
|
|
|
|
/**
|
|
* 简单的 diff 算法(基于行)
|
|
*/
|
|
export function generateDiff(oldContent: string, newContent: string): DiffLine[] {
|
|
const oldLines = oldContent.split('\n');
|
|
const newLines = newContent.split('\n');
|
|
const result: DiffLine[] = [];
|
|
|
|
let oldIndex = 0;
|
|
let newIndex = 0;
|
|
|
|
while (oldIndex < oldLines.length || newIndex < newLines.length) {
|
|
const oldLine = oldLines[oldIndex];
|
|
const newLine = newLines[newIndex];
|
|
|
|
if (oldIndex >= oldLines.length) {
|
|
// 只剩新行
|
|
result.push({
|
|
type: 'add',
|
|
content: newLine,
|
|
newLineNumber: newIndex + 1
|
|
});
|
|
newIndex++;
|
|
} else if (newIndex >= newLines.length) {
|
|
// 只剩旧行
|
|
result.push({
|
|
type: 'remove',
|
|
content: oldLine,
|
|
oldLineNumber: oldIndex + 1
|
|
});
|
|
oldIndex++;
|
|
} else if (oldLine === newLine) {
|
|
// 相同行
|
|
result.push({
|
|
type: 'context',
|
|
content: oldLine,
|
|
oldLineNumber: oldIndex + 1,
|
|
newLineNumber: newIndex + 1
|
|
});
|
|
oldIndex++;
|
|
newIndex++;
|
|
} else {
|
|
// 不同行,标记为删除和添加
|
|
result.push({
|
|
type: 'remove',
|
|
content: oldLine,
|
|
oldLineNumber: oldIndex + 1
|
|
});
|
|
result.push({
|
|
type: 'add',
|
|
content: newLine,
|
|
newLineNumber: newIndex + 1
|
|
});
|
|
oldIndex++;
|
|
newIndex++;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* 将 diff 结果渲染为 HTML
|
|
*/
|
|
export function renderDiffHtml(diffLines: DiffLine[]): string {
|
|
let html = '<div class="diff-viewer">';
|
|
|
|
for (const line of diffLines) {
|
|
const lineClass = `diff-line diff-line-${line.type}`;
|
|
const oldNum = line.oldLineNumber ? `<span class="line-num">${line.oldLineNumber}</span>` : '<span class="line-num"></span>';
|
|
const newNum = line.newLineNumber ? `<span class="line-num">${line.newLineNumber}</span>` : '<span class="line-num"></span>';
|
|
const prefix = line.type === 'add' ? '+' : line.type === 'remove' ? '-' : ' ';
|
|
const escapedContent = escapeHtml(line.content);
|
|
|
|
html += `
|
|
<div class="${lineClass}">
|
|
${oldNum}
|
|
${newNum}
|
|
<span class="line-prefix">${prefix}</span>
|
|
<span class="line-content">${escapedContent}</span>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += '</div>';
|
|
return html;
|
|
}
|
|
|
|
/**
|
|
* 转义 HTML 特殊字符
|
|
*/
|
|
function escapeHtml(text: string): string {
|
|
const map: Record<string, string> = {
|
|
'&': '&',
|
|
'<': '<',
|
|
'>': '>',
|
|
'"': '"',
|
|
"'": '''
|
|
};
|
|
return text.replace(/[&<>"']/g, m => map[m]);
|
|
}
|
|
|
|
/**
|
|
* 获取 diff 样式
|
|
*/
|
|
export function getDiffStyles(): string {
|
|
return `
|
|
.diff-viewer {
|
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
|
font-size: 13px;
|
|
line-height: 1.6;
|
|
background: var(--vscode-editor-background);
|
|
border: 1px solid var(--vscode-panel-border);
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.diff-line {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 4px 0;
|
|
white-space: pre;
|
|
transition: background 0.15s;
|
|
}
|
|
|
|
.diff-line:hover {
|
|
background: var(--vscode-list-hoverBackground);
|
|
}
|
|
|
|
.diff-line-add {
|
|
background: rgba(40, 167, 69, 0.2);
|
|
border-left: 3px solid #28a745;
|
|
}
|
|
|
|
.diff-line-add:hover {
|
|
background: rgba(40, 167, 69, 0.25);
|
|
}
|
|
|
|
.diff-line-remove {
|
|
background: rgba(220, 53, 69, 0.2);
|
|
border-left: 3px solid #dc3545;
|
|
}
|
|
|
|
.diff-line-remove:hover {
|
|
background: rgba(220, 53, 69, 0.25);
|
|
}
|
|
|
|
.diff-line-context {
|
|
background: transparent;
|
|
border-left: 3px solid transparent;
|
|
}
|
|
|
|
.line-num {
|
|
display: inline-block;
|
|
width: 45px;
|
|
text-align: right;
|
|
padding: 0 10px;
|
|
color: var(--vscode-editorLineNumber-foreground);
|
|
user-select: none;
|
|
flex-shrink: 0;
|
|
font-size: 11px;
|
|
opacity: 0.7;
|
|
}
|
|
|
|
.line-prefix {
|
|
display: inline-block;
|
|
width: 24px;
|
|
text-align: center;
|
|
font-weight: bold;
|
|
flex-shrink: 0;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.diff-line-add .line-prefix {
|
|
color: #28a745;
|
|
}
|
|
|
|
.diff-line-remove .line-prefix {
|
|
color: #dc3545;
|
|
}
|
|
|
|
.line-content {
|
|
flex: 1;
|
|
padding: 0 12px 0 8px;
|
|
overflow-x: auto;
|
|
}
|
|
`;
|
|
}
|
|
|