oimp
Version:
A CLI tool for generating OI problem and packages
213 lines (206 loc) • 7.81 kB
JavaScript
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);
}