* More events added, constants cleaned up * Events now handled using noccylabs/tinyevent * Fixed magenta/cyan mixup in style * Fixed LineRead not resetting history pointer on command
		
			
				
	
	
		
			218 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
 | 
						|
namespace NoccyLabs\Shell;
 | 
						|
 | 
						|
/**
 | 
						|
 * This is a readline-like implementation in pure PHP.
 | 
						|
 *
 | 
						|
 */
 | 
						|
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;
 | 
						|
    
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
}
 |