|
|
<!DOCTYPE html> |
|
|
<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.32.0/js/jquery.terminal.min.js"></script> |
|
|
<script src="https://cdn.jsdelivr.net/npm/jquery.terminal@2.23.0/js/unix_formatting.min.js"></script> |
|
|
<link |
|
|
href="https://cdn.jsdelivr.net/npm/jquery.terminal@2.32.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> |
|
|
"use strict"; |
|
|
function sleep(s) { |
|
|
return new Promise((resolve) => setTimeout(resolve, s)); |
|
|
} |
|
|
|
|
|
async function main() { |
|
|
globalThis.pyodide = await loadPyodide(); |
|
|
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 = [] |
|
|
`, |
|
|
{ globals: 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(); |
|
|
|
|
|
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}`); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let wrapped = await_fut(fut); |
|
|
|
|
|
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.enter(); |
|
|
term.echo("KeyboardInterrupt"); |
|
|
term.set_command(""); |
|
|
term.set_prompt(ps1); |
|
|
}, |
|
|
TAB: (event, original) => { |
|
|
const command = term.before_cursor(); |
|
|
|
|
|
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._api.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> |
|
|
|