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> | |