CoolFace
Apppublic

Nymbo/self-hosted-python

sourceHugging Faceupdated 4y agoView on Hugging Face
1likes
console.html170 linesDownload Raw Back to root
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