<!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.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>
      "use strict";
      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>