Nymbo/self-hosted-python
1
1<!DOCTYPE html>2<html>3 <head>4 <meta charset="UTF-8" />5 <script src="https://cdn.jsdelivr.net/npm/jquery"></script>6 <script src="https://cdn.jsdelivr.net/npm/jquery.terminal@2.23.0/js/jquery.terminal.min.js"></script>7 <link8 href="https://cdn.jsdelivr.net/npm/jquery.terminal@2.23.0/css/jquery.terminal.min.css"9 rel="stylesheet"10 />11 <script src="./pyodide.js"></script>12 <style>13 .terminal {14 --size: 1.5;15 --color: rgba(255, 255, 255, 0.8);16 }17 </style>18 </head>19 <body>20 <script>21 "use strict";22 function sleep(s) {23 return new Promise((resolve) => setTimeout(resolve, s));24 }25 26 async function main() {27 globalThis.pyodide = await loadPyodide({28 indexURL: "./",29 });30 let namespace = pyodide.globals.get("dict")();31 pyodide.runPython(32 `33 import sys34 from pyodide import to_js35 from pyodide.console import PyodideConsole, repr_shorten, BANNER36 import __main__37 BANNER = "Welcome to the Pyodide terminal emulator ๐\\n" + BANNER38 pyconsole = PyodideConsole(__main__.__dict__)39 import builtins40 async def await_fut(fut):41 res = await fut42 if res is not None:43 builtins._ = res44 return to_js([res], depth=1)45 def clear_console():46 pyconsole.buffer = []47 `,48 namespace49 );50 let repr_shorten = namespace.get("repr_shorten");51 let banner = namespace.get("BANNER");52 let await_fut = namespace.get("await_fut");53 let pyconsole = namespace.get("pyconsole");54 let clear_console = namespace.get("clear_console");55 namespace.destroy();56 57 let ps1 = ">>> ",58 ps2 = "... ";59 60 async function lock() {61 let resolve;62 let ready = term.ready;63 term.ready = new Promise((res) => (resolve = res));64 await ready;65 return resolve;66 }67 68 async function interpreter(command) {69 let unlock = await lock();70 term.pause();71 // multiline should be splitted (useful when pasting)72 for (const c of command.split("\n")) {73 let fut = pyconsole.push(c);74 term.set_prompt(fut.syntax_check === "incomplete" ? ps2 : ps1);75 switch (fut.syntax_check) {76 case "syntax-error":77 term.error(fut.formatted_error.trimEnd());78 continue;79 case "incomplete":80 continue;81 case "complete":82 break;83 default:84 throw new Error(`Unexpected type ${ty}`);85 }86 // In JavaScript, await automatically also awaits any results of87 // awaits, so if an async function returns a future, it will await88 // the inner future too. This is not what we want so we89 // temporarily put it into a list to protect it.90 let wrapped = await_fut(fut);91 // complete case, get result / error and print it.92 try {93 let [value] = await wrapped;94 if (value !== undefined) {95 term.echo(96 repr_shorten.callKwargs(value, {97 separator: "\n[[;orange;]<long output truncated>]\n",98 })99 );100 }101 if (pyodide.isPyProxy(value)) {102 value.destroy();103 }104 } catch (e) {105 if (e.constructor.name === "PythonError") {106 const message = fut.formatted_error || e.message;107 term.error(message.trimEnd());108 } else {109 throw e;110 }111 } finally {112 fut.destroy();113 wrapped.destroy();114 }115 }116 term.resume();117 await sleep(10);118 unlock();119 }120 121 let term = $("body").terminal(interpreter, {122 greetings: banner,123 prompt: ps1,124 completionEscape: false,125 completion: function (command, callback) {126 callback(pyconsole.complete(command).toJs()[0]);127 },128 keymap: {129 "CTRL+C": async function (event, original) {130 clear_console();131 term.echo_command();132 term.echo("KeyboardInterrupt");133 term.set_command("");134 term.set_prompt(ps1);135 },136 TAB: (event, original) => {137 const command = term.before_cursor();138 // Disable completion for whitespaces.139 if (command.trim() === "") {140 term.insert("\t");141 return false;142 }143 return original(event);144 },145 },146 });147 window.term = term;148 pyconsole.stdout_callback = (s) => term.echo(s, { newline: false });149 pyconsole.stderr_callback = (s) => {150 term.error(s.trimEnd());151 };152 term.ready = Promise.resolve();153 pyodide._module.on_fatal = async (e) => {154 term.error(155 "Pyodide has suffered a fatal error. Please report this to the Pyodide maintainers."156 );157 term.error("The cause of the fatal error was:");158 term.error(e);159 term.error("Look in the browser console for more details.");160 await term.ready;161 term.pause();162 await sleep(15);163 term.pause();164 };165 }166 window.console_ready = main();167 </script>168 </body>169</html>170 