Repository: pyscript/pyscript
File: pyscript.core/src/plugins/py-terminal.js (JavaScript)

Code before:
            };
        });

        // setup remote thread JS/Python code for whenever the
        // worker is ready to become a terminal
        hooks.worker.onReady.add(workerReady);
    } else {
        // in the main case, just bootstrap XTerm without
        // allowing any input as that's not possible / awkward
        hooks.main.onReady.add(function main({ interpreter, io }) {
            console.warn("py-terminal is read only on main thread");
            hooks.main.onReady.delete(main);
            init({
                disableStdin: true,
                cursorBlink: false,
                cursorStyle: "underline",
            });

            // This part is inevitably duplicated as external scope
            // can't be reached by workers out of the box.
            // The detail is that here we use readline here, not sync.
            const decoder = new TextDecoder();
            let data = "";
            const generic = {
                isatty: true,
                write(buffer) {
                    data = decoder.decode(buffer);
                    readline.write(data);
                    return buffer.length;
                },
            };
            interpreter.setStdout(generic);
            interpreter.setStderr(generic);
            interpreter.setStdin({
                isatty: true,
                stdin: () => readline.read(data),
            });

            io.stderr = (error) => {
                readline.write(`${error.message || error}\n`);
            };
        });
    }
};

const mo = new MutationObserver(pyTerminal);
mo.observe(document, { childList: true, subtree: true });

// try to check the current document ASAP
export default pyTerminal();


Review comment (suggestion):
You still ought to be able to assign to the Python scope directly from JavaScript rather than doing this `globalThis` dance...

Code after:
            xworker.sync.pyterminal_read = readline.read.bind(readline);
            xworker.sync.pyterminal_write = readline.write.bind(readline);
            // allow a worker to drop main thread hooks ASAP
            xworker.sync.pyterminal_drop_hooks = () => {
                hooks.worker.onReady.delete(workerReady);
            };
        });

        // setup remote thread JS/Python code for whenever the
        // worker is ready to become a terminal
        hooks.worker.onReady.add(workerReady);
    } else {
        // in the main case, just bootstrap XTerm without
        // allowing any input as that's not possible / awkward
        hooks.main.onReady.add(function main({ interpreter, io, run }) {
            console.warn("py-terminal is read only on main thread");
            hooks.main.onReady.delete(main);

            // on main, it's easy to trash and clean the current terminal
            globalThis.__py_terminal__ = init({
                disableStdin: true,
                cursorBlink: false,
                cursorStyle: "underline",
            });
            run("from js import __py_terminal__ as __terminal__");
            delete globalThis.__py_terminal__;

            // This part is inevitably duplicated as external scope
            // can't be reached by workers out of the box.
            // The detail is that here we use readline here, not sync.
            const decoder = new TextDecoder();
            let data = "";
            const generic = {
                isatty: true,
                write(buffer) {
                    data = decoder.decode(buffer);
                    readline.write(data);
                    return buffer.length;
                },
            };
            interpreter.setStdout(generic);
            interpreter.setStderr(generic);
            interpreter.setStdin({
                isatty: true,
                stdin: () => readline.read(data),
            });

            io.stderr = (error) => {
                readline.write(`${error.message || error}\n`);
            };
        });