"; protected $buffer = null; protected $history = []; protected $stashedBuffer = null; protected $posHistory = 0; protected $posCursor = 0; protected $posScroll = 0; protected $termWidth = 0; protected $sttyOld; protected $commandStyle; protected $promptStyle; public function __construct() { stream_set_blocking(STDIN, false); $this->sttyOld = trim(exec('stty -g')); exec('stty raw -echo opost onlret'); // isig'); } public function __destruct() { exec('stty '.$this->sttyOld); } public function update() { if ($this->state == self::STATE_IDLE) { $this->state = self::STATE_READ; $this->redraw(); } $buffer = $this->handleInput(); return $buffer; } public function erase() { fprintf(STDOUT, "\r\e[0m\e[2K"); } public function redraw() { $prompt = $this->prompt; $buffer = $this->buffer; if ($this->posCursor > strlen($this->buffer)) { $this->posCursor = strlen($this->buffer); } $cursor = strlen($this->prompt) + 2 + $this->posCursor - $this->posScroll; $endStyle = "\e[0m"; fprintf(STDOUT, "\r\e[2K%s %s\e[%dG{$endStyle}", ($this->promptStyle)($prompt), ($this->commandStyle)($buffer), $cursor); } protected function styleToAnsi($style) { if (!$style) { return; } return "\e[0;".$style."m"; } protected function handleInput() { static $keyBuffer; $readBuf = fread(STDIN, 32); $keyBuffer .= $readBuf; $update = false; $returnBuffer = null; while (strlen($keyBuffer)>0) { if ($keyBuffer[0] == "\e") { if (strlen($keyBuffer)==1) { $keyBuffer = ""; return "\e"; } if ($keyBuffer[1] == "[") { $ctrlChar = substr($keyBuffer, 0,3); $keyBuffer = substr($keyBuffer, 3); $this->parseControlCode($ctrlChar); } } elseif ($keyBuffer[0]=="\x03") { $this->posCursor = 0; $this->buffer = null; fprintf(STDOUT, "\r\n"); return $keyBuffer[0]; } else { $keyChar = $keyBuffer[0]; $keyCode = ord($keyChar); $keyBuffer = substr($keyBuffer, 1); if (($keyCode >= 32) && ($keyCode < 127)) { $edBuffer = substr($this->buffer, 0, $this->posCursor) . $keyChar . substr($this->buffer, $this->posCursor); $this->posCursor++; $this->buffer = $edBuffer; $update = true; } elseif ($keyCode == 127) { if ($this->posCursor > 0) { $this->posCursor--; $edBuffer = substr($this->buffer, 0, $this->posCursor) . substr($this->buffer, $this->posCursor+1); $this->buffer = $edBuffer; $update = true; } } elseif ($keyCode == 13) { $returnBuffer = $this->buffer; array_unshift($this->history, $this->buffer); $this->buffer = null; $this->posCursor = 0; $this->posHistory = 0; printf("\n\r"); $this->state = self::STATE_IDLE; } } } if ($update) { $this->redraw(); } return $returnBuffer; } protected function parseControlCode($code) { switch ($code) { case "\e[D": $this->posCursor = max($this->posCursor-1,0); $this->redraw(); break; case "\e[C": $this->posCursor = min($this->posCursor+1,strlen($this->buffer)); $this->redraw(); break; case "\e[H": $this->posCursor = 0; $this->redraw(); break; case "\e[F": $this->posCursor = strlen($this->buffer); $this->redraw(); break; case "\e[A": // up if ($this->posHistory == 0) { $this->stashedBuffer = $this->buffer; } if ($this->posCursor == strlen($this->buffer)) { $this->posCursor = -1; } if ($this->posHistory < count($this->history)) { $this->posHistory++; $this->buffer = $this->history[$this->posHistory-1]; } if ($this->posCursor == -1) { $this->posCursor = strlen($this->buffer); } $this->redraw(); break; case "\e[B": // down if ($this->posCursor == strlen($this->buffer)) { $this->posCursor = -1; } if ($this->posHistory > 1) { $this->posHistory--; $this->buffer = $this->history[$this->posHistory-1]; } elseif ($this->posHistory > 0) { $this->posHistory--; $this->buffer = $this->stashedBuffer; } if ($this->posCursor == -1) { $this->posCursor = strlen($this->buffer); } $this->redraw(); break; default: fprintf(STDERR, "\n%s\n", substr($code,1)); } } public function setCommandStyle($style) { $this->commandStyle = $style; } public function setPromptText($prompt) { $this->prompt = $prompt; } public function setPromptStyle($style) { $this->promptStyle = $style; } }