From fdd90e289e35a7001549c8f1f21a5723cf1d3c5a Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 9 Mar 2021 12:46:30 -0500 Subject: [PATCH] Fix arrow key movement and use Position class for cursor and offset --- src/Editor.php | 242 +++++++++++++++++++++++----------------------- src/Position.php | 17 ++++ src/functions.php | 2 +- 3 files changed, 141 insertions(+), 120 deletions(-) create mode 100644 src/Position.php diff --git a/src/Editor.php b/src/Editor.php index 9dcb779..9eadaf9 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -12,14 +12,34 @@ use Aviat\Kilo\Tokens\PHP8; class Editor { use Traits\MagicProperties; + /** + * @var string The screen buffer + */ private string $outputBuffer = ''; - protected int $cursorX = 0; - protected int $cursorY = 0; + /** + * @var Position The 0-based location of the cursor in the current viewport + */ + protected Position $cursor; + + /** + * @var Position The scroll offset of the file in the current viewport + */ + protected Position $offset; + + /** + * @var int The rendered cursor position + */ protected int $renderX = 0; - protected int $rowOffset = 0; - protected int $colOffset = 0; + + /** + * @var int The size of the current terminal in rows + */ protected int $screenRows = 0; + + /** + * @var int The size of the current terminal in columns + */ protected int $screenCols = 0; /** @@ -45,6 +65,8 @@ class Editor { private function __construct() { $this->statusMsgTime = time(); + $this->cursor = Position::default(); + $this->offset = Position::default(); [$this->screenRows, $this->screenCols] = Terminal::getWindowSize(); @@ -65,13 +87,11 @@ class Editor { public function __debugInfo(): array { return [ - 'colOffset' => $this->colOffset, - 'cursorX' => $this->cursorX, - 'cursorY' => $this->cursorY, + 'cursor' => $this->cursor, + 'offset' => $this->offset, 'dirty' => $this->dirty, 'filename' => $this->filename, 'renderX' => $this->renderX, - 'rowOffset' => $this->rowOffset, 'rows' => $this->rows, 'screenCols' => $this->screenCols, 'screenRows' => $this->screenRows, @@ -257,41 +277,41 @@ class Editor { protected function insertChar(string $c): void { - if ($this->cursorY === $this->numRows) + if ($this->cursor->y === $this->numRows) { $this->insertRow($this->numRows, ''); } - $this->rows[$this->cursorY]->insertChar($this->cursorX, $c); + $this->rows[$this->cursor->y]->insertChar($this->cursor->x, $c); // Re-tokenize the file $this->refreshPHPSyntax(); - $this->cursorX++; + $this->cursor->x++; } protected function insertNewline(): void { // @TODO attempt smart indentation on newline? - if ($this->cursorX === 0) + if ($this->cursor->x === 0) { - $this->insertRow($this->cursorY, ''); + $this->insertRow($this->cursor->y, ''); } else { - $row = $this->rows[$this->cursorY]; + $row = $this->rows[$this->cursor->y]; $chars = $row->chars; - $newChars = substr($chars, 0, $this->cursorX); + $newChars = substr($chars, 0, $this->cursor->x); // Truncate the previous row $row->chars = $newChars; // Add a new row, with the contents from the cursor to the end of the line - $this->insertRow($this->cursorY + 1, substr($chars, $this->cursorX)); + $this->insertRow($this->cursor->y + 1, substr($chars, $this->cursor->x)); } - $this->cursorY++; - $this->cursorX = 0; + $this->cursor->y++; + $this->cursor->x = 0; // Re-tokenize the file $this->refreshPHPSyntax(); @@ -299,23 +319,23 @@ class Editor { protected function deleteChar(): void { - if ($this->cursorY === $this->numRows || ($this->cursorX === 0 && $this->cursorY === 0)) + if ($this->cursor->y === $this->numRows || ($this->cursor->x === 0 && $this->cursor->y === 0)) { return; } - $row = $this->rows[$this->cursorY]; - if ($this->cursorX > 0) + $row = $this->rows[$this->cursor->y]; + if ($this->cursor->x > 0) { - $row->deleteChar($this->cursorX - 1); - $this->cursorX--; + $row->deleteChar($this->cursor->x - 1); + $this->cursor->x--; } else { - $this->cursorX = $this->rows[$this->cursorY - 1]->size; - $this->rows[$this->cursorY -1]->appendString($row->chars); - $this->deleteRow($this->cursorY); - $this->cursorY--; + $this->cursor->x = $this->rows[$this->cursor->y - 1]->size; + $this->rows[$this->cursor->y -1]->appendString($row->chars); + $this->deleteRow($this->cursor->y); + $this->cursor->y--; } // Re-tokenize the file @@ -457,9 +477,9 @@ class Editor { if ($match !== FALSE) { $lastMatch = $current; - $this->cursorY = (int)$current; - $this->cursorX = $this->rowRxToCx($row, $match); - $this->rowOffset = $this->numRows; + $this->cursor->y = (int)$current; + $this->cursor->x = $this->rowRxToCx($row, $match); + $this->offset->y = $this->numRows; $savedHlLine = $current; $savedHl = $row->hl; @@ -473,10 +493,10 @@ class Editor { protected function find(): void { - $savedCx = $this->cursorX; - $savedCy = $this->cursorY; - $savedColOff = $this->colOffset; - $savedRowOff = $this->rowOffset; + $savedCx = $this->cursor->x; + $savedCy = $this->cursor->y; + $savedColOff = $this->offset->x; + $savedRowOff = $this->offset->y; $query = $this->prompt('Search: %s (Use ESC/Arrows/Enter)', [$this, 'findCallback']); @@ -484,10 +504,10 @@ class Editor { // restore original cursor and scroll locations if ($query === '') { - $this->cursorX = $savedCx; - $this->cursorY = $savedCy; - $this->colOffset = $savedColOff; - $this->rowOffset = $savedRowOff; + $this->cursor->x = $savedCx; + $this->cursor->y = $savedCy; + $this->offset->x = $savedColOff; + $this->offset->y = $savedRowOff; } } @@ -498,29 +518,29 @@ class Editor { protected function scroll(): void { $this->renderX = 0; - if ($this->cursorY < $this->numRows) + if ($this->cursor->y < $this->numRows) { - $this->renderX = $this->rowCxToRx($this->rows[$this->cursorY], $this->cursorX); + $this->renderX = $this->rowCxToRx($this->rows[$this->cursor->y], $this->cursor->x); } // Vertical Scrolling - if ($this->cursorY < $this->rowOffset) + if ($this->cursor->y < $this->offset->y) { - $this->rowOffset = $this->cursorY; + $this->offset->y = $this->cursor->y; } - if ($this->cursorY >= ($this->rowOffset + $this->screenRows)) + else if ($this->cursor->y >= ($this->offset->y + $this->screenRows)) { - $this->rowOffset = $this->cursorY - $this->screenRows + 1; + $this->offset->y = $this->cursor->y - $this->screenRows + 1; } // Horizontal Scrolling - if ($this->renderX < $this->colOffset) + if ($this->renderX < $this->offset->x) { - $this->colOffset = $this->renderX; + $this->offset->x = $this->renderX; } - if ($this->renderX >= ($this->colOffset + $this->screenCols)) + else if ($this->renderX >= ($this->offset->x + $this->screenCols)) { - $this->colOffset = $this->renderX - $this->screenCols + 1; + $this->offset->x = $this->renderX - $this->screenCols + 1; } } @@ -528,7 +548,7 @@ class Editor { { for ($y = 0; $y < $this->screenRows; $y++) { - $filerow = $y + $this->rowOffset; + $filerow = $y + $this->offset->y; ($filerow >= $this->numRows) ? $this->drawPlaceholderRow($y) @@ -541,7 +561,7 @@ class Editor { protected function drawRow(int $rowIdx): void { - $len = $this->rows[$rowIdx]->rsize - $this->colOffset; + $len = $this->rows[$rowIdx]->rsize - $this->offset->x; if ($len < 0) { $len = 0; @@ -551,8 +571,8 @@ class Editor { $len = $this->screenCols; } - $chars = substr($this->rows[$rowIdx]->render, $this->colOffset, (int)$len); - $hl = array_slice($this->rows[$rowIdx]->hl, $this->colOffset, (int)$len); + $chars = substr($this->rows[$rowIdx]->render, $this->offset->x, (int)$len); + $hl = array_slice($this->rows[$rowIdx]->hl, $this->offset->x, (int)$len); $currentColor = -1; @@ -639,7 +659,7 @@ class Editor { $syntaxType = ($this->syntax !== NULL) ? $this->syntax->filetype : 'no ft'; $isDirty = ($this->dirty > 0) ? '(modified)' : ''; $status = sprintf('%.20s - %d lines %s', $statusFilename, $this->numRows, $isDirty); - $rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursorY + 1, $this->numRows); + $rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursor->y + 1, $this->numRows); $len = strlen($status); $rlen = strlen($rstatus); if ($len > $this->screenCols) @@ -692,8 +712,8 @@ class Editor { // Specify the current cursor position $this->outputBuffer .= ANSI::moveCursor( - $this->cursorY - $this->rowOffset, - $this->renderX - $this->colOffset + $this->cursor->y - $this->offset->y, + $this->renderX - $this->offset->x ); $this->outputBuffer .= ANSI::SHOW_CURSOR; @@ -753,58 +773,78 @@ class Editor { protected function moveCursor(string $key): void { - $row = ($this->cursorY >= $this->numRows) - ? NULL - : $this->rows[$this->cursorY]; + $row = $this->rows[$this->cursor->y]; switch ($key) { case KeyType::ARROW_LEFT: - if ($this->cursorX !== 0) + if ($this->cursor->x !== 0) { - $this->cursorX--; + $this->cursor->x--; } - else if ($this->cursorY > 0) + else if ($this->cursor->y > 0) { - $this->cursorY--; - $this->cursorX = $this->rows[$this->cursorY]->size; + $this->cursor->y--; + $this->cursor->x = $this->rows[$this->cursor->y]->size; } break; case KeyType::ARROW_RIGHT: - if ($row && $this->cursorX < $row->size) + if ($row && $this->cursor->x < $row->size) { - $this->cursorX++; + $this->cursor->x++; } - else if ($row && $this->cursorX === $row->size) + else if ($row && $this->cursor->x === $row->size) { - $this->cursorY++; - $this->cursorX = 0; + $this->cursor->y++; + $this->cursor->x = 0; } break; case KeyType::ARROW_UP: - if ($this->cursorY !== 0) + if ($this->cursor->y !== 0) { - $this->cursorY--; + $this->cursor->y--; } break; case KeyType::ARROW_DOWN: - if ($this->cursorY < $this->numRows) + if ($this->cursor->y < $this->numRows) { - $this->cursorY++; + $this->cursor->y++; } break; + + case KeyType::PAGE_UP: + $this->cursor->y = ($this->cursor->y > $this->screenRows) + ? $this->cursor->y - $this->screenRows + : 0; + break; + + case KeyType::PAGE_DOWN: + $this->cursor->y = ($this->cursor->y + $this->screenRows < $this->numRows) + ? $this->cursor->y + $this->screenRows + : $this->numRows; + break; + + case KeyType::HOME_KEY: + $this->cursor->x = 0; + break; + + case KeyType::END_KEY: + if ($this->cursor->y < $this->numRows) + { + $this->cursor->x = $this->rows[$this->cursor->y]->size - 1; + } + break; + + default: + // Do nothing } - $row = ($this->cursorY >= $this->numRows) - ? NULL - : $this->rows[$this->cursorY]; - $rowlen = $row->size ?? 0; - if ($this->cursorX > $rowlen) + if ($this->cursor->x > $row->size) { - $this->cursorX = $rowlen; + $this->cursor->x = $row->size; } } @@ -834,25 +874,12 @@ class Editor { return ''; } Terminal::clear(); - - return NULL; - break; + return NULL; case KeyCode::CTRL('s'): $this->save(); break; - case KeyType::HOME_KEY: - $this->cursorX = 0; - break; - - case KeyType::END_KEY: - if ($this->cursorY < $this->numRows) - { - $this->cursorX = $this->rows[$this->cursorY]->size - 1; - } - break; - case KeyCode::CTRL('f'): $this->find(); break; @@ -866,15 +893,14 @@ class Editor { $this->deleteChar(); break; - case KeyType::PAGE_UP: - case KeyType::PAGE_DOWN: - $this->pageUpOrDown($c); - 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; @@ -893,28 +919,6 @@ class Editor { return $c; } - public function pageUpOrDown(string $c): void - { - if ($c === KeyType::PAGE_UP) - { - $this->cursorY = $this->rowOffset; - } - else if ($c === KeyType::PAGE_DOWN) - { - $this->cursorY = $this->rowOffset + $this->screenRows - 1; - if ($this->cursorY > $this->numRows) - { - $this->cursorY = $this->numRows; - } - } - - $times = $this->screenRows; - for (; $times > 0; $times--) - { - $this->moveCursor($c === KeyType::PAGE_UP ? KeyType::ARROW_UP : KeyType::ARROW_DOWN); - } - } - protected function refreshSyntax(): void { // Update the syntax highlighting for all the rows of the file diff --git a/src/Position.php b/src/Position.php new file mode 100644 index 0000000..2bfb297 --- /dev/null +++ b/src/Position.php @@ -0,0 +1,17 @@ +