Start splitting render methods
All checks were successful
timw4mail/php-kilo/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2021-03-18 16:26:30 -04:00
parent d92ca27880
commit 6c568dfaac
5 changed files with 277 additions and 190 deletions

View File

@ -40,12 +40,17 @@ class Document {
return new self();
}
public function row(int $index): ?Row
public function row(int $index): Row
{
return (array_key_exists($index, $this->rows)) ? $this->rows[$index] : null;
return (array_key_exists($index, $this->rows))
? $this->rows[$index]
: Row::default();
}
public function isEmpty(): bool
{
return empty($this->rows);
}
// ------------------------------------------------------------------------
// ! File I/O

View File

@ -3,7 +3,13 @@
namespace Aviat\Kilo;
use Aviat\Kilo\Type\TerminalSize;
use Aviat\Kilo\Enum\{Color, KeyCode, KeyType, Highlight};
use Aviat\Kilo\Enum\{
Color,
KeyCode,
KeyType,
Highlight,
SearchDirection
};
use Aviat\Kilo\Type\{Point, StatusMessage};
/**
@ -127,6 +133,20 @@ class Editor {
}
}
/**
* Set a status message to be displayed, using printf formatting
* @param string $fmt
* @param mixed ...$args
*/
public function setStatusMessage(string $fmt, mixed ...$args): void
{
$text = func_num_args() > 1
? sprintf($fmt, ...$args)
: $fmt;
$this->statusMessage = StatusMessage::from($text);
}
// ------------------------------------------------------------------------
// ! Row Operations
// ------------------------------------------------------------------------
@ -180,17 +200,6 @@ class Editor {
return $cx;
}
// ------------------------------------------------------------------------
// ! Editor Operations
// ------------------------------------------------------------------------
protected function insertChar(string $c): void
{
$this->document->insert($this->cursor, $c);
$this->moveCursor(KeyType::ARROW_RIGHT);
}
// ------------------------------------------------------------------------
// ! File I/O
// ------------------------------------------------------------------------
@ -226,8 +235,8 @@ class Editor {
protected function findCallback(string $query, string $key): void
{
static $lastMatch = -1;
static $direction = 1;
static $lastMatch = NO_MATCH;
static $direction = SearchDirection::FORWARD;
static $savedHlLine = 0;
static $savedHl = [];
@ -236,7 +245,7 @@ class Editor {
{
$row = $this->document->row($savedHlLine);
if ($row !== null)
if ($row->isValid())
{
$row->hl = $savedHl;
}
@ -244,35 +253,31 @@ class Editor {
$savedHl = [];
}
switch ($key)
$direction = match ($key) {
KeyType::ARROW_UP, KeyType::ARROW_LEFT => SearchDirection::BACKWARD,
default => SearchDirection::FORWARD
};
$arrowKeys = [KeyType::ARROW_UP, KeyType::ARROW_DOWN, KeyType::ARROW_LEFT, KeyType::ARROW_RIGHT];
// Reset search state with non arrow-key input
if ( ! in_array($key, $arrowKeys, true))
{
$lastMatch = NO_MATCH;
$direction = SearchDirection::FORWARD;
if ($key === KeyCode::ENTER || $key === KeyCode::ESCAPE)
{
case KeyCode::ENTER:
case KeyCode::ESCAPE:
$lastMatch = -1;
$direction = 1;
return;
case KeyType::ARROW_DOWN:
case KeyType::ARROW_RIGHT:
$direction = 1;
break;
case KeyType::ARROW_UP:
case KeyType::ARROW_LEFT:
$direction = -1;
break;
default:
$lastMatch = -1;
$direction = 1;
}
}
if ($lastMatch === -1)
if ($lastMatch === NO_MATCH)
{
$direction = 1;
$direction = SearchDirection::FORWARD;
}
$current = $lastMatch;
$current = (int)$lastMatch;
if (empty($query))
{
@ -292,7 +297,7 @@ class Editor {
}
$row = $this->document->row($current);
if ($row === null)
if ( ! $row->isValid())
{
break;
}
@ -342,7 +347,7 @@ class Editor {
{
$row = $this->document->row($this->cursor->y);
if ($row !== null)
if ($row->isValid())
{
$this->renderX = $this->rowCxToRx($row, $this->cursor->x);
}
@ -389,7 +394,7 @@ class Editor {
protected function drawRow(int $rowIdx): void
{
$row = $this->document->row($rowIdx);
if ($row === null)
if ( ! $row->isValid())
{
return;
}
@ -556,15 +561,6 @@ class Editor {
Terminal::write($this->outputBuffer, strlen($this->outputBuffer));
}
public function setStatusMessage(string $fmt, mixed ...$args): void
{
$text = func_num_args() > 1
? sprintf($fmt, ...$args)
: $fmt;
$this->statusMessage = StatusMessage::from($text);
}
// ------------------------------------------------------------------------
// ! Input
// ------------------------------------------------------------------------
@ -607,12 +603,87 @@ class Editor {
}
}
/**
* Input processing
*/
protected function processKeypress(): void
{
$c = Terminal::readKey();
if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
{
return;
}
switch ($c)
{
case KeyCode::CTRL('q'):
$this->quitAttempt();
return;
case KeyCode::CTRL('s'):
$this->save();
break;
case KeyCode::CTRL('f'):
$this->find();
break;
case KeyType::DEL_KEY:
$this->document->delete($this->cursor);
break;
case KeyType::BACKSPACE:
if ($this->cursor->x > 0 || $this->cursor->y > 0)
{
$this->moveCursor(KeyType::ARROW_LEFT);
$this->document->delete($this->cursor);
}
break;
case KeyType::ARROW_UP:
case KeyType::ARROW_DOWN:
case KeyType::ARROW_LEFT:
case KeyType::ARROW_RIGHT:
case KeyType::PAGE_UP:
case KeyType::PAGE_DOWN:
case KeyType::HOME_KEY:
case KeyType::END_KEY:
$this->moveCursor($c);
break;
case KeyCode::CTRL('l'):
case KeyType::ESCAPE:
// Do nothing
break;
case KeyType::ENTER:
$this->insertChar("\n");
break;
default:
$this->insertChar($c);
break;
}
// Reset quit confirmation timer on different keypress
if ($this->quitTimes < KILO_QUIT_TIMES)
{
$this->quitTimes = KILO_QUIT_TIMES;
$this->setStatusMessage('');
}
}
// ------------------------------------------------------------------------
// ! Editor operation helpers
// ------------------------------------------------------------------------
protected function moveCursor(string $key): void
{
$x = $this->cursor->x;
$y = $this->cursor->y;
$row = $this->document->row($y);
if ($row === NULL)
if ( ! $row->isValid())
{
return;
}
@ -688,7 +759,7 @@ class Editor {
// Snap cursor to the end of a row when moving
// from a longer row to a shorter one
$row = $this->document->row($y);
if ($row !== null)
if ($row->isValid())
{
if ($x > $row->size)
{
@ -700,72 +771,10 @@ class Editor {
}
}
protected function processKeypress(): void
protected function insertChar(string $c): void
{
$c = Terminal::readKey();
if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
{
return;
}
switch ($c)
{
case KeyCode::CTRL('q'):
$this->quitAttempt();
return;
case KeyCode::CTRL('s'):
$this->save();
break;
case KeyCode::CTRL('f'):
$this->find();
break;
case KeyType::DEL_KEY:
$this->document->delete($this->cursor);
break;
case KeyType::BACKSPACE:
if ($this->cursor->x > 0 || $this->cursor->y > 0)
{
$this->moveCursor(KeyType::ARROW_LEFT);
$this->document->delete($this->cursor);
}
break;
case KeyType::ARROW_UP:
case KeyType::ARROW_DOWN:
case KeyType::ARROW_LEFT:
case KeyType::ARROW_RIGHT:
case KeyType::PAGE_UP:
case KeyType::PAGE_DOWN:
case KeyType::HOME_KEY:
case KeyType::END_KEY:
$this->moveCursor($c);
break;
case KeyCode::CTRL('l'):
case KeyType::ESCAPE:
// Do nothing
break;
case KeyType::ENTER:
$this->insertChar("\n");
break;
default:
$this->insertChar($c);
break;
}
// Reset quit confirmation timer on different keypress
if ($this->quitTimes < KILO_QUIT_TIMES)
{
$this->quitTimes = KILO_QUIT_TIMES;
$this->setStatusMessage('');
}
$this->document->insert($this->cursor, $c);
$this->moveCursor(KeyType::ARROW_RIGHT);
}
protected function quitAttempt(): void

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace Aviat\Kilo\Enum;
use Aviat\Kilo\Traits;
/**
* @enum
*/
class SearchDirection {
use Traits\ConstList;
public const FORWARD = 1;
public const BACKWARD = -1;
}

View File

@ -8,38 +8,60 @@ use Aviat\Kilo\Enum\KeyCode;
/**
* @property-read int $size
* @property-read int $rsize
* @property-read string $chars
* @property string $chars
*/
class Row {
use Traits\MagicProperties;
private string $chars = '';
/**
* The version of the row to be displayed (where tabs are converted to display spaces)
*/
public string $render = '';
/**
* The mapping of characters to their highlighting type
*/
public array $hl = [];
public int $idx;
// This feels dirty...
private Document $parent;
private bool $hlOpenComment = FALSE;
private const T_RAW = -1;
public static function new(Document $parent, string $chars, int $idx): self
{
$self = new self();
$self->chars = $chars;
$self->parent = $parent;
$self->idx = $idx;
return $self;
return new self(
$parent,
$chars,
$idx,
);
}
private function __construct() {
// Private in favor of ::new static function
public static function default(): self
{
return new self(
Document::new(),
'',
0,
);
}
private function __construct(
/**
* The document that this row belongs to
*/
private Document $parent,
/**
* @var string The raw characters in the row
*/
private string $chars,
/**
* @var int The line number of the current row
*/
public int $idx,
) {}
public function __get(string $name): mixed
{
return match ($name)
@ -77,6 +99,16 @@ class Row {
];
}
/**
* Is this row a valid part of a document?
*
* @return bool
*/
public function isValid(): bool
{
return ! $this->parent->isEmpty();
}
public function insert(int $at, string $c): void
{
if ($at < 0 || $at > $this->size)
@ -123,6 +155,60 @@ class Row {
$this->highlightGeneral();
}
protected function highlightComment(int $i, string $char): bool
{
$scs = $this->parent->fileType->syntax->singleLineCommentStart;
$scsLen = strlen($scs);
if ($scsLen > 0 && substr($this->render, $i, $scsLen) === $scs)
{
array_replace_range($this->hl, $i, $this->rsize - $i, Highlight::COMMENT);
return true;
}
return false;
}
protected function highlightString(int &$i): bool
{
$char = $this->render[$i];
$enabled = $this->parent->fileType->syntax->flags & Syntax::HIGHLIGHT_STRINGS;
if ($enabled && $char === '"' || $char === '\'')
{
$quote = $char;
$this->hl[$i] = Highlight::STRING;
$i++;
while ($i < $this->rsize)
{
$char = $this->render[$i];
$this->hl[$i] = Highlight::STRING;
// Check for escaped character
if ($char === '\\' && $i+1 < $this->rsize)
{
$this->hl[$i + 1] = Highlight::STRING;
$i += 2;
continue;
}
// End of the string!
if ($char === $quote)
{
$i++;
break;
}
$i++;
}
return true;
}
return false;
}
protected function highlightGeneral(): void
{
$this->hl = array_fill(0, $this->rsize, Highlight::NORMAL);
@ -192,37 +278,7 @@ class Row {
}
// String/Char literals
if ($this->parent->fileType->syntax->flags & Syntax::HIGHLIGHT_STRINGS)
{
if ($inString !== '')
{
$this->hl[$i] = Highlight::STRING;
// Check for escaped character
if ($char === '\\' && $i+1 < $this->rsize)
{
$this->hl[$i + 1] = Highlight::STRING;
$i += 2;
continue;
}
if ($char === $inString)
{
$inString = '';
}
$i++;
$prevSep = 1;
continue;
}
if ( $char === '"' || $char === '\'')
{
$inString = $char;
$this->hl[$i] = Highlight::STRING;
$i++;
continue;
}
}
$this->highlightString($i);
// Numbers, including decimal points
if ($this->parent->fileType->syntax->flags & Syntax::HIGHLIGHT_NUMBERS)

View File

@ -8,3 +8,5 @@ namespace Aviat\Kilo;
const KILO_VERSION = '0.3.0';
const KILO_TAB_STOP = 4;
const KILO_QUIT_TIMES = 3;
const NO_MATCH = -1;