mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-20 02:37:41 -05:00
147 lines
5.0 KiB
JavaScript
147 lines
5.0 KiB
JavaScript
// PyScript py-terminal plugin
|
|
import { TYPES, hooks } from "../core.js";
|
|
import { notify } from "./error.js";
|
|
|
|
const SELECTOR = [...TYPES.keys()]
|
|
.map((type) => `script[type="${type}"][terminal],${type}-script[terminal]`)
|
|
.join(",");
|
|
|
|
// show the error on main and
|
|
// stops the module from keep executing
|
|
const notifyAndThrow = (message) => {
|
|
notify(message);
|
|
throw new Error(message);
|
|
};
|
|
|
|
const pyTerminal = async () => {
|
|
const terminals = document.querySelectorAll(SELECTOR);
|
|
|
|
// no results will look further for runtime nodes
|
|
if (!terminals.length) return;
|
|
|
|
// if we arrived this far, let's drop the MutationObserver
|
|
// as we only support one terminal per page (right now).
|
|
mo.disconnect();
|
|
|
|
// we currently support only one terminal as in "classic"
|
|
if (terminals.length > 1) notifyAndThrow("You can use at most 1 terminal.");
|
|
|
|
const [element] = terminals;
|
|
// hopefully to be removed in the near future!
|
|
if (element.matches('script[type="mpy"],mpy-script'))
|
|
notifyAndThrow("Unsupported terminal.");
|
|
|
|
// import styles lazily
|
|
document.head.append(
|
|
Object.assign(document.createElement("link"), {
|
|
rel: "stylesheet",
|
|
href: new URL("./xterm.css", import.meta.url),
|
|
}),
|
|
);
|
|
|
|
// lazy load these only when a valid terminal is found
|
|
const [{ Terminal }, { Readline }] = await Promise.all([
|
|
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
|
|
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
|
|
]);
|
|
|
|
const readline = new Readline();
|
|
|
|
// common main thread initialization for both worker
|
|
// or main case, bootstrapping the terminal on its target
|
|
const init = (options) => {
|
|
let target = element;
|
|
const selector = element.getAttribute("target");
|
|
if (selector) {
|
|
target =
|
|
document.getElementById(selector) ||
|
|
document.querySelector(selector);
|
|
if (!target) throw new Error(`Unknown target ${selector}`);
|
|
} else {
|
|
target = document.createElement("py-terminal");
|
|
target.style.display = "block";
|
|
element.after(target);
|
|
}
|
|
const terminal = new Terminal({
|
|
theme: {
|
|
background: "#191A19",
|
|
foreground: "#F5F2E7",
|
|
},
|
|
...options,
|
|
});
|
|
terminal.loadAddon(readline);
|
|
terminal.open(target);
|
|
terminal.focus();
|
|
};
|
|
|
|
// branch logic for the worker
|
|
if (element.hasAttribute("worker")) {
|
|
// when the remote thread onReady triggers:
|
|
// setup the interpreter stdout and stderr
|
|
const workerReady = ({ interpreter }, { sync }) => {
|
|
sync.pyterminal_drop_hooks();
|
|
const decoder = new TextDecoder();
|
|
let data = "";
|
|
const generic = {
|
|
isatty: true,
|
|
write(buffer) {
|
|
data = decoder.decode(buffer);
|
|
sync.pyterminal_write(data);
|
|
return buffer.length;
|
|
},
|
|
};
|
|
interpreter.setStdout(generic);
|
|
interpreter.setStderr(generic);
|
|
interpreter.setStdin({
|
|
isatty: true,
|
|
stdin: () => sync.pyterminal_read(data),
|
|
});
|
|
};
|
|
|
|
// add a hook on the main thread to setup all sync helpers
|
|
// also bootstrapping the XTerm target on main
|
|
hooks.main.onWorker.add(function worker(_, xworker) {
|
|
hooks.main.onWorker.delete(worker);
|
|
init({
|
|
disableStdin: false,
|
|
cursorBlink: true,
|
|
cursorStyle: "block",
|
|
});
|
|
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({ io }) {
|
|
console.warn("py-terminal is read only on main thread");
|
|
hooks.main.onReady.delete(main);
|
|
init({
|
|
disableStdin: true,
|
|
cursorBlink: false,
|
|
cursorStyle: "underline",
|
|
});
|
|
io.stdout = (value) => {
|
|
readline.write(`${value}\n`);
|
|
};
|
|
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();
|