Spaces:
Running
Running
| <html> | |
| <head> | |
| <meta charset="UTF-8" /> | |
| <script src="https://cdn.jsdelivr.net/npm/jquery"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/jquery.terminal@2.23.0/js/jquery.terminal.min.js"></script> | |
| <link | |
| href="https://cdn.jsdelivr.net/npm/jquery.terminal@2.23.0/css/jquery.terminal.min.css" | |
| rel="stylesheet" | |
| /> | |
| <script src="./pyodide.js"></script> | |
| <style> | |
| .terminal { | |
| --size: 1.5; | |
| --color: rgba(255, 255, 255, 0.8); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script> | |
| ; | |
| function sleep(s) { | |
| return new Promise((resolve) => setTimeout(resolve, s)); | |
| } | |
| async function main() { | |
| globalThis.pyodide = await loadPyodide({ | |
| indexURL: "./", | |
| }); | |
| let namespace = pyodide.globals.get("dict")(); | |
| pyodide.runPython( | |
| ` | |
| import sys | |
| from pyodide import to_js | |
| from pyodide.console import PyodideConsole, repr_shorten, BANNER | |
| import __main__ | |
| BANNER = "Welcome to the Pyodide terminal emulator π\\n" + BANNER | |
| pyconsole = PyodideConsole(__main__.__dict__) | |
| import builtins | |
| async def await_fut(fut): | |
| res = await fut | |
| if res is not None: | |
| builtins._ = res | |
| return to_js([res], depth=1) | |
| def clear_console(): | |
| pyconsole.buffer = [] | |
| `, | |
| namespace | |
| ); | |
| let repr_shorten = namespace.get("repr_shorten"); | |
| let banner = namespace.get("BANNER"); | |
| let await_fut = namespace.get("await_fut"); | |
| let pyconsole = namespace.get("pyconsole"); | |
| let clear_console = namespace.get("clear_console"); | |
| namespace.destroy(); | |
| let ps1 = ">>> ", | |
| ps2 = "... "; | |
| async function lock() { | |
| let resolve; | |
| let ready = term.ready; | |
| term.ready = new Promise((res) => (resolve = res)); | |
| await ready; | |
| return resolve; | |
| } | |
| async function interpreter(command) { | |
| let unlock = await lock(); | |
| term.pause(); | |
| // multiline should be splitted (useful when pasting) | |
| for (const c of command.split("\n")) { | |
| let fut = pyconsole.push(c); | |
| term.set_prompt(fut.syntax_check === "incomplete" ? ps2 : ps1); | |
| switch (fut.syntax_check) { | |
| case "syntax-error": | |
| term.error(fut.formatted_error.trimEnd()); | |
| continue; | |
| case "incomplete": | |
| continue; | |
| case "complete": | |
| break; | |
| default: | |
| throw new Error(`Unexpected type ${ty}`); | |
| } | |
| // In JavaScript, await automatically also awaits any results of | |
| // awaits, so if an async function returns a future, it will await | |
| // the inner future too. This is not what we want so we | |
| // temporarily put it into a list to protect it. | |
| let wrapped = await_fut(fut); | |
| // complete case, get result / error and print it. | |
| try { | |
| let [value] = await wrapped; | |
| if (value !== undefined) { | |
| term.echo( | |
| repr_shorten.callKwargs(value, { | |
| separator: "\n[[;orange;]<long output truncated>]\n", | |
| }) | |
| ); | |
| } | |
| if (pyodide.isPyProxy(value)) { | |
| value.destroy(); | |
| } | |
| } catch (e) { | |
| if (e.constructor.name === "PythonError") { | |
| const message = fut.formatted_error || e.message; | |
| term.error(message.trimEnd()); | |
| } else { | |
| throw e; | |
| } | |
| } finally { | |
| fut.destroy(); | |
| wrapped.destroy(); | |
| } | |
| } | |
| term.resume(); | |
| await sleep(10); | |
| unlock(); | |
| } | |
| let term = $("body").terminal(interpreter, { | |
| greetings: banner, | |
| prompt: ps1, | |
| completionEscape: false, | |
| completion: function (command, callback) { | |
| callback(pyconsole.complete(command).toJs()[0]); | |
| }, | |
| keymap: { | |
| "CTRL+C": async function (event, original) { | |
| clear_console(); | |
| term.echo_command(); | |
| term.echo("KeyboardInterrupt"); | |
| term.set_command(""); | |
| term.set_prompt(ps1); | |
| }, | |
| TAB: (event, original) => { | |
| const command = term.before_cursor(); | |
| // Disable completion for whitespaces. | |
| if (command.trim() === "") { | |
| term.insert("\t"); | |
| return false; | |
| } | |
| return original(event); | |
| }, | |
| }, | |
| }); | |
| window.term = term; | |
| pyconsole.stdout_callback = (s) => term.echo(s, { newline: false }); | |
| pyconsole.stderr_callback = (s) => { | |
| term.error(s.trimEnd()); | |
| }; | |
| term.ready = Promise.resolve(); | |
| pyodide._module.on_fatal = async (e) => { | |
| term.error( | |
| "Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers." | |
| ); | |
| term.error("The cause of the fatal error was:"); | |
| term.error(e); | |
| term.error("Look in the browser console for more details."); | |
| await term.ready; | |
| term.pause(); | |
| await sleep(15); | |
| term.pause(); | |
| }; | |
| } | |
| window.console_ready = main(); | |
| </script> | |
| </body> | |
| </html> | |