Description
What is the problem?
Running PHP CLI in an interactive mode (-a
) does not support certain keys:
- Alphanumeric symbols all work
- TTY sequences like backspace, ctrl+c, ctrl+z, ctrl+w all work
- XTERM control sequences like
ctrl+a
,ctrl+e
are printed as^A
or^E
- ANSI Escape Codes like the arrow keys are printed as, e.g.,
^[[D
Presumably, it's because PHP.wasm does not have the same kind of access to the input and output streams as a native build does. Notably, PHP.wasm is compiled with libedit
and ncurses
and uses the xterm
TERMINFO database.
Investigation
The PHP runtime is connected to host's stdin through Emscripten's NODERAWFS.
At the same time, the wrapping node.js process seems to buffer the stdin input and handle the key inputs on its own. Nothing gets sent to the PHP process until the enter
key is pressed – I confirmed this by adding console.log()
calls to the NODERAWFS.read
function.
Once the relevant key codes reach PHP, they are somewhat processed. Here I pressed 123456789←←←←ab<ctrl+a>1_
and 12345ab1_
was printed:
php > echo <<<TTY
<<< > 123456789^[[D^[[D^[[D^[[Dab^A1_
<<< > TTY;
12345ab1_
The arrow keys clearly worked, but the ab
overwritten 67
instead of being inserted before. Also, the ctrl+a
key combination did not return to the beginning of the line.
Manually sending raw bytes
I thought maybe something's wrong with reading stdin and provided manually sent raw bytes throught stdin:
const stdIn = new Uint8Array([
// send echo "a":
...new TextEncoder().encode('echo "a";'),
// Press left arrow
27, 91, 68,
// Press "d", "e", "f"
100, 101, 102
]);
let stdInIterator = 0;
// Provide PHP with the raw bytes through the `stdin` callback:
async function main() {
const php = await startPHP(phpLoaderModule, 'NODE', {
stdin: () => {
const ord = stdIn[stdInIterator++];
return ord === undefined ? null : ord;
},
});
It did correctly move the caret to the left and the input buffer seemed to contain echo "a"def;
. Unfortunately, that's not what got rendered in the terminal.
stdin.setRawMode(true)
Node.js can be told to stop buffering the input lines and just pass through any bytes it receives as follows:
process.stdin.setRawMode(true);
process.stdin.resume();
Weirdly, that stops printing the line buffer to stdout:
$fp = fopen('php://stderr', 'w'); fwrite($fp, "STDERR!\n"); fclose($fp);
$fp = fopen('php://stdout', 'w'); fwrite($fp, "STDOUT!\n"); fclose($fp);
I tried manually printing characters to process.stdout
but didn't get anywhere – PHP CLI does a lot of work there to, e.g., replace the current line buffer with the last history entry when the up arrow is pressed.