/** * ⚡ UNIVERSAL MODULAR MONACO WEB-IDE ⚡ * ======================================= * Sprachunabhängige Entwicklungsumgebung mit: * - Dynamischer Monaco Editor Initialisierung (Python, JavaScript, SQL, Rust etc.) * - Delegierung an pluggbare Language Runner via RunnerRegistry * - Tastatur-Shortcuts: Strg+Enter (Run), Strg+Shift+Enter (Test), Strg+S (Save) * - Reaktives Terminal, Auto-Save & Gamification Integration */ (function () { let monacoEditor = null; let currentChapterData = null; let currentLanguage = "python"; let autoSaveTimeout = null; function escapeHtml(text) { if (!text) return ""; return String(text) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function getActiveRunner() { if (window.RunnerRegistry) { const runner = window.RunnerRegistry.get(currentLanguage); if (runner) return runner; } return null; } // 1. Initialisiere Monaco Editor function initMonacoEditor(initialCode = "", langId = "python") { currentLanguage = langId; const editorContainer = document.getElementById("monaco-editor-container"); if (!editorContainer) return; if (window.monaco) { createMonacoInstance(initialCode, langId); return; } const loaderScript = document.createElement("script"); loaderScript.src = "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs/loader.min.js"; loaderScript.onload = () => { window.require.config({ paths: { vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.45.0/min/vs" } }); window.require(["vs/editor/editor.main"], () => { createMonacoInstance(initialCode, langId); }); }; loaderScript.onerror = () => { editorContainer.innerHTML = ``; }; document.head.appendChild(loaderScript); } function createMonacoInstance(initialCode, langId) { const editorContainer = document.getElementById("monaco-editor-container"); if (!editorContainer) return; editorContainer.innerHTML = ""; const runner = getActiveRunner(); const monacoLang = runner ? runner.monacoLanguage : langId; monacoEditor = window.monaco.editor.create(editorContainer, { value: initialCode, language: monacoLang, theme: "vs-dark", fontSize: 14, fontFamily: "'JetBrains Mono', 'Fira Code', Consolas, monospace", automaticLayout: true, minimap: { enabled: false }, scrollBeyondLastLine: false, tabSize: 4, insertSpaces: true, lineNumbers: "on", bracketPairColorization: { enabled: true } }); monacoEditor.onDidChangeModelContent(() => { triggerAutoSave(); }); monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.Enter, () => { runCode(); }); monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyMod.Shift | window.monaco.KeyCode.Enter, () => { runTests(); }); monacoEditor.addCommand(window.monaco.KeyMod.CtrlCmd | window.monaco.KeyCode.KeyS, () => { saveProgressDraft(); }); } window.addEventListener("keydown", (e) => { if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); saveProgressDraft(); } }); // 2. Code-Ausführung via aktivem Language-Runner async function runCode() { if (window.AUTH && !window.AUTH.isLoggedIn()) { window.AUTH.requireAuth({ allowClose: false, title: '🔒 Registrierung erforderlich', message: 'Um Code in der Web-IDE auszuführen, erstelle bitte einen kostenlosen Account oder melde dich an.' }); return; } const code = getEditorCode(); clearTerminal(); const runner = getActiveRunner(); if (!runner) { logToTerminal(`❌ Kein Language-Runner für '${currentLanguage}' registriert.\n`, "error"); return; } logToTerminal(`▶ Starte Ausführung mit ${runner.displayName} (Strg+Enter)...\n----------------------------------------\n`, "info"); const result = await runner.runCode(code, logToTerminal); if (result.success) { logToTerminal("\n----------------------------------------\n✨ Erfolgreich ausgeführt!\n", "success"); } else if (result.error) { suggestErrorInterpreter(result.error); } } // 3. Test-Ausführung via aktivem Language-Runner async function runTests() { if (window.AUTH && !window.AUTH.isLoggedIn()) { window.AUTH.requireAuth({ allowClose: false, title: '🔒 Registrierung erforderlich', message: 'Um automatisierte Tests auszuführen und Lernfortschritt zu speichern, erstelle bitte einen kostenlosen Account oder melde dich an.' }); return; } if (!currentChapterData || !currentChapterData.testCode) { logToTerminal("❌ Keine Unittests für dieses Kapitel gefunden.\n", "error"); return; } const userCode = getEditorCode(); clearTerminal(); const runner = getActiveRunner(); if (!runner) { logToTerminal(`❌ Kein Test-Runner für '${currentLanguage}' verfügbar.\n`, "error"); return; } logToTerminal(`🧪 Führe automatisierte Tests aus (Strg+Shift+Enter)...\n----------------------------------------\n`, "info"); const result = await runner.runTests(userCode, currentChapterData.testCode, logToTerminal, currentChapterData); const isGenuineSuccess = Boolean( result && result.success === true && result.total > 0 && (!result.failures || result.failures === 0) && (!result.errors || result.errors === 0) ); if (isGenuineSuccess) { logToTerminal(`\n🎉 HERVORRAGEND! Alle ${result.total || 'erforderlichen'} Tests erfolgreich bestanden! (+100 XP)\n`, "success"); triggerSuccessCelebration(); recordChapterSolved(); } else { const failCount = (result && (result.failures || result.errors)) ? (result.failures + (result.errors || 0)) : 1; const errorMsg = (result && result.error) ? `\n❌ ${result.error}\n` : `\n⚠️ ${failCount} Test(s) fehlgeschlagen oder unvollständig. Überarbeite deinen Code!\n`; logToTerminal(errorMsg, "warning"); if (result && result.rawOutput) { suggestErrorInterpreter(result.rawOutput); } } // Strukturierten visuellen Test-Report mit Diff-Inspektor rendern if (result && (result.total > 0 || result.rawOutput)) { renderVisualTestReport(result); } } window.jumpToEditorLine = function(line) { if (monacoEditor && line) { monacoEditor.revealLineInCenter(line); monacoEditor.setPosition({ lineNumber: line, column: 1 }); monacoEditor.focus(); } }; function renderVisualTestReport(result) { const term = document.getElementById("workspace-terminal-output"); if (!term) return; const cases = []; const raw = result.rawOutput || ""; const lineRegex = /(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(ok|FAIL|ERROR)/g; let match; while ((match = lineRegex.exec(raw)) !== null) { cases.push({ name: match[1], passed: match[2] === "ok", status: match[2] }); } const fileLineMatch = raw.match(/File\s+["'].*?aufgabe\.\w+["'],\s+line\s+(\d+)/i); const errorLine = fileLineMatch ? parseInt(fileLineMatch[1], 10) : null; let expectedVal = null; let actualVal = null; const assertEqualMatch = raw.match(/AssertionError:\s*(.*?)\s*!=\s*(.*)/); if (assertEqualMatch) { actualVal = assertEqualMatch[1].trim(); expectedVal = assertEqualMatch[2].trim(); } const reportCard = document.createElement("div"); reportCard.style.marginTop = "14px"; const total = result.total || cases.length || 1; const passed = result.passed !== undefined ? result.passed : (result.success ? total : 0); const isSuccess = result.success; reportCard.innerHTML = `