oimp
Version:
A CLI tool for generating OI problem and packages
999 lines (867 loc) • 37.3 kB
JavaScript
import { showSaveMsg } from "./ide-message.js";
// 工具类
// 判断是否是windows
export function isWindow() {
return getOS() === "Windows";
}
// 获取操作系统
export function getOS() {
const userAgent = navigator.userAgent;
if (userAgent.indexOf("Windows") > -1) {
return "Windows";
} else if (userAgent.indexOf("Mac") > -1) {
return "Mac";
} else if (userAgent.indexOf("Linux") > -1) {
return "Linux";
} else {
return "Unknown";
}
}
// 在文件树中选中指定文件并模拟点击
export function selectFileInTree(filePath) {
// 获取jsTree实例
const $tree = $('#file-tree');
if (!$tree.length) {
console.warn('文件树未找到');
return;
}
const tree = $tree.jstree(true);
if (!tree) {
console.warn('jsTree实例未找到');
return;
}
// 去掉路径中的题目ID部分,只保留相对路径
// 例如:mahadon/problem_zh.md -> problem_zh.md
// 例如:mahadon/src/std.cpp -> src/std.cpp
const relativePath = filePath.replace(/^[^\/]+\//, '');
if(isWindow()){
relativePath = relativePath.replace(/\//g, '\\');
}
// 查找文件节点
const allNodes = tree.get_json('#', { flat: true });
const targetNode = allNodes.find(node => {
return node.data && node.data.path === relativePath;
});
if (targetNode) {
// 确保节点可见(展开父节点)
console.log('找到目标节点:', targetNode);
// 递归展开所有父节点
function expandParents(nodeId) {
const node = tree.get_node(nodeId);
if (node && node.parent && node.parent !== '#') {
// 先展开父节点
expandParents(node.parent);
// 然后展开当前父节点
if (tree.is_closed(node.parent)) {
tree.open_node(node.parent, null, false);
}
}
}
// 展开目标节点的所有父节点
expandParents(targetNode.id);
// 选中目标节点
tree.deselect_all();
tree.select_node(targetNode.id, false, false);
// 模拟点击节点,触发相应的逻辑(如打开文件)
tree.trigger('select_node.jstree', [targetNode.id, { originalEvent: { type: 'click' } }]);
console.log(`已选中并模拟点击文件: ${relativePath}`);
} else {
console.warn(`未找到文件: ${relativePath} (原始路径: ${filePath})`);
}
}
// 辅助函数:根据 path 查找节点 id
export function findNodeIdByPath(tree, path) {
const all = tree.get_json('#', { flat: true });
const found = all.find(n => n.data && n.data.path === path);
return found && found.id;
}
// 辅助函数:获取节点 path 列表
export function getNodePaths(tree, ids) {
return ids.map(id => {
const node = tree.get_node(id, false);
return node && node.data && node.data.path;
}).filter(Boolean);
}
// 自动获取题目ID(取文件树根节点或目录名)
export function getCurrentProblemId() {
// 尝试从文件树根节点获取
const tree = document.querySelector('#file-tree .jstree-anchor');
if (tree) return tree.textContent.trim();
// 或从当前目录推断
if (window.location.pathname && window.location.pathname.length > 1) {
return window.location.pathname.split('/')[1];
}
return '';
}
// 判断是否是表格分隔行
export function isTableSeparator(line) {
// 表格分隔行包含 | 和 - 字符,例如: | --- | --- |
return /^\s*\|.*-.*\|.*$/.test(line) || /^\s*[\|\-\s:]+$/.test(line);
}
// 移除C++工具栏
export function removeCppToolbar() {
const toolbar = document.getElementById('cpp-toolbar');
if (toolbar) toolbar.remove();
}
// 添加C++工具栏
export function addCppToolbar() {
removeCppToolbar(); // 先移除已存在的
const toolbar = document.createElement('div');
toolbar.id = 'cpp-toolbar';
toolbar.style.cssText = 'position:absolute;right:12px;top:12px;display:flex;flex-direction:column;gap:8px;z-index:100;';
// 保存按钮
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
saveBtn.title = '保存当前文件 (Ctrl+S)';
saveBtn.style.cssText = 'padding:6px 12px;background:#333;color:#e0e0e0;border:1px solid #555;border-radius:5px;cursor:pointer;font-size:12px;transition:background 0.2s;';
saveBtn.onmouseenter = () => saveBtn.style.background = '#444';
saveBtn.onmouseleave = () => saveBtn.style.background = '#333';
saveBtn.onclick = saveCurrentFile;
// 编译按钮
const compileBtn = document.createElement('button');
compileBtn.textContent = '编译';
compileBtn.title = '保存并编译当前文件';
compileBtn.style.cssText = saveBtn.style.cssText;
compileBtn.onmouseenter = () => compileBtn.style.background = '#444';
compileBtn.onmouseleave = () => compileBtn.style.background = '#333';
compileBtn.onclick = compileCurrentFile;
// 检查文件类型 - 使用当前正在编辑的文件路径
const fileName = currentFile ? currentFile.split('/').pop() : '';
console.log('addCppToolbar - currentFile:', currentFile, 'fileName:', fileName);
const isValidator = fileName === 'validator.cpp';
const isChecker = fileName === 'checker.cpp';
const isGenerator = fileName === 'generator.cpp';
// 所有C++文件都显示保存和编译按钮
toolbar.appendChild(saveBtn);
toolbar.appendChild(compileBtn);
// 添加运行按钮 - 对于普通C++文件和generator.cpp
if (!isValidator && !isChecker) {
const runBtn = document.createElement('button');
runBtn.textContent = '运行';
runBtn.title = '编译并运行当前程序,传入参数"1"';
runBtn.style.cssText = saveBtn.style.cssText;
runBtn.onmouseenter = () => runBtn.style.background = '#444';
runBtn.onmouseleave = () => runBtn.style.background = '#333';
runBtn.onclick = runCurrentFile;
toolbar.appendChild(runBtn);
}
if (isValidator) {
// validator文件:显示测试验证器按钮
const validatorBtn = document.createElement('button');
validatorBtn.textContent = '测试验证器';
validatorBtn.title = '编译并测试validator,使用sample中的in文件';
validatorBtn.style.cssText = saveBtn.style.cssText;
validatorBtn.onmouseenter = () => validatorBtn.style.background = '#444';
validatorBtn.onmouseleave = () => validatorBtn.style.background = '#333';
validatorBtn.onclick = runValidatorTest;
toolbar.appendChild(validatorBtn);
} else if (!isChecker && !isGenerator) {
// 普通C++文件(非validator、checker、generator):显示diff按钮
const diffBtn = document.createElement('button');
diffBtn.textContent = 'diff';
diffBtn.title = '编译并测试样例,显示与标准答案的差异';
diffBtn.style.cssText = saveBtn.style.cssText;
diffBtn.onmouseenter = () => diffBtn.style.background = '#444';
diffBtn.onmouseleave = () => diffBtn.style.background = '#333';
diffBtn.onclick = runDiffTest;
toolbar.appendChild(diffBtn);
}
// checker.cpp 只显示保存和编译按钮
document.getElementById('editor').appendChild(toolbar);
}
// 创建并显示保存确认模态对话框
export async function showSaveConfirmModal() {
return new Promise((resolve) => {
// 创建模态对话框元素
const modal = document.createElement('div');
modal.id = 'save-confirm-modal';
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 10000;
`;
// 创建对话框内容
const modalContent = document.createElement('div');
modalContent.style.cssText = `
background-color: #2d2d2d;
padding: 20px;
border-radius: 6px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
max-width: 400px;
width: 90%;
text-align: center;
`;
// 添加标题
const title = document.createElement('h3');
title.textContent = '保存确认';
title.style.cssText = `
margin-top: 0;
color: #e0e0e0;
`;
// 添加消息文本
const message = document.createElement('p');
message.textContent = '当前文件有未保存内容,是否保存?';
message.style.cssText = `
margin: 20px 0;
color: #ccc;
`;
// 创建按钮容器
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = `
display: flex;
justify-content: center;
gap: 10px;
`;
// 保存按钮
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
saveBtn.style.cssText = `
padding: 8px 16px;
background-color: #007bff;
color: white;
border: 1px solid #0069d9;
border-radius: 4px;
cursor: pointer;
`;
saveBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve('save');
});
// 不保存按钮
const noSaveBtn = document.createElement('button');
noSaveBtn.textContent = '不保存';
noSaveBtn.style.cssText = `
padding: 8px 16px;
background-color: #333;
color: #e0e0e0;
border: 1px solid #444;
border-radius: 4px;
cursor: pointer;
`;
noSaveBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve('nosave');
});
// 取消按钮
const cancelBtn = document.createElement('button');
cancelBtn.textContent = '取消';
cancelBtn.style.cssText = `
padding: 8px 16px;
background-color: #333;
color: #e0e0e0;
border: 1px solid #444;
border-radius: 4px;
cursor: pointer;
`;
cancelBtn.addEventListener('click', () => {
document.body.removeChild(modal);
resolve('cancel');
});
// 组装对话框
modalContent.appendChild(title);
modalContent.appendChild(message);
buttonContainer.appendChild(saveBtn);
buttonContainer.appendChild(noSaveBtn);
buttonContainer.appendChild(cancelBtn);
modalContent.appendChild(buttonContainer);
modal.appendChild(modalContent);
// 添加到页面
document.body.appendChild(modal);
// 点击模态框背景关闭
modal.addEventListener('click', (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
resolve('cancel');
}
});
});
}
export function updateCurrentFileDisplay() {
const el = document.getElementById('current-file');
el.innerHTML = currentFile ? ((isDirty || isMdDirty) ? currentFile + ' <span style="color:red">*</span>' : currentFile) : '';
}
// save current file
export function saveCurrentFile() {
// showSaveMsg('保存当前文件中...');
if (!window.currentFile) return showSaveMsg('未选择文件', true);
let content = '';
if (window.currentIsMarkdown) {
content = window.mdTextarea.value;
saveMdFile(currentFile, content);
} else {
content = window.codeEditor.getValue();
saveCodeFile(currentFile, content);
}
}
// 保存md file
export async function saveMdFile(filename, fileContent) {
if (window.isMdDirty)
await saveFile(filename, fileContent);
window.isMdDirty = false;
}
// 保存code file
export async function saveCodeFile(filename, fileContent) {
if (window.isDirty)
await saveFile(filename, fileContent);
window.isDirty = false;
}
// 保存文件内容
export async function saveFile(filename, fileContent) {
//if (!currentFile) return showSaveMsg('未选择文件', true);
const res = await fetch('/api/file', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ path: filename, content: fileContent })
});
if (res.ok) {
lastSavedContent = fileContent;
window.isDirty = false;
window.isMdDirty = false;
showSaveMsg('已保存');
updateCurrentFileDisplay();
}
else
showSaveMsg('保存失败', true);
}
// 新建按钮自定义 tooltip
export function addBtnTooltip(btn, text) {
// 添加null检查以防止错误
if (!btn) return;
let tooltip = null;
btn.removeAttribute('title');
btn.addEventListener('mouseenter', function (e) {
if (tooltip) return;
tooltip = document.createElement('div');
tooltip.textContent = text;
tooltip.style.position = 'fixed';
tooltip.style.left = (e.clientX + 12) + 'px';
tooltip.style.top = (e.clientY - 8) + 'px';
tooltip.style.background = '#222';
tooltip.style.color = '#fff';
tooltip.style.fontSize = '13px';
tooltip.style.padding = '3px 10px';
tooltip.style.borderRadius = '6px';
tooltip.style.boxShadow = '0 2px 8px rgba(0,0,0,0.18)';
tooltip.style.zIndex = 9999;
tooltip.style.pointerEvents = 'none';
document.body.appendChild(tooltip);
});
btn.addEventListener('mousemove', function (e) {
if (tooltip) {
tooltip.style.left = (e.clientX + 12) + 'px';
tooltip.style.top = (e.clientY - 8) + 'px';
}
});
btn.addEventListener('mouseleave', function () {
if (tooltip) { document.body.removeChild(tooltip); tooltip = null; }
});
}
// 运行validator测试
export async function runValidatorTest() {
if (!window.currentFile) {
showSaveMsg('错误:未选择文件', true);
return;
}
// 先保存文件
saveCurrentFile();
const ext = window.currentFile.split('.').pop().toLowerCase();
if (!['cpp', 'cc', 'cxx'].includes(ext)) {
showSaveMsg('错误:不是C++文件', true);
return;
}
// 检查是否是validator文件
const fileName = window.currentFile.split('/').pop();
if (fileName !== 'validator.cpp') {
showSaveMsg('错误:只有validator.cpp文件支持此功能', true);
return;
}
// 获取sample文件列表
try {
const sampleRes = await fetch('/api/tree?path=sample');
if (!sampleRes.ok) {
showSaveMsg('错误:无法获取sample目录', true);
return;
}
const sampleFiles = await sampleRes.json();
const inFiles = sampleFiles.filter(f => f.text.endsWith('.in')).map(f => f.text);
if (inFiles.length === 0) {
showSaveMsg('错误:sample目录中没有.in文件', true);
return;
}
// 构建命令序列
const commands = [];
const problemId = getCurrentProblemId();
// 获取平台信息并选择跨平台命令
let rmCmd = 'rm -f'; // 默认使用rm
let fileCheckCmd = 'if [ -f'; // 默认使用bash语法
try {
const platformRes = await fetch('/api/platform');
if (platformRes.ok) {
const platformData = await platformRes.json();
rmCmd = platformData.rmCommand || 'rm -f';
fileCheckCmd = platformData.fileCheckCommand || 'if [ -f';
console.log(`[VALIDATOR] 平台信息:`, platformData);
}
} catch (err) {
console.warn('无法获取平台信息,使用默认命令');
}
console.log(`[VALIDATOR] 最终使用的命令:`, { rmCmd, fileCheckCmd });
// 构建清理、编译和检查命令
const executablePath = `${problemId}/${currentFile.replace(/\.(cpp|cc|cxx)$/, '')}`;
const cleanCmd = `${rmCmd} "${executablePath}"`;
const compileCmd = `g++ -O2 -std=c++14 -o "${problemId}/${currentFile.replace(/\.(cpp|cc|cxx)$/, '')}" "${problemId}/${currentFile}"`;
// 先添加清理命令
commands.push(cleanCmd);
console.log(`[VALIDATOR] 清理命令:`, cleanCmd);
// 再添加编译命令
commands.push(compileCmd);
console.log(`[VALIDATOR] 编译命令:`, compileCmd);
// 添加编译结果检查命令
const checkCompileCmd = `${fileCheckCmd} "${executablePath}" ]; then echo "编译成功: ${executablePath} 已生成"; else echo "编译失败: ${executablePath} 不存在"; exit 1; fi`;
commands.push(checkCompileCmd);
console.log(`[VALIDATOR] 编译检查命令:`, checkCompileCmd);
// 构建所有sample的validator测试命令
const testCommands = [];
for (const inFile of inFiles) {
// 单个sample的validator测试命令
let testInputPath = `${problemId}/sample/${inFile}`;
// 处理Windows平台路径分隔符
if (isWindow()) {
testInputPath = testInputPath.replace(/\//g, '\\');
}
const singleTestCmd = `"${executablePath}" < "${testInputPath}"`;
testCommands.push(singleTestCmd);
console.log(`[VALIDATOR] 构建测试命令 ${testCommands.length}:`, {
inFile,
singleTestCmd
});
}
// 添加所有测试命令
commands.push(...testCommands);
console.log(`[VALIDATOR] 总共 ${commands.length} 个命令:`, commands);
// 在终端中执行命令
if (window.terminalWs && window.terminalWs.readyState === WebSocket.OPEN) {
// 清空终端显示并滚动到底部
if (window.term) {
//window.term.clear();
// 确保滚动到底部
setTimeout(() => {
window.term.scrollToBottom();
}, 100);
}
// 逐个发送命令到终端
for (let i = 0; i < commands.length; i++) {
const cmd = commands[i];
console.log(`[VALIDATOR] 发送第 ${i + 1}/${commands.length} 个命令:`, cmd);
// 检查命令类型并显示相应提示
if (cmd.includes('rm -f')) {
showSaveMsg('清理旧的可执行文件...');
} else if (cmd.includes('g++') && cmd.includes('-o')) {
showSaveMsg(`正在编译 validator.cpp...`);
} else if (cmd.includes('if [ -f') || (cmd.includes('if exist') && cmd.includes('echo.'))) {
showSaveMsg('检查编译结果...');
} else {
const sampleMatch = cmd.match(/sample(\d+)\.in/);
if (sampleMatch) {
showSaveMsg(`验证 sample${sampleMatch[1]}...`);
} else {
showSaveMsg(`执行命令 ${i + 1}/${commands.length}...`);
}
}
window.terminalWs.send(cmd + '\r');
// 发送命令后立即滚动到底部
setTimeout(() => {
scrollTerminalToBottom();
}, 50);
// 等待一段时间再发送下一个命令
if (i < commands.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000)); // 增加等待时间
}
}
showSaveMsg('所有validator测试命令已发送到终端');
} else {
showSaveMsg('错误:终端WebSocket连接不可用', true);
}
} catch (err) {
showSaveMsg('validator测试失败: ' + err.message, true);
}
}
// 自动滚动终端到底部
function scrollTerminalToBottom() {
if (window.term) {
// 滚动到最底部
window.term.scrollLines(window.term.buffer.active.viewportY);
}
}
// 清除终端内容
function clearTerminal() {
if (window.term) {
//window.term.clear();
}
}
// 清除终端内容并滚动到底部
function clearTerminalAndScroll() {
clearTerminal();
// 延迟滚动确保清除完成
setTimeout(() => {
scrollTerminalToBottom();
}, 50);
}
// 将函数挂载到window对象,方便控制台测试
window.scrollTerminalToBottom = scrollTerminalToBottom;
window.clearTerminal = clearTerminal;
window.clearTerminalAndScroll = clearTerminalAndScroll;
// 编译当前文件
export async function compileCurrentFile() {
const currentFile = window.currentFile;
if (!window.currentFile) {
showSaveMsg('错误:未选择文件', true);
return;
}
// 先保存文件
await saveCurrentFile();
const ext = window.currentFile.split('.').pop().toLowerCase();
if (!['cpp', 'cc', 'cxx'].includes(ext)) {
showSaveMsg('错误:不是C++文件', true);
return;
}
// 获取平台信息
let rmCmd = 'rm -f'; // 默认使用rm
try {
const platformRes = await fetch('/api/platform');
if (platformRes.ok) {
const platformData = await platformRes.json();
rmCmd = platformData.rmCommand || 'rm -f';
}
} catch (err) {
console.warn('无法获取平台信息,使用默认命令');
}
// 构建清理和编译命令
const fileName = window.currentFile.split('/').pop();
const problemId = getCurrentProblemId();
let executablePath = `${problemId}/${window.currentFile.replace(/\.(cpp|cc|cxx)$/, '')}`;
let currentFilePath = `${problemId}/${window.currentFile}`;
// 处理Windows平台路径分隔符
if (isWindow()) {
executablePath = executablePath.replace(/\//g, '\\');
currentFilePath = currentFilePath.replace(/\//g, '\\');
}
const cleanCmd = `${rmCmd} "${executablePath}"`;
const compileCmd = `g++ -O2 -std=c++14 -o "${executablePath}" "${currentFilePath}"`;
// 在终端中执行编译命令
if (window.terminalWs && window.terminalWs.readyState === WebSocket.OPEN) {
// 清空终端显示并滚动到底部
clearTerminalAndScroll();
// 发送清理命令
console.log(`[COMPILE] 发送清理命令:`, cleanCmd);
showSaveMsg('清理旧的可执行文件...');
window.terminalWs.send(cleanCmd + '\r');
// 延迟发送编译命令
setTimeout(() => {
console.log(`[COMPILE] 发送编译命令:`, compileCmd);
showSaveMsg(`正在编译 ${currentFile}...`);
window.terminalWs.send(compileCmd + '\r');
// 发送命令后立即滚动到底部
setTimeout(() => {
scrollTerminalToBottom();
}, 50);
}, 500);
showSaveMsg('编译命令已发送到终端');
} else {
showSaveMsg('错误:终端WebSocket连接不可用', true);
}
}
// 运行当前文件
async function runCurrentFile() {
if (!window.currentFile) {
showSaveMsg('错误:未选择文件', true);
return;
}
// 先保存文件
await saveCurrentFile();
const ext = window.currentFile.split('.').pop().toLowerCase();
if (!['cpp', 'cc', 'cxx'].includes(ext)) {
showSaveMsg('错误:不是C++文件', true);
return;
}
// 检查是否是特殊文件
const fileName = currentFile.split('/').pop();
if (['validator.cpp', 'checker.cpp'].includes(fileName)) {
showSaveMsg('错误:特殊文件不支持直接运行', true);
return;
}
// 获取平台信息
let rmCmd = 'rm -f'; // 默认使用rm
let isWindows = false; // 默认不是Windows
let fileCheckCommand = 'if [ -f'; // 默认使用Unix/Linux/macOS语法
try {
const platformRes = await fetch('/api/platform');
if (platformRes.ok) {
const platformData = await platformRes.json();
rmCmd = platformData.rmCommand || 'rm -f';
isWindows = platformData.isWindows || false;
fileCheckCommand = platformData.fileCheckCommand || 'if [ -f';
}
} catch (err) {
console.warn('无法获取平台信息,使用默认命令');
}
// 构建清理、编译和运行命令
const problemId = getCurrentProblemId();
let executablePath = `${problemId}/${currentFile.replace(/\.(cpp|cc|cxx)$/, '')}`;
let currentFilePath = `${problemId}/${currentFile}`;
// 处理Windows平台路径分隔符
if (isWindow()) {
executablePath = executablePath.replace(/\//g, '\\');
currentFilePath = currentFilePath.replace(/\//g, '\\');
}
const cleanCmd = `${rmCmd} "${executablePath}"`;
const compileCmd = `g++ -O2 -std=c++14 -o "${executablePath}" "${currentFilePath}"`;
// 构建跨平台的文件检查和运行命令
let fileCheckCmd;
if (isWindow()) {
// Windows平台使用cmd语法
fileCheckCmd = `if exist "${executablePath}" ("${executablePath}" 1)`;
} else {
// Unix/Linux/macOS平台使用bash语法
fileCheckCmd = `if [ -f "${executablePath}" ]; then "${executablePath}" 1; fi`;
}
// 在终端中执行命令
if (window.terminalWs && window.terminalWs.readyState === WebSocket.OPEN) {
// 清空终端显示并滚动到底部
clearTerminalAndScroll();
// 发送清理命令
console.log(`[RUN] 发送清理命令:`, cleanCmd);
showSaveMsg('清理旧的可执行文件...');
window.terminalWs.send(cleanCmd + '\r');
// 延迟发送编译命令
setTimeout(() => {
console.log(`[RUN] 发送编译命令:`, compileCmd);
showSaveMsg(`正在编译 ${currentFile}...`);
window.terminalWs.send(compileCmd + '\r');
// 编译完成后运行程序
setTimeout(() => {
// 检查可执行文件是否存在并直接运行或不执行
console.log(`[RUN] 发送运行命令:`, fileCheckCmd);
showSaveMsg(`正在运行 ${currentFile}...`);
window.terminalWs.send(fileCheckCmd + '\r');
// 发送命令后立即滚动到底部
setTimeout(() => {
scrollTerminalToBottom();
}, 50);
}, 1000);
// 发送命令后立即滚动到底部
setTimeout(() => {
scrollTerminalToBottom();
}, 50);
}, 500);
showSaveMsg('运行命令已发送到终端');
} else {
showSaveMsg('错误:终端WebSocket连接不可用', true);
}
}
// 运行diff测试
async function runDiffTest() {
const currentFile = window.currentFile;
if (!currentFile) {
showSaveMsg('错误:未选择文件', true);
return;
}
// 先保存文件
await saveCurrentFile();
const ext = currentFile.split('.').pop().toLowerCase();
if (!['cpp', 'cc', 'cxx'].includes(ext)) {
showSaveMsg('错误:不是C++文件', true);
return;
}
// 检查是否是特殊文件
const fileName = currentFile.split('/').pop();
if (['validator.cpp', 'check.cpp', 'generator.cpp'].includes(fileName)) {
showSaveMsg('错误:validator.cpp、check.cpp、generator.cpp 不支持diff测试', true);
return;
}
// 获取sample文件列表
try {
const sampleRes = await fetch('/api/tree?path=sample');
if (!sampleRes.ok) {
showSaveMsg('错误:无法获取sample目录', true);
return;
}
const sampleFiles = await sampleRes.json();
const inFiles = sampleFiles.filter(f => f.text.endsWith('.in')).map(f => f.text);
if (inFiles.length === 0) {
showSaveMsg('错误:sample目录中没有.in文件', true);
return;
}
// 构建命令序列
const commands = [];
const problemId = getCurrentProblemId();
const baseName = currentFile.split('/').pop().replace(/\.(cpp|cc|cxx)$/, '');
// 获取平台信息并选择跨平台命令
let diffCmd = 'diff -B -w'; // 默认使用diff,忽略空行和空白字符
let rmCmd = 'rm -f'; // 默认使用rm
let isWindows = false; // 默认不是Windows
let fileCheckCommand = 'if [ -f'; // 默认使用bash语法
let mdCommand = 'mkdir -p';
try {
const platformRes = await fetch('/api/platform');
if (platformRes.ok) {
const platformData = await platformRes.json();
diffCmd = platformData.diffCommand; // 已经包含了忽略参数
rmCmd = platformData.rmCommand || 'rm -f';
isWindows = platformData.isWindows || false;
fileCheckCommand = platformData.fileCheckCommand || 'if [ -f';
mdCommand = platformData.mdCommand || 'md';
console.log(`[DIFF] 平台信息:`, platformData);
console.log(`[DIFF] 使用忽略空白和空行的diff命令:`, diffCmd);
}
} catch (err) {
console.warn('无法获取平台信息,使用默认命令');
}
console.log(`[DIFF] 最终使用的命令:`, { diffCmd, rmCmd, fileCheckCommand });
// 构建清理、编译和检查命令
let executablePath = `${problemId}/${currentFile.replace(/\.(cpp|cc|cxx)$/, '')}`;
let currentFilePath = `${problemId}/${currentFile}`;
let outputsPath = `${problemId}/outputs/${baseName}`;
// 处理Windows平台路径分隔符
if (isWindow()) {
executablePath = executablePath.replace(/\//g, '\\');
currentFilePath = currentFilePath.replace(/\//g, '\\');
outputsPath = outputsPath.replace(/\//g, '\\');
}
const cleanCmd = `${rmCmd} "${executablePath}"`;
const compileCmd = `g++ -O2 -std=c++14 -o "${executablePath}" "${currentFilePath}"`;
mdCommand = isWindows ? 'md ' : 'mkdir -p ';
const mkdirCmd = `${mdCommand} ${outputsPath}`;
// 先添加清理命令
commands.push(cleanCmd);
console.log(`[DIFF] 清理命令:`, cleanCmd);
// 再添加编译命令
commands.push(compileCmd);
console.log(`[DIFF] 编译命令:`, compileCmd);
// 添加跨平台的编译结果检查命令(无echo输出)
let checkCompileCmd;
if (isWindows) {
// Windows平台使用cmd语法,无echo输出
checkCompileCmd = `if exist "${executablePath}" (echo.) else (echo.)`;
} else {
// Unix/Linux/macOS平台使用bash语法,无echo输出
checkCompileCmd = `if [ -f "${executablePath}" ]; then true; else true; fi`;
}
commands.push(checkCompileCmd);
console.log(`[DIFF] 编译检查命令:`, checkCompileCmd);
// 构建所有sample的测试命令
const testCommands = [];
for (const inFile of inFiles) {
const ansFile = inFile.replace('.in', '.ans');
const outputFile = inFile.replace('.in', '.out');
// 使用平台特定的比较命令,已包含忽略空白和空行参数
let diffCmdFinal = `${diffCmd} "${problemId}/outputs/${baseName}/${outputFile}" "${problemId}/sample/${ansFile}"`;
let testInputPath = `${problemId}/sample/${inFile}`;
let testOutputPath = `${problemId}/outputs/${baseName}/${outputFile}`;
// 处理Windows平台路径分隔符
if (isWindow()) {
testInputPath = testInputPath.replace(/\//g, '\\');
testOutputPath = testOutputPath.replace(/\//g, '\\');
diffCmdFinal = diffCmdFinal.replace(/\//g, '\\');
}
// 单个sample的测试命令
const singleTestCmd = `"${executablePath}" < "${testInputPath}" > "${testOutputPath}" && ${diffCmdFinal}`;
testCommands.push(singleTestCmd);
console.log(`[DIFF] 构建测试命令 ${testCommands.length}:`, {
inFile,
ansFile,
outputFile,
diffCmd,
diffCmdFinal,
singleTestCmd
});
}
// 添加mkdir命令
commands.push(mkdirCmd);
console.log(`[DIFF] 创建目录命令:`, mkdirCmd);
// 添加所有测试命令
commands.push(...testCommands);
console.log(`[DIFF] 总共 ${commands.length} 个命令:`, commands);
// 在终端中执行命令
if (window.terminalWs && window.terminalWs.readyState === WebSocket.OPEN) {
// 清空终端显示并滚动到底部
if (window.term) {
// window.term.clear();
// 确保滚动到底部
setTimeout(() => {
window.term.scrollToBottom();
}, 100);
}
// 逐个发送命令到终端
for (let i = 0; i < commands.length; i++) {
const cmd = commands[i];
console.log(`[DIFF] 发送第 ${i + 1}/${commands.length} 个命令:`, cmd);
// 检查命令类型并显示相应提示
if (cmd.includes('rm -f')) {
showSaveMsg('清理旧的可执行文件...');
} else if (cmd.includes('g++') && cmd.includes('-o')) {
showSaveMsg(`正在编译 ${currentFile}...`);
} else if (cmd.includes('if [ -f')) {
showSaveMsg('检查编译结果...');
} else if (cmd.includes('mkdir')|| cmd.includes('md')) {
showSaveMsg('创建输出目录...');
} else {
const sampleMatch = cmd.match(/sample(\d+)\.in/);
if (sampleMatch) {
showSaveMsg(`测试 sample${sampleMatch[1]}...`);
} else {
showSaveMsg(`执行命令 ${i + 1}/${commands.length}...`);
}
}
window.terminalWs.send(cmd + '\r');
// 发送命令后立即滚动到底部
setTimeout(() => {
scrollTerminalToBottom();
}, 50);
// 等待一段时间再发送下一个命令
if (i < commands.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000)); // 增加等待时间,让用户看到编译结果
}
}
showSaveMsg('所有diff测试命令已发送到终端');
} else {
showSaveMsg('错误:终端WebSocket连接不可用', true);
}
} catch (err) {
showSaveMsg('diff测试失败: ' + err.message, true);
}
}
export function syncMainHeight() {
const previewMain = document.getElementById('ide-preview-main');
const ideMain = document.getElementById('ide-main');
const fileTree = document.getElementById('file-tree');
const editorDiv = document.getElementById('editor');
const previewDiv = document.getElementById('preview');
const topbar = document.getElementById('topbar');
const workflowBar = document.getElementById('workflow-bar');
const terminal = document.getElementById('terminal');
// 计算总高度,减去topbar和workflow-bar的实际高度
const totalHeight = window.innerHeight - (topbar ? topbar.offsetHeight : 0) - (workflowBar ? workflowBar.offsetHeight : 0);
// 终端高度
const termH = terminal ? terminal.offsetHeight : 200;
// 主区高度
const mainH = Math.max(0, totalHeight - termH);
// 同步所有主区域的高度
if (previewMain) previewMain.style.height = mainH + 'px';
if (ideMain) ideMain.style.height = mainH + 'px';
if (fileTree) fileTree.style.height = mainH + 'px';
if (editorDiv) editorDiv.style.height = mainH + 'px';
if (previewDiv) previewDiv.style.height = mainH + 'px';
// 同步编辑器布局(如果有Monaco编辑器)
if (window.editor && window.editor.layout) {
window.editor.layout();
}
}