php-termbuf/src/TerminalBuffer.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);
}
}