187 lines
5.3 KiB
PHP
187 lines
5.3 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace NoccyLabs\TermBuf;
|
||
|
|
||
|
|
||
|
class TerminalBuffer
|
||
|
{
|
||
|
|
||
|
private $buffer = [];
|
||
|
|
||
|
private $cursorLine = 0;
|
||
|
|
||
|
private $cursorColumn = 0;
|
||
|
|
||
|
private $columns = 0;
|
||
|
|
||
|
private $lines = 0;
|
||
|
|
||
|
private $textAttributes = [];
|
||
|
|
||
|
private $wrapAtEnd = true;
|
||
|
|
||
|
private $scrollAtEnd = true;
|
||
|
|
||
|
public function __construct(int $columns, int $lines)
|
||
|
{
|
||
|
$this->columns = $columns;
|
||
|
$this->lines = $lines;
|
||
|
}
|
||
|
|
||
|
public function getLines(): int
|
||
|
{
|
||
|
return $this->lines;
|
||
|
}
|
||
|
|
||
|
public function getColumns(): int
|
||
|
{
|
||
|
return $this->columns;
|
||
|
}
|
||
|
|
||
|
protected function translateBufferOffs(int $line, int $column): int
|
||
|
{
|
||
|
return ($line * $this->columns) + $column;
|
||
|
}
|
||
|
|
||
|
public function bufferGetRaw(int $line, int $column): array
|
||
|
{
|
||
|
$offs = 'c'.$this->translateBufferOffs($line, $column);
|
||
|
if (array_key_exists($offs, $this->buffer)) {
|
||
|
return $this->buffer[$offs];
|
||
|
}
|
||
|
return [ ' ', [ 0 ] ];
|
||
|
}
|
||
|
|
||
|
public function bufferSetRaw(int $line, int $column, array $raw)
|
||
|
{
|
||
|
$offs = 'c'.$this->translateBufferOffs($line, $column);
|
||
|
$this->buffer[$offs] = $raw;
|
||
|
}
|
||
|
|
||
|
public function bufferSetChar(int $line, int $column, string $char, array $attr=null)
|
||
|
{
|
||
|
$this->bufferSetRaw($line, $column, [
|
||
|
$char,
|
||
|
$attr??$this->textAttributes
|
||
|
]);
|
||
|
}
|
||
|
|
||
|
public function setAttributes(array $attr)
|
||
|
{
|
||
|
$this->textAttributes = $attr;
|
||
|
}
|
||
|
|
||
|
public function getAttributes(): array
|
||
|
{
|
||
|
return $this->textAttributes;
|
||
|
}
|
||
|
|
||
|
public function setCursor(int $line, int $column)
|
||
|
{
|
||
|
$this->cursorColumn = $column;
|
||
|
if ($line >= $this->lines) {
|
||
|
$scrollBy = $line - $this->lines + 1;
|
||
|
$this->scrollBuffer(-$scrollBy);
|
||
|
$line = $this->lines - 1;
|
||
|
}
|
||
|
$this->cursorLine = $line;
|
||
|
}
|
||
|
|
||
|
public function getCursorPosition(): array
|
||
|
{
|
||
|
return [ $this->cursorLine, $this->cursorColumn ];
|
||
|
}
|
||
|
|
||
|
public function moveCursorColumns(int $columns)
|
||
|
{
|
||
|
$lines = 0;
|
||
|
while (($this->cursorColumn + $columns) > $this->columns) {
|
||
|
$lines++;
|
||
|
$columns -= $this->columns;
|
||
|
}
|
||
|
// TODO: This will always wrap, make it respect the wrap and scroll attributes
|
||
|
$this->setCursor($this->cursorLine + $lines, $this->cursorColumn + $columns);
|
||
|
}
|
||
|
|
||
|
public function moveCursorRows(int $rows)
|
||
|
{
|
||
|
|
||
|
}
|
||
|
|
||
|
public function write(string $text)
|
||
|
{
|
||
|
//printf("write %d bytes\n", mb_strlen($text));
|
||
|
for ($ch = 0; $ch < mb_strlen($text); $ch++) {
|
||
|
$c = mb_substr($text, $ch, 1);
|
||
|
if (mb_ord($c) >= 32) {
|
||
|
//printf("\r[%s] %d,%d\n", $c, $this->cursorLine, $this->cursorColumn); usleep(250000);
|
||
|
$this->bufferSetChar( $this->cursorLine, $this->cursorColumn, $c);
|
||
|
$this->moveCursorColumns(1);
|
||
|
} elseif ($c == chr(10)) {
|
||
|
//printf("\r[NL] %d,%d\n", $c, $this->cursorLine, $this->cursorColumn); usleep(250000);
|
||
|
$line = $this->cursorLine + 1;
|
||
|
$this->setCursor($line, 0);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function writeAnsi(string $text)
|
||
|
{
|
||
|
for ($ch = 0; $ch < mb_strlen($text); $ch++) {
|
||
|
$c = mb_substr($text, $ch, 1);
|
||
|
$cc = mb_substr($text, $ch, 2);
|
||
|
if ($cc == "\e[") {
|
||
|
$sb = null; $ch += 2;
|
||
|
while ($ch < mb_strlen($text)) {
|
||
|
$cp = mb_substr($text, $ch++, 1);
|
||
|
if (ctype_alpha($cp)) { $ch--; break; }
|
||
|
$sb .= $cp;
|
||
|
}
|
||
|
$attr = explode(";", $sb);
|
||
|
$this->setAttributes($attr);
|
||
|
} else {
|
||
|
$this->write($c);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Scroll lines in the buffer, from $firstLine to $lastLine, moving every line by
|
||
|
* $scroll lines while adding empty lines at top or bottom.
|
||
|
*/
|
||
|
public function scrollLines(int $firstLine, int $lastLine, int $scroll = -1)
|
||
|
{
|
||
|
if ($scroll < 0) {
|
||
|
for ($n = 0; $n < abs($scroll); $n++) {
|
||
|
for ($line = $firstLine; $line < $lastLine; $line++) {
|
||
|
for ($col = 0; $col < $this->columns; $col++) {
|
||
|
$this->bufferSetRaw($line, $col, $this->bufferGetRaw($line + 1, $col));
|
||
|
}
|
||
|
}
|
||
|
for ($col = 0; $col < $this->columns; $col++) {
|
||
|
$this->bufferSetRaw($lastLine, $col, [' ', []]);
|
||
|
}
|
||
|
}
|
||
|
} elseif ($scroll > 0) {
|
||
|
$first = $firstLine + $scroll; // add scroll to top
|
||
|
$last = $lastLine - $scroll; // remove from bottom
|
||
|
for ($line = $last - 1; $line >= $first; $line--) {
|
||
|
for ($col = 0; $col < $this->columns; $col++) {
|
||
|
$this->bufferSetRaw($line, $col, $this->bufferGetRaw($line - 1, $col));
|
||
|
}
|
||
|
}
|
||
|
for ($line = $firstLine; $line <= $firstLine + $scroll; $line++) {
|
||
|
for ($col = 0; $col < $this->columns; $col++) {
|
||
|
$this->bufferSetRaw($line, $col, [' ', []]);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public function scrollBuffer(int $scroll)
|
||
|
{
|
||
|
$this->scrollLines(0, $this->lines - 1, $scroll);
|
||
|
}
|
||
|
|
||
|
}
|