Fix arrow key movement and use Position class for cursor and offset
This commit is contained in:
parent
e4ffe8eb98
commit
fdd90e289e
242
src/Editor.php
242
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
|
||||
|
17
src/Position.php
Normal file
17
src/Position.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user