From 6c568dfaacb10b2900939f849d72e59e98dbd9fc Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 18 Mar 2021 16:26:30 -0400 Subject: [PATCH] Start splitting render methods --- src/Document.php | 11 +- src/Editor.php | 293 ++++++++++++++++++----------------- src/Enum/SearchDirection.php | 15 ++ src/Row.php | 146 +++++++++++------ src/constants.php | 2 + 5 files changed, 277 insertions(+), 190 deletions(-) create mode 100644 src/Enum/SearchDirection.php diff --git a/src/Document.php b/src/Document.php index 5b74987..8958374 100644 --- a/src/Document.php +++ b/src/Document.php @@ -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 diff --git a/src/Editor.php b/src/Editor.php index 3125732..48ab926 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -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)) { - case KeyCode::ENTER: - case KeyCode::ESCAPE: - $lastMatch = -1; - $direction = 1; + $lastMatch = NO_MATCH; + $direction = SearchDirection::FORWARD; + + if ($key === KeyCode::ENTER || $key === KeyCode::ESCAPE) + { 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,99 +603,9 @@ class Editor { } } - protected function moveCursor(string $key): void - { - $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; - } - } - + /** + * Input processing + */ protected function processKeypress(): void { $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 { if ($this->document->isDirty() && $this->quitTimes > 0) diff --git a/src/Enum/SearchDirection.php b/src/Enum/SearchDirection.php new file mode 100644 index 0000000..4e149b7 --- /dev/null +++ b/src/Enum/SearchDirection.php @@ -0,0 +1,15 @@ +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) diff --git a/src/constants.php b/src/constants.php index 7a78500..b3318e6 100644 --- a/src/constants.php +++ b/src/constants.php @@ -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;