Repository navigation
Add Io\Terminal native terminal primitives - #23941
prateekbhujel wants to merge 52 commits into
Conversation
DanielEScherzer
left a comment
There was a problem hiding this comment.
some initial thoughts
|
FWIW the timespec functions may be useful to extract into another utility file in the future. poll has Also, perhaps it might be useful to split the Unix/Windows backends into their own files, if it gets unwieldy. |
|
Yeah, both make sense. I kept them local for now since the terminal implementation is still fairly self-contained, but if the timespec helpers start being reused elsewhere, pulling them into a shared utility would be cleaner. Same for the platform code, if the Unix/Windows sides keep growing, splitting them out would probably make the implementation easier to maintain. Thanks for pointing it out. |
…m handling - Refactor public API to final Terminal and ModeToken classes under Nicolas's shared lease model - Implement explicit mode restoration via restoreMode(ModeToken $mode): void with transactional rollback - Implement bounded readiness and overall sequence deadlines for ordinary streams on POSIX and Windows - Unify byte-stream readSecret cancellation (Escape, Ctrl+C, Ctrl+D) and SS3/CSI controls across platforms - Harden CRLF lookahead across secret and line reading, preserving data CR before CRLF and at EOF - Isolate test seam hooks to debug/test builds (ZEND_DEBUG || PHP_TERMINAL_TEST_BUILD) - Add regression PHPT test suite covering fork isolation, failure injection, output flags, and streams
Drop the readLine() suppression and the EIO-to-EOF conversion from fe88929; they were broader than the CI failure required and could hide real read errors.
… raw mode ISIG checks
Mark enableRawMode() with NoDiscard so accidental disposal of the lease is visible. Document token destruction, cover the warning and explicit discard, and remove two setup calls that discarded their tokens.
There was a problem hiding this comment.
I would drop the terminal_ from all test file names, that's redundant with the directory name.
There was a problem hiding this comment.
Done, dropped the terminal_ prefix across all test files in the directory.
| --SKIPIF-- | ||
| <?php | ||
| if (!class_exists(\Io\Terminal\Terminal::class)) die('skip Io\Terminal\Terminal not available'); | ||
| if (!class_exists(\Time\Duration::class)) die('skip Time\Duration not available'); |
There was a problem hiding this comment.
Time\Duration is guaranteed to be available. And my understanding is that the terminal class is as well.
There was a problem hiding this comment.
Removed the redundant class checks from the --SKIPIF-- sections.
| #ifdef PHP_WIN32 | ||
| typedef struct php_io_terminal_identity { | ||
| php_io_terminal_native_stream stream; | ||
| bool is_console; | ||
| bool is_non_tty_stream; | ||
| zend_ulong stream_res_id; | ||
| php_stream *php_stream_ptr; | ||
| } php_io_terminal_identity; | ||
| #else | ||
| typedef struct php_io_terminal_identity { | ||
| php_io_terminal_native_stream stream; | ||
| dev_t dev; | ||
| bool has_dev; | ||
| pid_t sid; | ||
| bool has_sid; | ||
| bool is_non_tty_stream; | ||
| zend_ulong stream_res_id; | ||
| php_stream *php_stream_ptr; | ||
| } php_io_terminal_identity; | ||
| #endif |
There was a problem hiding this comment.
For readability I would suggest to only wrap the condition around the Unix/Windows differences and keep the common fields outside of the condition.
There was a problem hiding this comment.
Cleaned up: moved the common fields outside the #ifdef PHP_WIN32 block, and did the same for the other platform conditionals in the file.
| #define PHP_IO_TERMINAL_READ_EOF (-2) | ||
| #define PHP_IO_TERMINAL_READ_ERROR (-1) | ||
| #define PHP_IO_TERMINAL_READ_TIMEOUT 0 | ||
| #define PHP_IO_TERMINAL_READ_SUCCESS 1 | ||
| #define PHP_IO_TERMINAL_READ_RESIZE 2 |
There was a problem hiding this comment.
C doesn't allow switching on function pointers (and SIG_DFL/SIG_IGN expand to pointer casts), but I simplified this to a boolean check (handler != SIG_DFL && handler != SIG_IGN).
| if (handler == NULL) { | ||
| return false; | ||
| } | ||
| if (handler == SIG_DFL) { | ||
| return false; | ||
| } | ||
| if (handler == SIG_IGN) { | ||
| return false; | ||
| } | ||
| return true; |
There was a problem hiding this comment.
switch() should be more readable.
There was a problem hiding this comment.
C doesn't allow switching on function pointers (and SIG_DFL/SIG_IGN expand to pointer casts), but I simplified this to a one-line boolean check (handler != NULL && handler != SIG_DFL && handler != SIG_IGN).
| unsigned char key; | ||
| int read_result = php_io_terminal_read_byte(fd, stream, poll_ctx, &key, timeout, true, | ||
| resize_handler_installed ? &resize_generation : NULL); | ||
| if (read_result == PHP_IO_TERMINAL_READ_RESIZE) { |
There was a problem hiding this comment.
Switched to switch (read_result).
| RETURN_THROWS(); | ||
| } | ||
|
|
||
| zend_update_property_long(php_io_terminal_terminal_size_ce, Z_OBJ_P(ZEND_THIS), "cols", sizeof("cols") - 1, cols); |
There was a problem hiding this comment.
| zend_update_property_long(php_io_terminal_terminal_size_ce, Z_OBJ_P(ZEND_THIS), "cols", sizeof("cols") - 1, cols); | |
| zend_update_property_long(php_io_terminal_terminal_size_ce, Z_OBJ_P(ZEND_THIS), "cols", strlen("cols"), cols); |
In new code prefer strlen().
There was a problem hiding this comment.
Updated to strlen(), including the exception registration in MINIT.
…ensions() Avoid repeating the Terminal namespace in the size value object and use getDimensions() on Terminal. Also cover separate wrappers reading across a split CRLF secret delimiter.
Implements the Io\Terminal RFC (v0.6) for PHP 8.7.
Adds
Io\Terminal\Terminalwith terminal dimensions (getDimensions()returningDimensions), raw-mode leases (enableRawMode()/restoreMode()withModeToken), and key, line, and hidden input. It works on POSIX terminals and Windows consoles, and also accepts ordinary streams so input can be scripted in tests.Raw-mode leases are tracked per logical terminal. They can be released in any order or through another
Terminalwrapping the same terminal, and the last release restores the saved mode.enableRawMode()is#[\NoDiscard]because a discarded token releases its lease immediately.Tests are in
ext/standard/tests/terminal/. They cover lease ownership and restoration (including PTYs and fork), key decoding, timeouts, resize, line and secret input, and handoff between reads on the same instance.The RFC is still under discussion.