php-shell/lib/LineRead.php

218 lines
6.2 KiB
PHP
Raw Normal View History

2016-04-13 01:19:22 +00:00
<?php
namespace NoccyLabs\Shell;
/**
* This is a readline-like implementation in pure PHP.
*
*/
2016-04-13 01:19:22 +00:00
class LineRead
{
const STATE_IDLE = 0;
const STATE_READ = 1;
protected $state = self::STATE_IDLE;
protected $prompt = "cmd:>";
protected $buffer = null;
protected $history = [];
protected $stashedBuffer = null;
2016-04-13 01:19:22 +00:00
protected $posHistory = 0;
protected $posCursor = 0;
protected $posScroll = 0;
protected $termWidth = 0;
protected $sttyOld;
protected $commandStyle;
protected $promptStyle;
2016-04-13 01:19:22 +00:00
public function __construct()
{
stream_set_blocking(STDIN, false);
2016-04-25 18:47:30 +00:00
$this->sttyOld = trim(exec('stty -g'));
2016-11-01 14:12:11 +00:00
exec('stty raw -echo opost onlret'); // isig');
2016-04-13 01:19:22 +00:00
}
public function __destruct()
{
2016-04-25 18:47:30 +00:00
exec('stty '.$this->sttyOld);
2016-04-13 01:19:22 +00:00
}
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";
2016-04-13 01:19:22 +00:00
}
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";
}
2016-04-13 01:19:22 +00:00
if ($keyBuffer[1] == "[") {
$ctrlChar = substr($keyBuffer, 0,3);
$keyBuffer = substr($keyBuffer, 3);
$this->parseControlCode($ctrlChar);
}
2016-04-25 18:47:30 +00:00
} elseif ($keyBuffer[0]=="\x03") {
$this->posCursor = 0;
$this->buffer = null;
fprintf(STDOUT, "\r\n");
return $keyBuffer[0];
2016-04-13 01:19:22 +00:00
} 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);
2016-04-13 01:19:22 +00:00
$this->buffer = null;
$this->posCursor = 0;
$this->posHistory = 0;
2016-04-25 18:47:30 +00:00
printf("\n\r");
2016-04-13 01:19:22 +00:00
$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;
2016-04-13 01:19:22 +00:00
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();
2016-04-13 01:19:22 +00:00
break;
default:
fprintf(STDERR, "\n%s\n", substr($code,1));
}
}
public function setCommandStyle($style)
{
$this->commandStyle = $style;
}
2016-04-13 01:19:22 +00:00
2016-11-01 14:12:11 +00:00
public function setPromptText($prompt)
2016-04-25 18:47:30 +00:00
{
$this->prompt = $prompt;
2016-11-01 14:12:11 +00:00
}
public function setPromptStyle($style)
{
$this->promptStyle = $style;
2016-04-25 18:47:30 +00:00
}
2016-04-13 01:19:22 +00:00
}