2019-10-14 16:21:41 -04:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace Kilo;
|
|
|
|
|
|
|
|
use FFI;
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
trait MagicProperties {
|
|
|
|
abstract public function __get(string $name);
|
|
|
|
|
|
|
|
public function __set(string $name, $value)
|
|
|
|
{
|
|
|
|
if (property_exists($this, $name))
|
|
|
|
{
|
|
|
|
$this->$name = $value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __isset(string $name): bool
|
|
|
|
{
|
|
|
|
return isset($this->$name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
class Key {
|
2019-10-16 16:43:15 -04:00
|
|
|
public const ARROW_LEFT = 'ARROW_LEFT';
|
|
|
|
public const ARROW_RIGHT = 'ARROW_RIGHT';
|
|
|
|
public const ARROW_UP = 'ARROW_UP';
|
|
|
|
public const ARROW_DOWN = 'ARROW_DOWN';
|
2019-10-14 16:21:41 -04:00
|
|
|
public const DEL_KEY = 'DEL';
|
|
|
|
public const HOME_KEY = 'HOME';
|
|
|
|
public const END_KEY = 'END';
|
|
|
|
public const PAGE_UP = 'PAGE_UP';
|
|
|
|
public const PAGE_DOWN = 'PAGE_DOWN';
|
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
/**
|
|
|
|
* @property-read int size
|
|
|
|
* @property-read int rsize
|
|
|
|
*/
|
2019-10-15 13:23:25 -04:00
|
|
|
class Row {
|
2019-10-16 16:43:15 -04:00
|
|
|
use MagicProperties;
|
|
|
|
|
|
|
|
public string $chars = '';
|
|
|
|
public string $render = '';
|
2019-10-15 13:23:25 -04:00
|
|
|
|
|
|
|
public static function new(string $chars): self
|
|
|
|
{
|
|
|
|
return new self($chars);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function __construct($chars)
|
|
|
|
{
|
|
|
|
$this->chars = $chars;
|
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
|
|
|
public function __get(string $name)
|
|
|
|
{
|
|
|
|
switch ($name)
|
|
|
|
{
|
|
|
|
case 'size':
|
|
|
|
return strlen($this->chars);
|
|
|
|
|
|
|
|
case 'rsize':
|
|
|
|
return strlen($this->render);
|
|
|
|
|
|
|
|
default:
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
}
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
/**
|
|
|
|
* @property-read int numRows
|
|
|
|
*/
|
2019-10-14 16:21:41 -04:00
|
|
|
class Editor {
|
2019-10-16 16:43:15 -04:00
|
|
|
use MagicProperties;
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
private FFI $ffi;
|
2019-10-16 16:43:15 -04:00
|
|
|
private string $ab = '';
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
protected int $cursorX = 0;
|
|
|
|
protected int $cursorY = 0;
|
|
|
|
protected int $renderX = 0;
|
|
|
|
protected int $rowOffset = 0;
|
|
|
|
protected int $colOffset = 0;
|
2019-10-14 16:21:41 -04:00
|
|
|
protected int $screenRows = 0;
|
|
|
|
protected int $screenCols = 0;
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
/**
|
|
|
|
* Array of Row objects
|
|
|
|
*/
|
|
|
|
protected array $rows = [];
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
protected string $filename = '';
|
|
|
|
protected string $statusMsg = '';
|
|
|
|
protected int $statusMsgTime;
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
public static function new(FFI $ffi): Editor
|
|
|
|
{
|
|
|
|
return new self($ffi);
|
|
|
|
}
|
|
|
|
|
|
|
|
private function __construct($ffi)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
|
|
|
$this->ffi = $ffi;
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->statusMsgTime = time();
|
2019-10-14 16:21:41 -04:00
|
|
|
|
|
|
|
if ( ! $this->getWindowSize())
|
|
|
|
{
|
|
|
|
die('Failed to get screen size');
|
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
|
|
|
$this->screenRows -= 2;
|
|
|
|
|
|
|
|
// print_r($this); die();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __get(string $name)
|
|
|
|
{
|
|
|
|
if ($name === 'numRows')
|
|
|
|
{
|
|
|
|
return count($this->rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Terminal
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
protected function readKey(): string
|
|
|
|
{
|
|
|
|
$c = read_stdin(1);
|
|
|
|
|
|
|
|
if ($c === '\x1b')
|
|
|
|
{
|
|
|
|
$seq = read_stdin();
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
if (strlen($seq) < 3)
|
|
|
|
{
|
|
|
|
return '\x1b';
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
if (strpos($seq, '[') === 0)
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$seq1 = (int)$seq[1];
|
|
|
|
|
|
|
|
if ($seq1 >= 0 && $seq1 <= 9)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
|
|
|
if (strpos($seq, '~') === 2)
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
switch ($seq[1])
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
case '1':
|
|
|
|
case '7':
|
|
|
|
return Key::HOME_KEY;
|
|
|
|
|
|
|
|
case '4':
|
|
|
|
case '8':
|
|
|
|
return Key::END_KEY;
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
case '3': return Key::DEL_KEY;
|
|
|
|
case '5': return Key::PAGE_UP;
|
|
|
|
case '6': return Key::PAGE_DOWN;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
switch ($seq[1])
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
|
|
|
case 'A': return Key::ARROW_UP;
|
|
|
|
case 'B': return Key::ARROW_DOWN;
|
|
|
|
case 'C': return Key::ARROW_RIGHT;
|
|
|
|
case 'D': return Key::ARROW_LEFT;
|
|
|
|
case 'H': return Key::HOME_KEY;
|
|
|
|
case 'F': return Key::END_KEY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (strpos($seq, 'O') === 0)
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
switch ($seq[1])
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
|
|
|
case 'H': return Key::HOME_KEY;
|
|
|
|
case 'F': return Key::END_KEY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return '\x1b';
|
|
|
|
}
|
|
|
|
|
|
|
|
return $c;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @TODO fix
|
|
|
|
*/
|
|
|
|
private function getCursorPosition(): bool
|
|
|
|
{
|
|
|
|
write_stdout("\x1b[999C\x1b[999B");
|
|
|
|
write_stdout("\x1b[6n");
|
|
|
|
|
|
|
|
$rows = 0;
|
|
|
|
$cols = 0;
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
$buffer = read_stdout();
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
$res = sscanf($buffer, '\x1b[%d;%dR', $rows, $cols);
|
|
|
|
|
|
|
|
if ($res === -1 || $buffer[0] !== '\x1b' || $buffer[1] !== '[')
|
|
|
|
{
|
|
|
|
die('Failed to get screen size');
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->screenRows = $rows;
|
|
|
|
$this->screenCols = $cols;
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
return TRUE;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private function getWindowSize(): bool
|
|
|
|
{
|
|
|
|
$ws = $this->ffi->new('struct winsize');
|
|
|
|
$res = $this->ffi->ioctl(STDOUT_FILENO, TIOCGWINSZ, FFI::addr($ws));
|
|
|
|
|
|
|
|
if ($res === -1 || $ws->ws_col === 0)
|
|
|
|
{
|
|
|
|
return $this->getCursorPosition();
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->screenCols = $ws->ws_col;
|
|
|
|
$this->screenRows = $ws->ws_row;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Row Operations
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
protected function rowCxToRx(Row $row, int $cx): int
|
|
|
|
{
|
|
|
|
$rx = 0;
|
|
|
|
for ($i = 0; $i < $cx; $i++)
|
|
|
|
{
|
|
|
|
if ($row->chars[$i] === "\t")
|
|
|
|
{
|
|
|
|
$rx += (KILO_TAB_STOP - 1) - ($rx % KILO_TAB_STOP);
|
|
|
|
}
|
|
|
|
$rx++;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rx;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function updateRow(Row $row): void
|
|
|
|
{
|
|
|
|
$idx = 0;
|
|
|
|
|
|
|
|
for ($i = 0; $i < $row->size; $i++)
|
|
|
|
{
|
|
|
|
if ($row->chars[$i] === "\t")
|
|
|
|
{
|
|
|
|
$row->render[$idx++] = ' ';
|
|
|
|
while ($idx % KILO_TAB_STOP !== 0)
|
|
|
|
{
|
|
|
|
$row->render[$idx++] = ' ';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$row->render[$idx++] = $row->chars[$i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
protected function appendRow(string $s): void
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$at = $this->numRows;
|
|
|
|
$this->rows[$at] = Row::new($s);
|
|
|
|
$this->updateRow($this->rows[$at]);
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! File I/O
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
public function open(string $filename): void
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
// Copy filename for display
|
|
|
|
$this->filename = $filename;
|
|
|
|
|
|
|
|
// Determine the full path to the file
|
2019-10-15 13:23:25 -04:00
|
|
|
$baseFile = basename($filename);
|
|
|
|
$basePath = str_replace($baseFile, '', $filename);
|
|
|
|
$path = (is_dir($basePath)) ? $basePath : getcwd();
|
|
|
|
|
|
|
|
$fullname = $path . '/' . $baseFile;
|
|
|
|
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
// #TODO gracefully handle issues with loading a file
|
2019-10-15 13:23:25 -04:00
|
|
|
$handle = fopen($fullname, 'rb');
|
|
|
|
|
|
|
|
while (($line = fgets($handle)) !== FALSE)
|
|
|
|
{
|
|
|
|
$this->appendRow($line);
|
|
|
|
}
|
|
|
|
|
|
|
|
fclose($handle);
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Output
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
protected function scroll(): void
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->renderX = 0;
|
|
|
|
if ($this->cursorY < $this->numRows)
|
|
|
|
{
|
|
|
|
$this->renderX = $this->rowCxToRx($this->rows[$this->cursorY], $this->cursorX);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Vertical Scrolling
|
|
|
|
if ($this->cursorY < $this->rowOffset)
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->rowOffset = $this->cursorY;
|
|
|
|
}
|
|
|
|
if ($this->cursorY >= $this->rowOffset + $this->screenRows)
|
|
|
|
{
|
|
|
|
$this->rowOffset = $this->cursorY - $this->screenRows + 1;
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
// Horizontal Scrolling
|
|
|
|
if ($this->renderX < $this->colOffset)
|
|
|
|
{
|
|
|
|
$this->colOffset = $this->renderX;
|
|
|
|
}
|
|
|
|
if ($this->renderX >= $this->colOffset + $this->screenCols)
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->colOffset = $this->renderX - $this->screenCols + 1;
|
2019-10-15 13:23:25 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
protected function drawRows(): void
|
|
|
|
{
|
|
|
|
for ($y = 0; $y < $this->screenRows; $y++)
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$filerow = $y + $this->rowOffset;
|
2019-10-15 13:23:25 -04:00
|
|
|
if ($filerow >= count($this->rows))
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($this->numRows === 0 && $y === $this->screenRows / 3)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-15 13:23:25 -04:00
|
|
|
$welcome = sprintf('PHP Kilo editor -- version %s', KILO_VERSION);
|
|
|
|
$welcomelen = strlen($welcome);
|
|
|
|
if ($welcomelen > $this->screenCols)
|
|
|
|
{
|
|
|
|
$welcomelen = $this->screenCols;
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
$padding = ($this->screenCols - $welcomelen) / 2;
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($padding > 0)
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
|
|
|
$this->ab .= '~';
|
|
|
|
$padding--;
|
|
|
|
}
|
|
|
|
while ($padding--)
|
|
|
|
{
|
|
|
|
$this->ab .= ' ';
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->ab .= substr($welcome, 0, $welcomelen);
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2019-10-15 13:23:25 -04:00
|
|
|
else
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-15 13:23:25 -04:00
|
|
|
$this->ab .= '~';
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$len = $this->rows[$filerow]->rsize - $this->colOffset;
|
2019-10-15 13:23:25 -04:00
|
|
|
if ($len < 0)
|
|
|
|
{
|
|
|
|
$len = 0;
|
|
|
|
}
|
|
|
|
if ($len > $this->screenCols)
|
|
|
|
{
|
|
|
|
$len = $this->screenCols;
|
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->ab .= substr($this->rows[$filerow]->render, $this->colOffset, $len);
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
$this->ab .= "\x1b[K"; // Clear the current line
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->ab .= "\r\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function drawStatusBar(): void
|
|
|
|
{
|
|
|
|
$this->ab .= "\x1b[7m";
|
|
|
|
|
|
|
|
$statusFilename = $this->filename !== '' ? $this->filename : '[No Name]';
|
|
|
|
$status = sprintf("%.20s - %d lines", $statusFilename, count($this->rows));
|
|
|
|
$rstatus = sprintf("%d/%d", $this->cursorY + 1, count($this->rows));
|
|
|
|
$len = strlen($status);
|
|
|
|
$rlen = strlen($rstatus);
|
|
|
|
if ($len > $this->screenCols)
|
|
|
|
{
|
|
|
|
$len = $this->screenCols;
|
|
|
|
}
|
|
|
|
$this->ab .= substr($status, 0, $len);
|
|
|
|
while ($len < $this->screenCols)
|
|
|
|
{
|
|
|
|
if ($this->screenCols - $len === $rlen)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->ab .= substr($rstatus, 0, $rlen);
|
|
|
|
break;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
|
|
|
$this->ab .= ' ';
|
|
|
|
$len++;
|
|
|
|
}
|
|
|
|
$this->ab .= "\x1b[m";
|
|
|
|
$this->ab .= "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
protected function drawMessageBar(): void
|
|
|
|
{
|
|
|
|
$this->ab .= "\x1b[K";
|
|
|
|
$len = strlen($this->statusMsg);
|
|
|
|
if ($len > $this->screenCols)
|
|
|
|
{
|
|
|
|
$len = $this->screenCols;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($len > 0 && (time() - $this->statusMsgTime) < 5)
|
|
|
|
{
|
|
|
|
$this->ab .= substr($this->statusMsg, 0, $len);
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public function refreshScreen(): void
|
|
|
|
{
|
2019-10-15 13:23:25 -04:00
|
|
|
$this->scroll();
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
$this->ab = '';
|
|
|
|
|
|
|
|
$this->ab .= "\x1b[?25l"; // Hide the cursor
|
|
|
|
$this->ab .= "\x1b[H"; // Reposition cursor to top-left
|
|
|
|
|
|
|
|
$this->drawRows();
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->drawStatusBar();
|
|
|
|
$this->drawMessageBar();
|
2019-10-14 16:21:41 -04:00
|
|
|
|
|
|
|
// Specify the current cursor position
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->ab .= sprintf("\x1b[%d;%dH",
|
|
|
|
($this->cursorY - $this->rowOffset) + 1,
|
|
|
|
($this->renderX - $this->colOffset) + 1
|
|
|
|
);
|
2019-10-14 16:21:41 -04:00
|
|
|
|
|
|
|
$this->ab .= "\x1b[?25h"; // Show the cursor
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
echo $this->ab;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setStatusMessage(string $fmt, ...$args): void
|
|
|
|
{
|
|
|
|
$this->statusMsg = (count($args) > 0)
|
|
|
|
? sprintf($fmt, ...$args)
|
|
|
|
: $fmt;
|
|
|
|
$this->statusMsgTime = time();
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
// ! Input
|
|
|
|
// ------------------------------------------------------------------------
|
|
|
|
|
|
|
|
protected function moveCursor(string $key): void
|
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$row = ($this->cursorY >= $this->numRows)
|
|
|
|
? NULL
|
|
|
|
: $this->rows[$this->cursorY];
|
|
|
|
|
2019-10-14 16:21:41 -04:00
|
|
|
switch ($key)
|
|
|
|
{
|
|
|
|
case Key::ARROW_LEFT:
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($this->cursorX !== 0)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->cursorX--;
|
|
|
|
}
|
|
|
|
else if ($this->cursorX > 0)
|
|
|
|
{
|
|
|
|
$this->cursorY--;
|
|
|
|
$this->cursorX = $this->rows[$this->cursorY]->size;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Key::ARROW_RIGHT:
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($row && $this->cursorX < $row->size)
|
|
|
|
{
|
|
|
|
$this->cursorX++;
|
|
|
|
}
|
|
|
|
else if ($row && $this->cursorX === $row->size)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->cursorY++;
|
|
|
|
$this->cursorX = 0;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Key::ARROW_UP:
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($this->cursorY !== 0)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->cursorY--;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case Key::ARROW_DOWN:
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($this->cursorY < $this->numRows)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->cursorY++;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-10-16 16:43:15 -04:00
|
|
|
|
|
|
|
$row = ($this->cursorY >= $this->numRows)
|
|
|
|
? NULL
|
|
|
|
: $this->rows[$this->cursorY];
|
|
|
|
$rowlen = $row ? $row->size : 0;
|
|
|
|
if ($this->cursorX > $rowlen)
|
|
|
|
{
|
|
|
|
$this->cursorX = $rowlen;
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
2019-10-16 22:14:30 -04:00
|
|
|
public function processKeypress(): ?string
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
|
|
|
$c = $this->readKey();
|
2019-10-16 22:14:30 -04:00
|
|
|
|
|
|
|
if ($c === "\0")
|
|
|
|
{
|
|
|
|
return '';
|
|
|
|
}
|
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
switch ($c)
|
2019-10-14 16:21:41 -04:00
|
|
|
{
|
|
|
|
case Key::HOME_KEY:
|
2019-10-16 16:43:15 -04:00
|
|
|
$this->cursorX = 0;
|
2019-10-14 16:21:41 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Key::END_KEY:
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($this->cursorY < $this->numRows)
|
|
|
|
{
|
|
|
|
$this->cursorX = $this->rows[$this->cursorY]->size;
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Key::PAGE_UP:
|
|
|
|
case Key::PAGE_DOWN:
|
2019-10-15 13:23:25 -04:00
|
|
|
$this->pageUpOrDown($c);
|
2019-10-14 16:21:41 -04:00
|
|
|
break;
|
|
|
|
|
|
|
|
case Key::ARROW_UP:
|
|
|
|
case Key::ARROW_DOWN:
|
|
|
|
case Key::ARROW_LEFT:
|
|
|
|
case Key::ARROW_RIGHT:
|
|
|
|
$this->moveCursor($c);
|
|
|
|
break;
|
2019-10-16 16:43:15 -04:00
|
|
|
|
|
|
|
case chr(ctrl_key('q')):
|
|
|
|
write_stdout("\x1b[2J"); // Clear the screen
|
|
|
|
write_stdout("\x1b[H"); // Reposition cursor to top-left
|
2019-10-16 22:14:30 -04:00
|
|
|
return NULL;
|
2019-10-16 16:43:15 -04:00
|
|
|
break;
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
return $c;
|
|
|
|
}
|
2019-10-15 13:23:25 -04:00
|
|
|
|
2019-10-16 16:43:15 -04:00
|
|
|
private function pageUpOrDown(string $c): void
|
2019-10-15 13:23:25 -04:00
|
|
|
{
|
2019-10-16 16:43:15 -04:00
|
|
|
if ($c === Key::PAGE_UP)
|
|
|
|
{
|
|
|
|
$this->cursorY = $this->rowOffset;
|
|
|
|
}
|
|
|
|
else if ($c === Key::PAGE_DOWN)
|
|
|
|
{
|
|
|
|
$this->cursorY = $this->rowOffset + $this->screenRows - 1;
|
|
|
|
if ($this->cursorY > $this->numRows)
|
|
|
|
{
|
|
|
|
$this->cursorY = $this->numRows;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-15 13:23:25 -04:00
|
|
|
$times = $this->screenRows;
|
|
|
|
while ($times--)
|
|
|
|
{
|
|
|
|
$this->moveCursor($c === Key::PAGE_UP ? Key::ARROW_UP : Key::ARROW_DOWN);
|
|
|
|
}
|
|
|
|
}
|
2019-10-14 16:21:41 -04:00
|
|
|
}
|