Fix arrow key movement and use Position class for cursor and offset

This commit is contained in:
Timothy Warren 2021-03-09 12:46:30 -05:00
parent e4ffe8eb98
commit fdd90e289e
3 changed files with 141 additions and 120 deletions

View File

@ -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

17
src/Position.php Normal file
View File

@ -0,0 +1,17 @@
<?php declare(strict_types=1);
namespace Aviat\Kilo;
class Position {
private function __construct(public int $x, public int $y) {}
public static function new(int $x, int $y): self
{
return new Position($x, $y);
}
public static function default(): self
{
return new Position(0, 0);
}
}

View File

@ -118,7 +118,7 @@ function is_separator(string $char): bool
* @param int $length The number of indices to update
* @param mixed $value The value to replace in the range
*/
function array_replace_range(array &$array, int $offset, int $length, $value):void
function array_replace_range(array &$array, int $offset, int $length, mixed $value):void
{
if ($length === 1)
{