UNPKG

oimp

Version:

A CLI tool for generating OI problem and packages

999 lines (867 loc) 37.3 kB
"use strict" 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(); } }