
| Current Path : /var/www/html/strat/vendor/psy/psysh/src/Readline/Interactive/Input/ |
Linux ift1.ift-informatik.de 5.4.0-216-generic #236-Ubuntu SMP Fri Apr 11 19:53:21 UTC 2025 x86_64 |
| Current File : /var/www/html/strat/vendor/psy/psysh/src/Readline/Interactive/Input/StdinReader.php |
<?php
/*
* This file is part of Psy Shell.
*
* (c) 2012-2026 Justin Hileman
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Psy\Readline\Interactive\Input;
/**
* Read characters from stdin.
*/
class StdinReader
{
/** @var resource */
private $stream;
/**
* @param resource $stream
*/
public function __construct($stream = \STDIN)
{
$this->stream = $stream;
}
/**
* Check if there's pending input (useful for paste detection).
*/
public function hasPendingInput(): bool
{
// Memory streams don't support stream_select.
$meta = \stream_get_meta_data($this->stream);
if (isset($meta['stream_type']) && $meta['stream_type'] === 'MEMORY') {
return false;
}
\stream_set_blocking($this->stream, false);
$read = [$this->stream];
$write = null;
$except = null;
$ready = @\stream_select($read, $write, $except, 0, 0);
\stream_set_blocking($this->stream, true);
return $ready > 0;
}
/**
* Read all available input (used for handling pastes).
*
* @return string The pasted content
*/
public function readPastedContent(): string
{
// Memory streams don't support stream_select.
$meta = \stream_get_meta_data($this->stream);
if (isset($meta['stream_type']) && $meta['stream_type'] === 'MEMORY') {
$content = '';
while (($char = \fgetc($this->stream)) !== false) {
$content .= $char;
}
return $content;
}
\stream_set_blocking($this->stream, false);
$content = '';
$timeout = 50000;
$start = \microtime(true);
while ((\microtime(true) - $start) < ($timeout / 1000000)) {
$char = \fgetc($this->stream);
if ($char === false) {
$read = [$this->stream];
$write = null;
$except = null;
if (@\stream_select($read, $write, $except, 0, 1000) > 0) {
continue;
}
break;
}
$content .= $char;
}
\stream_set_blocking($this->stream, true);
return $content;
}
/**
* Read a single key press from the input stream.
*/
public function readKey(): Key
{
$char = \fgetc($this->stream);
if ($char === false) {
return new Key('', Key::TYPE_EOF);
}
// Escape sequences must be handled before paste detection.
if ($char === "\033") {
$escapeKey = $this->readEscapeSequence($char);
if ($escapeKey->getValue() === "\033[200~") {
return $this->readBracketedPaste();
}
return $escapeKey;
}
if ($this->hasPendingInput()) {
$pastedContent = $char.$this->readPastedContent();
// No ungetc support in PHP, so return any buffered content as a paste.
if (\strlen($pastedContent) > 1) {
return new Key($pastedContent, Key::TYPE_PASTE);
}
}
// CR/LF are handled as regular chars, not control characters.
$ord = \ord($char);
if ($ord < 32 && $char !== "\n" && $char !== "\r") {
return new Key($char, Key::TYPE_CONTROL);
}
if ($ord === 127) {
return new Key($char, Key::TYPE_CONTROL);
}
return new Key($char, Key::TYPE_CHAR);
}
/**
* Read an escape sequence from the input stream.
*/
protected function readEscapeSequence(string $prefix): Key
{
\stream_set_blocking($this->stream, false);
$sequence = $prefix;
$timeout = 100000;
$start = \microtime(true);
while ((\microtime(true) - $start) < ($timeout / 1000000)) {
$char = \fgetc($this->stream);
if ($char === false) {
\usleep(1000); // 1ms
continue;
}
$sequence .= $char;
if ($this->isCompleteEscapeSequence($sequence)) {
break;
}
if (\strlen($sequence) > 32) {
break;
}
}
\stream_set_blocking($this->stream, true);
return new Key($sequence, Key::TYPE_ESCAPE);
}
/**
* Check if an escape sequence is complete.
*/
protected function isCompleteEscapeSequence(string $sequence): bool
{
// Esc+Enter remaps (common in terminal keybind customization).
if ($sequence === "\033\r" || $sequence === "\033\n") {
return true;
}
// Arrow keys: \033[A-D, Home/End: \033[H, \033[F
if (\preg_match('/^\033\[[A-DHF]$/', $sequence)) {
return true;
}
// Function keys, Delete, bracketed paste: \033[NNN~ or \033[27;2;13~
if (\preg_match('/^\033\[\d+(?:[;:]\d+)*~$/', $sequence)) {
return true;
}
// Modified keys: \033[1;3C (Alt+Right), \033[1;5D (Ctrl+Left), etc.
if (\preg_match('/^\033\[\d+(?:[;:]\d+)*[A-Za-z]$/', $sequence)) {
return true;
}
// CSI-u protocol: \033[13;2u (Shift+Enter), \033[97;5u (Ctrl+A), etc.
if (\preg_match('/^\033\[\d+(?:[;:]\d+)*u$/', $sequence)) {
return true;
}
// SS3 keys: \033OM (keypad Enter), \033OA, etc.
if (\preg_match('/^\033O[A-Z]$/', $sequence)) {
return true;
}
// Alt+char: \033b, \033f, etc.
if (\preg_match('/^\033[a-z]$/i', $sequence)) {
return true;
}
return false;
}
/**
* Read bracketed paste content.
*
* Called after detecting \033[200~ sequence.
* Reads until \033[201~ is encountered.
*/
protected function readBracketedPaste(): Key
{
$content = '';
$escapeBuffer = '';
while (true) {
$char = \fgetc($this->stream);
if ($char === false) {
break;
}
if ($char === "\033") {
$escapeBuffer = $char;
\stream_set_blocking($this->stream, false);
$timeout = 10000;
$start = \microtime(true);
while ((\microtime(true) - $start) < ($timeout / 1000000)) {
$nextChar = \fgetc($this->stream);
if ($nextChar === false) {
\usleep(500);
continue;
}
$escapeBuffer .= $nextChar;
if ($escapeBuffer === "\033[201~") {
\stream_set_blocking($this->stream, true);
return new Key($content, Key::TYPE_PASTE);
}
if ($this->isCompleteEscapeSequence($escapeBuffer)) {
break;
}
if (\strlen($escapeBuffer) > 32) {
break;
}
}
\stream_set_blocking($this->stream, true);
$content .= $escapeBuffer;
$escapeBuffer = '';
} else {
$content .= $char;
}
}
return new Key($content, Key::TYPE_PASTE);
}
/**
* Get the underlying stream resource.
*
* @return resource
*/
public function getStream()
{
return $this->stream;
}
}