|
110 | 110 | renderLineHighlight: "all", |
111 | 111 | padding: { top: 12, bottom: 12 } |
112 | 112 | }); |
| 113 | + window.monacoEditor = monacoEditor; |
113 | 114 |
|
114 | 115 | // Shortcut: Ctrl+Enter / Cmd+Enter to Run |
115 | 116 | monacoEditor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter, function () { |
|
131 | 132 | // Python Runtime Harness (Pyodide & MicroPython WASM) |
132 | 133 | // ========================================================================= |
133 | 134 |
|
| 135 | + let micropythonInstance = null; |
| 136 | + let mpyPackagesInstalled = false; |
| 137 | + |
| 138 | + async function getMicroPython() { |
| 139 | + if (micropythonInstance) return micropythonInstance; |
| 140 | + |
| 141 | + setStatus("Booting MicroPython engine…", "busy"); |
| 142 | + logConsole("[Runtime] Loading MicroPython WASM environment…\n", "info"); |
| 143 | + |
| 144 | + const { loadMicroPython } = await import("https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript/micropython.mjs"); |
| 145 | + micropythonInstance = await loadMicroPython({ |
| 146 | + stdout: (text) => logConsole(text + "\n"), |
| 147 | + stderr: (text) => logConsole(text + "\n", "error"), |
| 148 | + url: "https://cdn.jsdelivr.net/npm/@micropython/micropython-webassembly-pyscript/micropython.wasm" |
| 149 | + }); |
| 150 | + |
| 151 | + // Setup sys.path and PyScript/DOM shims |
| 152 | + try { |
| 153 | + micropythonInstance.runPython(` |
| 154 | +import sys, os |
| 155 | +if "." not in sys.path: |
| 156 | + sys.path.insert(0, ".") |
| 157 | +if "/lib" not in sys.path: |
| 158 | + sys.path.append("/lib") |
| 159 | +
|
| 160 | +# PyScript compatibility shims |
| 161 | +try: |
| 162 | + import js |
| 163 | + from js import document, window |
| 164 | + class _PS: |
| 165 | + pass |
| 166 | + ps = _PS() |
| 167 | + ps.document = document |
| 168 | + ps.window = window |
| 169 | + sys.modules["pyscript"] = ps |
| 170 | +except Exception: |
| 171 | + pass |
| 172 | +`); |
| 173 | + } catch (e) { |
| 174 | + console.warn("MicroPython init shim:", e); |
| 175 | + } |
| 176 | + |
| 177 | + // Preload local mip.py into MicroPython filesystem |
| 178 | + try { |
| 179 | + const mipRes = await fetch("/vendor/pydevices-chrome/mip.py"); |
| 180 | + if (mipRes.ok) { |
| 181 | + const mipCode = await mipRes.text(); |
| 182 | + micropythonInstance.FS.writeFile("mip.py", mipCode); |
| 183 | + } |
| 184 | + } catch (e) { |
| 185 | + console.warn("Could not preload local mip.py for MicroPython:", e); |
| 186 | + } |
| 187 | + |
| 188 | + setStatus("Ready", "ready"); |
| 189 | + logConsole("[Runtime] MicroPython environment ready.\n", "success"); |
| 190 | + return micropythonInstance; |
| 191 | + } |
| 192 | + |
134 | 193 | async function getPyodide() { |
135 | 194 | if (pyodideInstance) return pyodideInstance; |
136 | 195 |
|
@@ -206,8 +265,49 @@ if "pyscript" not in sys.modules: |
206 | 265 | logConsole(`\n--- Execution started (${new Date().toLocaleTimeString()}) ---\n`, "dim"); |
207 | 266 |
|
208 | 267 | const t0 = performance.now(); |
| 268 | + const runtime = elRuntimeSelect ? elRuntimeSelect.value : "pyodide"; |
209 | 269 |
|
210 | 270 | try { |
| 271 | + if (runtime === "mpy") { |
| 272 | + const mp = await getMicroPython(); |
| 273 | + |
| 274 | + setCanvasResolution(currentResolution.width, currentResolution.height, currentResolution.shape); |
| 275 | + |
| 276 | + // Set MicroPython display environment |
| 277 | + mp.runPython(` |
| 278 | +import sys, os |
| 279 | +if not hasattr(os, "environ"): |
| 280 | + os.environ = {} |
| 281 | +os.environ["PYDEVICES_WIDTH"] = "${currentResolution.width}" |
| 282 | +os.environ["PYDEVICES_HEIGHT"] = "${currentResolution.height}" |
| 283 | +os.environ["PYDEVICES_CANVAS_ID"] = "display_canvas" |
| 284 | +`); |
| 285 | + |
| 286 | + mp.FS.writeFile("main.py", code); |
| 287 | + mp.runPython(code); |
| 288 | + |
| 289 | + // Pump LVGL / display refresh |
| 290 | + try { |
| 291 | + mp.runPython(` |
| 292 | +import sys |
| 293 | +if "lvgl" in sys.modules: |
| 294 | + lv = sys.modules["lvgl"] |
| 295 | + if hasattr(lv, "task_handler"): |
| 296 | + lv.task_handler() |
| 297 | +if "board_config" in sys.modules: |
| 298 | + bc = sys.modules["board_config"] |
| 299 | + if hasattr(bc, "display_drv") and hasattr(bc.display_drv, "show"): |
| 300 | + bc.display_drv.show() |
| 301 | +`); |
| 302 | + } catch (e) {} |
| 303 | + |
| 304 | + const elapsed = ((performance.now() - t0) / 1000).toFixed(2); |
| 305 | + logConsole(`--- Completed in ${elapsed}s ---\n`, "dim"); |
| 306 | + setStatus("Ready", "ready"); |
| 307 | + isRunning = false; |
| 308 | + return; |
| 309 | + } |
| 310 | + |
211 | 311 | const pyodide = await getPyodide(); |
212 | 312 |
|
213 | 313 | // Always install pydevices-desktop for all scenarios |
@@ -549,7 +649,59 @@ if "display_driver" in sys.modules: |
549 | 649 | const promptStr = isMoreLines ? "... " : ">>> "; |
550 | 650 | logConsole(`${promptStr}${line}\n`, "prompt"); |
551 | 651 |
|
| 652 | + const runtime = elRuntimeSelect ? elRuntimeSelect.value : "pyodide"; |
| 653 | + |
552 | 654 | try { |
| 655 | + if (runtime === "mpy") { |
| 656 | + const mp = await getMicroPython(); |
| 657 | + |
| 658 | + const pyWrapper = ` |
| 659 | +import sys |
| 660 | +
|
| 661 | +_line_input = ${JSON.stringify(line)} |
| 662 | +
|
| 663 | +try: |
| 664 | + _res = eval(_line_input) |
| 665 | + if _res is not None: |
| 666 | + print(repr(_res)) |
| 667 | +except SyntaxError: |
| 668 | + try: |
| 669 | + _parts = [p.strip() for p in _line_input.split(";") if p.strip()] |
| 670 | + if len(_parts) > 1: |
| 671 | + for _p in _parts[:-1]: |
| 672 | + exec(_p) |
| 673 | + _last = _parts[-1] |
| 674 | + try: |
| 675 | + _res = eval(_last) |
| 676 | + if _res is not None: |
| 677 | + print(repr(_res)) |
| 678 | + except SyntaxError: |
| 679 | + exec(_last) |
| 680 | + else: |
| 681 | + exec(_line_input) |
| 682 | + except Exception as _e: |
| 683 | + import sys |
| 684 | + sys.print_exception(_e) if hasattr(sys, "print_exception") else print(_e) |
| 685 | +except Exception as _e: |
| 686 | + import sys |
| 687 | + sys.print_exception(_e) if hasattr(sys, "print_exception") else print(_e) |
| 688 | +
|
| 689 | +try: |
| 690 | + if "lvgl" in sys.modules: |
| 691 | + _lv = sys.modules["lvgl"] |
| 692 | + if hasattr(_lv, "task_handler"): |
| 693 | + _lv.task_handler() |
| 694 | + if "board_config" in sys.modules: |
| 695 | + _bc = sys.modules["board_config"] |
| 696 | + if hasattr(_bc, "display_drv") and hasattr(_bc.display_drv, "show"): |
| 697 | + _bc.display_drv.show() |
| 698 | +except Exception: |
| 699 | + pass |
| 700 | +`; |
| 701 | + mp.runPython(pyWrapper); |
| 702 | + return; |
| 703 | + } |
| 704 | + |
553 | 705 | const pyodide = await getPyodide(); |
554 | 706 |
|
555 | 707 | const pyWrapper = ` |
@@ -645,6 +797,14 @@ _more |
645 | 797 | elResolutionSelect.addEventListener("change", (e) => handleResolutionChange(e.target.value)); |
646 | 798 | } |
647 | 799 |
|
| 800 | + if (elRuntimeSelect) { |
| 801 | + elRuntimeSelect.addEventListener("change", (e) => { |
| 802 | + const val = e.target.value; |
| 803 | + const name = val === "mpy" ? "MicroPython" : "Pyodide (CPython)"; |
| 804 | + logConsole(`[Runtime] Active execution engine set to ${name}.\n`, "info"); |
| 805 | + }); |
| 806 | + } |
| 807 | + |
648 | 808 | const themeToggle = document.getElementById("theme-toggle"); |
649 | 809 | if (themeToggle) { |
650 | 810 | themeToggle.addEventListener("click", toggleTheme); |
|
0 commit comments