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(); 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 // ! File I/O

View File

@ -3,7 +3,13 @@
namespace Aviat\Kilo; namespace Aviat\Kilo;
use Aviat\Kilo\Type\TerminalSize; 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}; 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 // ! Row Operations
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -180,17 +200,6 @@ class Editor {
return $cx; return $cx;
} }
// ------------------------------------------------------------------------
// ! Editor Operations
// ------------------------------------------------------------------------
protected function insertChar(string $c): void
{
$this->document->insert($this->cursor, $c);
$this->moveCursor(KeyType::ARROW_RIGHT);
}
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// ! File I/O // ! File I/O
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -226,8 +235,8 @@ class Editor {
protected function findCallback(string $query, string $key): void protected function findCallback(string $query, string $key): void
{ {
static $lastMatch = -1; static $lastMatch = NO_MATCH;
static $direction = 1; static $direction = SearchDirection::FORWARD;
static $savedHlLine = 0; static $savedHlLine = 0;
static $savedHl = []; static $savedHl = [];
@ -236,7 +245,7 @@ class Editor {
{ {
$row = $this->document->row($savedHlLine); $row = $this->document->row($savedHlLine);
if ($row !== null) if ($row->isValid())
{ {
$row->hl = $savedHl; $row->hl = $savedHl;
} }
@ -244,35 +253,31 @@ class Editor {
$savedHl = []; $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))
{ {
case KeyCode::ENTER: $lastMatch = NO_MATCH;
case KeyCode::ESCAPE: $direction = SearchDirection::FORWARD;
$lastMatch = -1;
$direction = 1; if ($key === KeyCode::ENTER || $key === KeyCode::ESCAPE)
{
return; 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)) if (empty($query))
{ {
@ -292,7 +297,7 @@ class Editor {
} }
$row = $this->document->row($current); $row = $this->document->row($current);
if ($row === null) if ( ! $row->isValid())
{ {
break; break;
} }
@ -342,7 +347,7 @@ class Editor {
{ {
$row = $this->document->row($this->cursor->y); $row = $this->document->row($this->cursor->y);
if ($row !== null) if ($row->isValid())
{ {
$this->renderX = $this->rowCxToRx($row, $this->cursor->x); $this->renderX = $this->rowCxToRx($row, $this->cursor->x);
} }
@ -389,7 +394,7 @@ class Editor {
protected function drawRow(int $rowIdx): void protected function drawRow(int $rowIdx): void
{ {
$row = $this->document->row($rowIdx); $row = $this->document->row($rowIdx);
if ($row === null) if ( ! $row->isValid())
{ {
return; return;
} }
@ -556,15 +561,6 @@ class Editor {
Terminal::write($this->outputBuffer, strlen($this->outputBuffer)); 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 // ! Input
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -607,99 +603,9 @@ class Editor {
} }
} }
protected function moveCursor(string $key): void /**
{ * Input processing
$x = $this->cursor->x; */
$y = $this->cursor->y;
$row = $this->document->row($y);
if ($row === NULL)
{
return;
}
switch ($key)
{
case KeyType::ARROW_LEFT:
if ($x !== 0)
{
$x--;
}
else if ($y > 0)
{
// Beginning of a line, go to end of previous line
$y--;
$x = $row->size - 1;
}
break;
case KeyType::ARROW_RIGHT:
if ($x < $row->size)
{
$x++;
}
else if ($x === $row->size)
{
$y++;
$x = 0;
}
break;
case KeyType::ARROW_UP:
if ($y !== 0)
{
$y--;
}
break;
case KeyType::ARROW_DOWN:
if ($y < $this->document->numRows)
{
$y++;
}
break;
case KeyType::PAGE_UP:
$y = ($y > $this->terminalSize->rows)
? $y - $this->terminalSize->rows
: 0;
break;
case KeyType::PAGE_DOWN:
$y = ($y + $this->terminalSize->rows < $this->document->numRows)
? $y + $this->terminalSize->rows
: $this->document->numRows;
break;
case KeyType::HOME_KEY:
$x = 0;
break;
case KeyType::END_KEY:
if ($y < $this->document->numRows)
{
$x = $row->size;
}
break;
default:
// Do nothing
}
// 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 ($x > $row->size)
{
$x = $row->size;
}
$this->cursor->x = $x;
$this->cursor->y = $y;
}
}
protected function processKeypress(): void protected function processKeypress(): void
{ {
$c = Terminal::readKey(); $c = Terminal::readKey();
@ -768,6 +674,109 @@ class Editor {
} }
} }
// ------------------------------------------------------------------------
// ! Editor operation helpers
// ------------------------------------------------------------------------
protected function moveCursor(string $key): void
{
$x = $this->cursor->x;
$y = $this->cursor->y;
$row = $this->document->row($y);
if ( ! $row->isValid())
{
return;
}
switch ($key)
{
case KeyType::ARROW_LEFT:
if ($x !== 0)
{
$x--;
}
else if ($y > 0)
{
// Beginning of a line, go to end of previous line
$y--;
$x = $row->size - 1;
}
break;
case KeyType::ARROW_RIGHT:
if ($x < $row->size)
{
$x++;
}
else if ($x === $row->size)
{
$y++;
$x = 0;
}
break;
case KeyType::ARROW_UP:
if ($y !== 0)
{
$y--;
}
break;
case KeyType::ARROW_DOWN:
if ($y < $this->document->numRows)
{
$y++;
}
break;
case KeyType::PAGE_UP:
$y = ($y > $this->terminalSize->rows)
? $y - $this->terminalSize->rows
: 0;
break;
case KeyType::PAGE_DOWN:
$y = ($y + $this->terminalSize->rows < $this->document->numRows)
? $y + $this->terminalSize->rows
: $this->document->numRows;
break;
case KeyType::HOME_KEY:
$x = 0;
break;
case KeyType::END_KEY:
if ($y < $this->document->numRows)
{
$x = $row->size;
}
break;
default:
// Do nothing
}
// 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->isValid())
{
if ($x > $row->size)
{
$x = $row->size;
}
$this->cursor->x = $x;
$this->cursor->y = $y;
}
}
protected function insertChar(string $c): void
{
$this->document->insert($this->cursor, $c);
$this->moveCursor(KeyType::ARROW_RIGHT);
}
protected function quitAttempt(): void protected function quitAttempt(): void
{ {
if ($this->document->isDirty() && $this->quitTimes > 0) if ($this->document->isDirty() && $this->quitTimes > 0)

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 $size
* @property-read int $rsize * @property-read int $rsize
* @property-read string $chars * @property string $chars
*/ */
class Row { class Row {
use Traits\MagicProperties; use Traits\MagicProperties;
private string $chars = ''; /**
* The version of the row to be displayed (where tabs are converted to display spaces)
*/
public string $render = ''; public string $render = '';
/**
* The mapping of characters to their highlighting type
*/
public array $hl = []; public array $hl = [];
public int $idx;
// This feels dirty...
private Document $parent;
private bool $hlOpenComment = FALSE; private bool $hlOpenComment = FALSE;
private const T_RAW = -1; private const T_RAW = -1;
public static function new(Document $parent, string $chars, int $idx): self public static function new(Document $parent, string $chars, int $idx): self
{ {
$self = new self(); return new self(
$self->chars = $chars; $parent,
$self->parent = $parent; $chars,
$self->idx = $idx; $idx,
);
return $self;
} }
private function __construct() { public static function default(): self
// Private in favor of ::new static function {
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 public function __get(string $name): mixed
{ {
return match ($name) 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 public function insert(int $at, string $c): void
{ {
if ($at < 0 || $at > $this->size) if ($at < 0 || $at > $this->size)
@ -123,6 +155,60 @@ class Row {
$this->highlightGeneral(); $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 protected function highlightGeneral(): void
{ {
$this->hl = array_fill(0, $this->rsize, Highlight::NORMAL); $this->hl = array_fill(0, $this->rsize, Highlight::NORMAL);
@ -192,37 +278,7 @@ class Row {
} }
// String/Char literals // String/Char literals
if ($this->parent->fileType->syntax->flags & Syntax::HIGHLIGHT_STRINGS) $this->highlightString($i);
{
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;
}
}
// Numbers, including decimal points // Numbers, including decimal points
if ($this->parent->fileType->syntax->flags & Syntax::HIGHLIGHT_NUMBERS) 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_VERSION = '0.3.0';
const KILO_TAB_STOP = 4; const KILO_TAB_STOP = 4;
const KILO_QUIT_TIMES = 3; const KILO_QUIT_TIMES = 3;
const NO_MATCH = -1;