UNPKG

oimp

Version:

A CLI tool for generating OI problem and packages

213 lines (206 loc) 7.81 kB
import { getCurrentProblemId } from "./ide-utility.js"; // terminal 相关 export async function initTerminal(TerminalDivId, FitAddonDivId) { if (window.Terminal && window.FitAddon) { let term = new Terminal({ fontSize: 14, theme: { background: "#1e1e1e" } }); let fitAddon = new window.FitAddon.FitAddon(); term.loadAddon(fitAddon); term.open(document.getElementById('terminal')); fitAddon.fit(); const ws = new WebSocket(`ws://${location.host}/api/terminal`); ws.onmessage = e => { term.write(e.data); // 移除自动滚动留两行逻辑 }; term.onData(data => ws.send(data)); window.term = term; window.fitAddon = fitAddon; window.terminalWs = ws; window.addEventListener('resize', function () { if (fitAddon) fitAddon.fit(); }); // 给终端容器加底部 padding document.getElementById('terminal').style.paddingBottom = '32px'; } else { console.error('xterm.js or FitAddon not loaded'); } terminalButtons(); } function terminalButtons() { const terminalDiv = document.getElementById('terminal'); const previewMain = document.getElementById('ide-preview-main'); if (!terminalDiv || !previewMain) return; // 按钮容器 const btnBar = document.createElement('div'); btnBar.className = 'terminal-btn-bar'; btnBar.style.position = 'absolute'; btnBar.style.right = '12px'; btnBar.style.top = '6px'; btnBar.style.zIndex = '20'; btnBar.style.display = 'flex'; btnBar.style.gap = '6px'; // 最小化按钮 const minBtn = document.createElement('button'); minBtn.textContent = '_'; minBtn.setAttribute('data-tooltip', '最小化终端'); minBtn.className = 'min-btn'; minBtn.onclick = function () { terminalDiv.style.height = '36px'; terminalDiv.style.minHeight = '0'; terminalDiv.style.maxHeight = '36px'; previewMain.style.height = `calc(100vh - 40px - 36px)`; if (window.syncMainHeight) window.syncMainHeight(); if (window.fitAddon) window.fitAddon.fit(); }; // 最大化按钮 const maxBtn = document.createElement('button'); maxBtn.textContent = '^'; maxBtn.setAttribute('data-tooltip', '最大化终端'); maxBtn.className = 'max-btn'; maxBtn.onclick = function () { terminalDiv.style.height = '90vh'; terminalDiv.style.minHeight = '200px'; terminalDiv.style.maxHeight = 'none'; previewMain.style.height = 'calc(1vh - 40px)'; if (window.syncMainHeight) window.syncMainHeight(); if (window.fitAddon) window.fitAddon.fit(); }; // 还原按钮 const restoreBtn = document.createElement('button'); restoreBtn.textContent = '='; restoreBtn.setAttribute('data-tooltip', '还原终端'); restoreBtn.className = 'restore-btn'; restoreBtn.onclick = function () { terminalDiv.style.height = '180px'; terminalDiv.style.minHeight = ''; terminalDiv.style.maxHeight = ''; previewMain.style.height = `calc(100vh - 40px - 180px)`; if (window.syncMainHeight) window.syncMainHeight(); if (window.fitAddon) window.fitAddon.fit(); }; // 字体缩小按钮 const fontMinusBtn = document.createElement('button'); fontMinusBtn.textContent = 'A-'; fontMinusBtn.setAttribute('data-tooltip', '减小终端字体'); fontMinusBtn.className = 'font-btn'; fontMinusBtn.onclick = function () { if (window.term) { let size = getTerminalFontSize(); size = Math.max(8, size - 1); setTerminalFontSize(size); if (window.fitAddon) window.fitAddon.fit(); } }; // 兼容 xterm.js 4.x/5.x 的字体大小读写 function getTerminalFontSize() { if (window.term && window.term.getOption) { return window.term.getOption('fontSize'); } else if (window.term && window.term.options && window.term.options.fontSize) { return window.term.options.fontSize; } else { return 14; } } function setTerminalFontSize(size) { if (window.term && window.term.setOption) { window.term.setOption('fontSize', size); } else if (window.term && window.term.options) { window.term.options.fontSize = size; if (window.term.refresh && window.term.rows) { window.term.refresh(0, window.term.rows - 1); } } } // 字体放大按钮 const fontPlusBtn = document.createElement('button'); fontPlusBtn.textContent = 'A+'; fontPlusBtn.setAttribute('data-tooltip', '增大终端字体'); fontPlusBtn.className = 'font-btn'; fontPlusBtn.onclick = function () { if (window.term) { let size = getTerminalFontSize(); size = Math.min(40, size + 1); setTerminalFontSize(size); if (window.fitAddon) window.fitAddon.fit(); } }; btnBar.appendChild(minBtn); btnBar.appendChild(maxBtn); btnBar.appendChild(restoreBtn); btnBar.appendChild(fontMinusBtn); btnBar.appendChild(fontPlusBtn); terminalDiv.style.position = 'relative'; terminalDiv.appendChild(btnBar); // 自定义 tooltip 逻辑 let tooltip = document.getElementById('terminal-tooltip'); if (!tooltip) { tooltip = document.createElement('div'); tooltip.id = 'terminal-tooltip'; tooltip.className = 'terminal-tooltip'; document.body.appendChild(tooltip); } function showTooltip(e, text) { tooltip.textContent = text; tooltip.style.opacity = 1; const rect = e.target.getBoundingClientRect(); tooltip.style.left = (rect.left + rect.width / 2 - tooltip.offsetWidth / 2) + 'px'; tooltip.style.top = (rect.bottom + 6) + 'px'; } function hideTooltip() { tooltip.style.opacity = 0; } [minBtn, maxBtn, restoreBtn, fontMinusBtn, fontPlusBtn].forEach(btn => { btn.addEventListener('mouseenter', e => showTooltip(e, btn.getAttribute('data-tooltip'))); btn.addEventListener('mouseleave', hideTooltip); }); if (terminalDiv) { terminalDiv.addEventListener('transitionend', function (e) { if (window.fitAddon) window.fitAddon.fit(); }); } } // 命令执行与日志弹窗 export function runCommandInTerminal(cmd) { // 自动带上题目ID作为最后一个参数 const problemId = window.problemName || getCurrentProblemId(); if (!problemId) { alert('无法自动识别题目ID'); return; } console.log(problemId); // 构建完整命令 const fullCommand = `oimp ${cmd} ${problemId}`; // 在终端中显示命令 if (window.term) { window.term.focus(); window.term.write(`${fullCommand}\r`); } // 通过WebSocket发送命令到后端执行 if (window.terminalWs && window.terminalWs.readyState === WebSocket.OPEN) { window.terminalWs.send(fullCommand + '\r'); } else { console.error('WebSocket连接未建立,无法执行命令'); alert('终端连接未建立,请刷新页面重试'); } } // 自动滚动终端到底部 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); }