Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
6eddf0e847 | |||
ba27d840ea | |||
17064e7e43 |
@ -10,9 +10,11 @@ $shell = new NoccyLabs\React\Shell\Shell();
|
|||||||
$commands = new CommandHandler();
|
$commands = new CommandHandler();
|
||||||
$commands->add('help', function ($args, $shell) {
|
$commands->add('help', function ($args, $shell) {
|
||||||
$shell->write("This could be usage help :)\n");
|
$shell->write("This could be usage help :)\n");
|
||||||
|
$shell->write("Exit by pressing ^C\n");
|
||||||
});
|
});
|
||||||
$commands->on('notfound', function ($command, $shell) {
|
$commands->on('command', function ($command, $args, $shell) {
|
||||||
$shell->write("Command not found: {$command}. Try help\n");
|
$shell->write("Bad command '{$command}', try help.\n");
|
||||||
|
$shell->write("Arguments passed: ".json_encode($args)."\n");
|
||||||
});
|
});
|
||||||
|
|
||||||
$shell->on('prompt', function ($shell) {
|
$shell->on('prompt', function ($shell) {
|
||||||
|
@ -13,6 +13,8 @@ class CommandHandler implements EventEmitterInterface
|
|||||||
|
|
||||||
private array $commands = [];
|
private array $commands = [];
|
||||||
|
|
||||||
|
private bool $allowAbbreviatedCommands = true;
|
||||||
|
|
||||||
public function add(string $command, callable $handler, array $signature=[]): self
|
public function add(string $command, callable $handler, array $signature=[]): self
|
||||||
{
|
{
|
||||||
$this->commands[$command] = [ 'handler' => $handler, 'signature' => $signature ];
|
$this->commands[$command] = [ 'handler' => $handler, 'signature' => $signature ];
|
||||||
@ -28,8 +30,21 @@ class CommandHandler implements EventEmitterInterface
|
|||||||
{
|
{
|
||||||
$command = array_shift($line);
|
$command = array_shift($line);
|
||||||
|
|
||||||
|
if ($this->allowAbbreviatedCommands) {
|
||||||
|
$candidates = array_filter(
|
||||||
|
array_keys($this->commands),
|
||||||
|
fn($c)=>str_starts_with($c,$command)
|
||||||
|
);
|
||||||
|
if (count($candidates)>1) {
|
||||||
|
$this->emit("candidates", [ $candidates, $shell ]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (count($candidates)==1) {
|
||||||
|
$command = array_shift($candidates);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (!array_key_exists($command, $this->commands)) {
|
if (!array_key_exists($command, $this->commands)) {
|
||||||
$this->emit("notfound", [ $command, $shell ]);
|
$this->emit("command", [ $command, $line, $shell ]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ namespace NoccyLabs\React\Shell;
|
|||||||
|
|
||||||
use Evenement\EventEmitterInterface;
|
use Evenement\EventEmitterInterface;
|
||||||
use Evenement\EventEmitterTrait;
|
use Evenement\EventEmitterTrait;
|
||||||
|
use PgSql\Lob;
|
||||||
use React\EventLoop\Loop;
|
use React\EventLoop\Loop;
|
||||||
use React\Stream\ReadableResourceStream;
|
use React\Stream\ReadableResourceStream;
|
||||||
use React\Stream\ReadableStreamInterface;
|
use React\Stream\ReadableStreamInterface;
|
||||||
@ -34,6 +35,18 @@ class Shell implements WritableStreamInterface, EventEmitterInterface
|
|||||||
|
|
||||||
private int $promptWidth = 0;
|
private int $promptWidth = 0;
|
||||||
|
|
||||||
|
private ?string $promptStyle = "36;1";
|
||||||
|
|
||||||
|
private ?string $inputStyle = "36";
|
||||||
|
|
||||||
|
private array $history = [];
|
||||||
|
|
||||||
|
private ?int $historyIndex = null;
|
||||||
|
|
||||||
|
private ?string $historyCache = null;
|
||||||
|
|
||||||
|
private int $maxHistory = 100;
|
||||||
|
|
||||||
public function __construct(?ReadableStreamInterface $input=null, ?WritableStreamInterface $output=null)
|
public function __construct(?ReadableStreamInterface $input=null, ?WritableStreamInterface $output=null)
|
||||||
{
|
{
|
||||||
$this->istream = $input ?? new ReadableResourceStream(STDIN);
|
$this->istream = $input ?? new ReadableResourceStream(STDIN);
|
||||||
@ -68,6 +81,10 @@ class Shell implements WritableStreamInterface, EventEmitterInterface
|
|||||||
exit(0);
|
exit(0);
|
||||||
|
|
||||||
case "\n":
|
case "\n":
|
||||||
|
// Update the history
|
||||||
|
array_unshift($this->history, $this->buffer);
|
||||||
|
while (count($this->history) > $this->maxHistory) array_pop($this->history);
|
||||||
|
// Parse and empty the buffer
|
||||||
$buffer = str_getcsv(trim($this->buffer), " ");
|
$buffer = str_getcsv(trim($this->buffer), " ");
|
||||||
$this->buffer = '';
|
$this->buffer = '';
|
||||||
$this->cursorPos = 0;
|
$this->cursorPos = 0;
|
||||||
@ -93,11 +110,33 @@ class Shell implements WritableStreamInterface, EventEmitterInterface
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case "\e[A": // up
|
case "\e[A": // up
|
||||||
|
if ($this->historyIndex === null && count($this->history) > 0) {
|
||||||
|
$this->historyCache = $this->buffer;
|
||||||
|
$this->historyIndex = 0;
|
||||||
|
$this->buffer = $this->history[$this->historyIndex];
|
||||||
|
} elseif ($this->historyIndex < count($this->history) - 1) {
|
||||||
|
$this->historyIndex++;
|
||||||
|
$this->buffer = $this->history[$this->historyIndex];
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "\e[B": // down
|
case "\e[B": // down
|
||||||
|
if ($this->historyIndex === null) {
|
||||||
|
break;
|
||||||
|
} elseif ($this->historyIndex === 0) {
|
||||||
|
$this->historyIndex = null;
|
||||||
|
$this->buffer = $this->historyCache;
|
||||||
|
$this->historyCache = null;
|
||||||
|
} else {
|
||||||
|
$this->historyIndex--;
|
||||||
|
$this->buffer = $this->history[$this->historyIndex];
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "\e[H": // home
|
case "\e[H": // home
|
||||||
|
$this->cursorPos = 0;
|
||||||
|
break;
|
||||||
case "\e[F": // end
|
case "\e[F": // end
|
||||||
|
$this->cursorPos = mb_strlen($this->buffer);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "\e[D": // left
|
case "\e[D": // left
|
||||||
@ -121,6 +160,13 @@ class Shell implements WritableStreamInterface, EventEmitterInterface
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($this->cursorPos < $this->scrollOffset) {
|
||||||
|
$this->scrollOffset = $this->cursorPos;
|
||||||
|
}
|
||||||
|
$availWidth = $this->termWidth - $this->promptWidth;
|
||||||
|
if ($this->cursorPos - $this->scrollOffset >= $availWidth) {
|
||||||
|
$this->scrollOffset = abs($availWidth - $this->cursorPos) + 2;
|
||||||
|
}
|
||||||
$this->updatePrompt();
|
$this->updatePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +175,7 @@ class Shell implements WritableStreamInterface, EventEmitterInterface
|
|||||||
{
|
{
|
||||||
$this->hidePrompt();
|
$this->hidePrompt();
|
||||||
$this->ostream->write($data);
|
$this->ostream->write($data);
|
||||||
$this->redrawPrompt();
|
Loop::futureTick($this->redrawPrompt(...));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,10 +221,10 @@ class Shell implements WritableStreamInterface, EventEmitterInterface
|
|||||||
|
|
||||||
$pos = $this->promptWidth + $this->cursorPos - $this->scrollOffset + 1;
|
$pos = $this->promptWidth + $this->cursorPos - $this->scrollOffset + 1;
|
||||||
|
|
||||||
$obuf = "\r" . "\e[1m" . $this->prompt . "\e[22m";
|
$obuf = "\r" . "\e[{$this->promptStyle}m" . $this->prompt . "\e[0m";
|
||||||
$ostr = mb_substr($this->buffer, $this->scrollOffset) . " ";
|
$ostr = mb_substr($this->buffer, $this->scrollOffset) . " ";
|
||||||
$ostr = mb_substr($ostr, 0, $this->termWidth - $this->promptWidth);
|
$ostr = mb_substr($ostr, 0, $this->termWidth - $this->promptWidth);
|
||||||
$obuf .= $ostr . "\e[K\e[" . $pos . "G";
|
$obuf .= "\e[{$this->inputStyle}m" . $ostr . "\e[K\e[0m\e[" . $pos . "G";
|
||||||
|
|
||||||
$this->ostream->write($obuf);
|
$this->ostream->write($obuf);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user