mirror of
https://github.com/python/cpython.git
synced 2025-11-05 08:01:58 +00:00
195 lines
4.9 KiB
JavaScript
195 lines
4.9 KiB
JavaScript
|
|
// Much of this is adapted from here:
|
||
|
|
// https://github.com/mame/xterm-pty/blob/main/emscripten-pty.js
|
||
|
|
// Thanks to xterm-pty for making this possible!
|
||
|
|
|
||
|
|
import createEmscriptenModule from "./python.mjs";
|
||
|
|
import { openpty } from "https://unpkg.com/xterm-pty/index.mjs";
|
||
|
|
import "https://unpkg.com/@xterm/xterm/lib/xterm.js";
|
||
|
|
|
||
|
|
var term = new Terminal();
|
||
|
|
term.open(document.getElementById("terminal"));
|
||
|
|
const { master, slave: PTY } = openpty();
|
||
|
|
term.loadAddon(master);
|
||
|
|
globalThis.PTY = PTY;
|
||
|
|
|
||
|
|
async function setupStdlib(Module) {
|
||
|
|
const versionInt = Module.HEAPU32[Module._Py_Version >>> 2];
|
||
|
|
const major = (versionInt >>> 24) & 0xff;
|
||
|
|
const minor = (versionInt >>> 16) & 0xff;
|
||
|
|
// Prevent complaints about not finding exec-prefix by making a lib-dynload directory
|
||
|
|
Module.FS.mkdirTree(`/lib/python${major}.${minor}/lib-dynload/`);
|
||
|
|
const resp = await fetch(`python${major}.${minor}.zip`);
|
||
|
|
const stdlibBuffer = await resp.arrayBuffer();
|
||
|
|
Module.FS.writeFile(
|
||
|
|
`/lib/python${major}${minor}.zip`,
|
||
|
|
new Uint8Array(stdlibBuffer),
|
||
|
|
{ canOwn: true },
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const tty_ops = {
|
||
|
|
ioctl_tcgets: () => {
|
||
|
|
const termios = PTY.ioctl("TCGETS");
|
||
|
|
const data = {
|
||
|
|
c_iflag: termios.iflag,
|
||
|
|
c_oflag: termios.oflag,
|
||
|
|
c_cflag: termios.cflag,
|
||
|
|
c_lflag: termios.lflag,
|
||
|
|
c_cc: termios.cc,
|
||
|
|
};
|
||
|
|
return data;
|
||
|
|
},
|
||
|
|
|
||
|
|
ioctl_tcsets: (_tty, _optional_actions, data) => {
|
||
|
|
PTY.ioctl("TCSETS", {
|
||
|
|
iflag: data.c_iflag,
|
||
|
|
oflag: data.c_oflag,
|
||
|
|
cflag: data.c_cflag,
|
||
|
|
lflag: data.c_lflag,
|
||
|
|
cc: data.c_cc,
|
||
|
|
});
|
||
|
|
return 0;
|
||
|
|
},
|
||
|
|
|
||
|
|
ioctl_tiocgwinsz: () => PTY.ioctl("TIOCGWINSZ").reverse(),
|
||
|
|
|
||
|
|
get_char: () => {
|
||
|
|
throw new Error("Should not happen");
|
||
|
|
},
|
||
|
|
put_char: () => {
|
||
|
|
throw new Error("Should not happen");
|
||
|
|
},
|
||
|
|
|
||
|
|
fsync: () => {},
|
||
|
|
};
|
||
|
|
|
||
|
|
const POLLIN = 1;
|
||
|
|
const POLLOUT = 4;
|
||
|
|
|
||
|
|
const waitResult = {
|
||
|
|
READY: 0,
|
||
|
|
SIGNAL: 1,
|
||
|
|
TIMEOUT: 2,
|
||
|
|
};
|
||
|
|
|
||
|
|
function onReadable() {
|
||
|
|
var handle;
|
||
|
|
var promise = new Promise((resolve) => {
|
||
|
|
handle = PTY.onReadable(() => resolve(waitResult.READY));
|
||
|
|
});
|
||
|
|
return [promise, handle];
|
||
|
|
}
|
||
|
|
|
||
|
|
function onSignal() {
|
||
|
|
// TODO: signal handling
|
||
|
|
var handle = { dispose() {} };
|
||
|
|
var promise = new Promise((resolve) => {});
|
||
|
|
return [promise, handle];
|
||
|
|
}
|
||
|
|
|
||
|
|
function onTimeout(timeout) {
|
||
|
|
var id;
|
||
|
|
var promise = new Promise((resolve) => {
|
||
|
|
if (timeout > 0) {
|
||
|
|
id = setTimeout(resolve, timeout, waitResult.TIMEOUT);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
var handle = {
|
||
|
|
dispose() {
|
||
|
|
if (id) {
|
||
|
|
clearTimeout(id);
|
||
|
|
}
|
||
|
|
},
|
||
|
|
};
|
||
|
|
return [promise, handle];
|
||
|
|
}
|
||
|
|
|
||
|
|
async function waitForReadable(timeout) {
|
||
|
|
let p1, p2, p3;
|
||
|
|
let h1, h2, h3;
|
||
|
|
try {
|
||
|
|
[p1, h1] = onReadable();
|
||
|
|
[p2, h2] = onTimeout(timeout);
|
||
|
|
[p3, h3] = onSignal();
|
||
|
|
return await Promise.race([p1, p2, p3]);
|
||
|
|
} finally {
|
||
|
|
h1.dispose();
|
||
|
|
h2.dispose();
|
||
|
|
h3.dispose();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const FIONREAD = 0x541b;
|
||
|
|
|
||
|
|
const tty_stream_ops = {
|
||
|
|
async readAsync(stream, buffer, offset, length, pos /* ignored */) {
|
||
|
|
let readBytes = PTY.read(length);
|
||
|
|
if (length && !readBytes.length) {
|
||
|
|
const status = await waitForReadable(-1);
|
||
|
|
if (status === waitResult.READY) {
|
||
|
|
readBytes = PTY.read(length);
|
||
|
|
} else {
|
||
|
|
throw new Error("Not implemented");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
buffer.set(readBytes, offset);
|
||
|
|
return readBytes.length;
|
||
|
|
},
|
||
|
|
|
||
|
|
write: (stream, buffer, offset, length) => {
|
||
|
|
// Note: default `buffer` is for some reason `HEAP8` (signed), while we want unsigned `HEAPU8`.
|
||
|
|
buffer = new Uint8Array(
|
||
|
|
buffer.buffer,
|
||
|
|
buffer.byteOffset,
|
||
|
|
buffer.byteLength,
|
||
|
|
);
|
||
|
|
const toWrite = Array.from(buffer.subarray(offset, offset + length));
|
||
|
|
PTY.write(toWrite);
|
||
|
|
return length;
|
||
|
|
},
|
||
|
|
|
||
|
|
async pollAsync(stream, timeout) {
|
||
|
|
if (!PTY.readable && timeout) {
|
||
|
|
await waitForReadable(timeout);
|
||
|
|
}
|
||
|
|
return (PTY.readable ? POLLIN : 0) | (PTY.writable ? POLLOUT : 0);
|
||
|
|
},
|
||
|
|
ioctl(stream, request, varargs) {
|
||
|
|
if (request === FIONREAD) {
|
||
|
|
const res = PTY.fromLdiscToUpperBuffer.length;
|
||
|
|
Module.HEAPU32[varargs / 4] = res;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
throw new Error("Unimplemented ioctl request");
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
async function setupStdio(Module) {
|
||
|
|
Object.assign(Module.TTY.default_tty_ops, tty_ops);
|
||
|
|
Object.assign(Module.TTY.stream_ops, tty_stream_ops);
|
||
|
|
}
|
||
|
|
|
||
|
|
const emscriptenSettings = {
|
||
|
|
async preRun(Module) {
|
||
|
|
Module.addRunDependency("pre-run");
|
||
|
|
Module.ENV.TERM = "xterm-256color";
|
||
|
|
// Uncomment next line to turn on tracing (messages go to browser console).
|
||
|
|
// Module.ENV.PYREPL_TRACE = "1";
|
||
|
|
|
||
|
|
// Leak module so we can try to show traceback if we crash on startup
|
||
|
|
globalThis.Module = Module;
|
||
|
|
await Promise.all([setupStdlib(Module), setupStdio(Module)]);
|
||
|
|
Module.removeRunDependency("pre-run");
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
try {
|
||
|
|
await createEmscriptenModule(emscriptenSettings);
|
||
|
|
} catch (e) {
|
||
|
|
// Show JavaScript exception and traceback
|
||
|
|
console.warn(e);
|
||
|
|
// Show Python exception and traceback
|
||
|
|
Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
|
||
|
|
process.exit(1);
|
||
|
|
}
|