Hi Pratik, hi internals,

I maintain phasync/tui, a terminal UI library for PHP (layouts, widgets, and a 
VT emulator so shells and full-screen programs run in panes). I built 
php-src#23941 and switched tui's terminal handling to Io\Terminal where it 
exists:

https://github.com/phasync/tui/commit/eb7f919

Raw mode is now a ModeToken lease and the size comes from getSize(), instead of 
four stty runs (stty -g, stty raw -echo, stty <saved>, stty size). tui's test 
suite gives the same results on the PR build as on stock PHP, including the 
tests that crash a program in a pty in various ways and check with stty -a that 
the terminal was restored. That part of the RFC works, and it is exactly what 
userland cannot do without spawning stty, using FFI, or Windows-specific 
tricks. Thank you for it.

I would like to suggest that the RFC proceeds with a reduced scope: the 
operations userland now shells out for, and nothing that userland already does 
well.

What userland needs from core today:

  *   Saving the mode, entering raw mode, restoring it: Laravel Prompts runs 
stty -g and stty <mode> (src/Terminal.php), Symfony Console runs stty in 
Terminal and in QuestionHelper for hidden input, tui ran stty -g / raw -echo / 
restore.
  *   The size: Symfony Console uses stty -a and mode CON, tui used stty size.

enableRawMode()/restoreMode(), getSize(), TerminalSize, ModeToken and 
TerminalException cover all of these, on POSIX and Windows.

What userland already does well, so it need not be in core:

  *   Key decoding. php-tui/term has a 600-line EventParser with modifiers, 
mouse and focus events; Laravel Prompts keeps its own key table, modified 
arrows included; tui's InputDecoder reads legacy xterm keys with modifiers, the 
kitty keyboard protocol, modifyOtherKeys, SGR and X10 mouse, bracketed paste 
and focus reports. readKey() returns Key|string|null with 28 keys and no 
modifiers, so none of these libraries could switch to it, and the enum cannot 
gain modifiers later without a new method beside it.
  *   readLine() overlaps with fgets(); readSecret() is policy (Escape, Ctrl+C 
and Ctrl+D cancel, Backspace edits, no masking) that is a few lines in userland 
once raw mode exists, and that prompt libraries will want to decide for 
themselves.

If key input is wanted in core later, I think a separate RFC could do it as a 
pure decoder (bytes in, events out, plus a flush for a lone Escape), with an 
event object that has room for modifiers, mouse and paste. It would serve 
blocking scripts and event loops alike, and avoid decoder state that belongs to 
one Terminal instance.

Smaller points on the reduced scope:

  1.  Factories: fromStreams() is enough. It already covers STDIN/STDOUT, a 
pty, and /dev/tty (CONIN$/CONOUT$) when stdio is redirected, which is common 
for prompts in piped scripts. fromStdio() re-resolves the standard handles on 
each call; is there a use case that fromStreams(STDIN, STDOUT) does not cover? 
If not, one factory is easier to explain.
  2.  Raw mode on Windows: libraries that read and write escape sequences need 
virtual terminal input (ENABLE_VIRTUAL_TERMINAL_INPUT) and output processing. 
The RFC leaves the console flags to the implementation. Could raw mode specify 
them, or take an option for them?
  3.  Ctrl+C: some programs want raw input but Ctrl+C still delivered as a 
signal. An optional argument to enableRawMode() would allow that now or in a 
later version without breaking anything.
  4.  Resize: with readKey() out, Key::Resize goes too. On POSIX a cheap 
getSize() plus SIGWINCH is enough; on Windows the console reports resizes as 
input records, which a later input RFC could cover.

I would support Io\Terminal in that form, and I am happy to test further 
revisions with tui.


Reply via email to