Get syntax highlighting and keyboard natvigation working again...buggy editing functionality
Some checks failed
timw4mail/php-kilo/pipeline/head There was a failure building this commit

This commit is contained in:
Timothy Warren 2021-03-17 13:14:16 -04:00
parent d0aea78ac3
commit 85e96264a8
6 changed files with 176 additions and 194 deletions

18
kilo
View File

@ -6,6 +6,22 @@ namespace Aviat\Kilo;
require_once __DIR__ . '/vendor/autoload.php';
// Log notices/errors/warnings to file
set_error_handler(static function (
$errno,
$errstr,
$errfile,
$errline
) {
$msg = print_r([
'code' => $errno,
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
], TRUE);
file_put_contents('error.log', $msg, FILE_APPEND);
return true;
}, -1);
set_exception_handler(static function (mixed $e) {
$msg = print_r([
'code' => $e->getCode(),
@ -14,7 +30,7 @@ set_exception_handler(static function (mixed $e) {
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
], TRUE);
file_put_contents('kilo.log', $msg, FILE_APPEND);
file_put_contents('exception.log', $msg, FILE_APPEND);
});
// ! Init with an IIFE

View File

@ -17,8 +17,8 @@ class Document {
public array $tokens = [];
private function __construct(
public array $rows = [],
public string $filename = '',
public array $rows = [],
public bool $dirty = FALSE,
) {
$this->fileType = FileType::from($this->filename);
@ -34,7 +34,7 @@ class Document {
return NULL;
}
public static function default(): self
public static function new(): self
{
return new self();
}
@ -46,7 +46,11 @@ class Document {
return implode('', $lines);
}
public static function open(string $filename): ?self
// ------------------------------------------------------------------------
// ! File I/O
// ------------------------------------------------------------------------
public function open(string $filename): ?self
{
$handle = fopen($filename, 'rb');
if ($handle === FALSE)
@ -54,19 +58,20 @@ class Document {
return NULL;
}
$self = new self(filename: $filename);
$this->__construct($filename);
while (($line = fgets($handle)) !== FALSE)
{
// Remove line endings when reading the file
$self->insertRow($self->numRows, rtrim($line), FALSE);
$this->rows[] = Row::new($this, rtrim($line), $this->numRows);
// $this->insertRow($this->numRows, rtrim($line), FALSE);
}
fclose($handle);
$self->selectSyntaxHighlight();
$this->selectSyntaxHighlight();
return $self;
return $this;
}
public function save(): int|false
@ -85,11 +90,44 @@ class Document {
public function insert(Point $at, string $c): void
{
if ($at->y === $this->numRows)
if ($at->y > $this->numRows)
{
return;
}
$this->dirty = true;
if ($c === "\n")
{
$this->insertNewline($at);
}
else if ($at->y === $this->numRows)
{
$this->insertRow($this->numRows, '');
}
$this->rows[$at->y]->insertChar($at->x, $c);
$this->rows[$at->y]->insert($at->x, $c);
}
public function delete(Point $at): void
{
if ($at->y > $this->numRows)
{
return;
}
$this->dirty = true;
$row =& $this->rows[$at->y];
if ($at->x === $this->rows[$at->y]->size && $at->y + 1 < $this->numRows)
{
$this->rows[$at->y]->append($this->rows[$at->y + 1]->chars);
$this->deleteRow($at->y + 1);
}
else
{
$row->delete($at->x);
}
}
public function insertRow(int $at, string $s, bool $updateSyntax = TRUE): void
@ -122,9 +160,7 @@ class Document {
ksort($this->rows);
$this->rows[$at]->update();
$this->dirty = true;
// $this->rows[$at]->highlight();
// Re-tokenize the file
if ($updateSyntax)
@ -133,42 +169,61 @@ class Document {
}
}
protected function deleteRow(int $at): void
{
if ($at < 0 || $at >= $this->numRows)
{
return;
}
// Remove the row
unset($this->rows[$at]);
// Re-index the array of rows
$this->rows = array_values($this->rows);
for ($i = $at; $i < $this->numRows; $i++)
{
$this->rows[$i]->idx = $i;
}
// Re-tokenize the file
$this->refreshPHPSyntax();
$this->dirty = true;
}
public function isDirty(): bool
{
return $this->dirty;
}
public function deleteChar(Point $at): void
protected function insertNewline(Point $at): void
{
}
protected function insertNewline(): void
{
// @TODO attempt smart indentation on newline?
if ($this->cursor->x === 0)
if ($at->y > $this->numRows)
{
$this->insertRow($this->cursor->y, '');
return;
}
if ($at->y === $this->numRows)
{
$this->insertRow($this->numRows, '');
}
else if ($at->x === 1)
{
$this->insertRow($at->y, '');
}
else
{
$row = $this->rows[$this->cursor->y];
$row = $this->rows[$at->y];
$chars = $row->chars;
$newChars = substr($chars, 0, $this->cursor->x);
$newChars = substr($chars, 0, $at->x);
// Truncate the previous row
$row->chars = $newChars;
$row->chars = $newChars;
// Add a new row, with the contents from the cursor to the end of the line
$this->insertRow($this->cursor->y + 1, substr($chars, $this->cursor->x));
// Add a new row with the contents of the previous row at the point of the split
$this->insertRow($at->y + 1, substr($chars, $at->x));
}
$this->cursor->y++;
$this->cursor->x = 0;
// Re-tokenize the file
$this->refreshPHPSyntax();
}
public function selectSyntaxHighlight(): void
@ -189,7 +244,7 @@ class Document {
public function refreshSyntax(): void
{
// Update the syntax highlighting for all the rows of the file
array_walk($this->rows, static fn (Row $row) => $row->update());
array_walk($this->rows, static fn (Row $row) => $row->highlight());
}
private function refreshPHPSyntax(): void

View File

@ -83,14 +83,13 @@ class Editor {
$this->cursor = Point::new();
$this->offset = Point::new();
$this->terminalSize = Terminal::size();
$this->document = Document::default();
if (is_string($filename))
{
$maybeDocument = Document::open($filename);
$maybeDocument = Document::new()->open($filename);
if ($maybeDocument === NULL)
{
$this->document = Document::default();
$this->document = Document::new();
$this->setStatusMessage("ERR: Could not open file: {}", $filename);
}
else
@ -124,41 +123,6 @@ class Editor {
}
}
// ------------------------------------------------------------------------
// ! Terminal
// ------------------------------------------------------------------------
protected function readKey(): string
{
$c = Terminal::read();
return match($c)
{
// Unambiguous mappings
KeyCode::ARROW_DOWN => KeyType::ARROW_DOWN,
KeyCode::ARROW_LEFT => KeyType::ARROW_LEFT,
KeyCode::ARROW_RIGHT => KeyType::ARROW_RIGHT,
KeyCode::ARROW_UP => KeyType::ARROW_UP,
KeyCode::DEL_KEY => KeyType::DEL_KEY,
KeyCode::ENTER => KeyType::ENTER,
KeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
KeyCode::PAGE_UP => KeyType::PAGE_UP,
// Backspace
KeyCode::CTRL('h'), KeyCode::BACKSPACE => KeyType::BACKSPACE,
// Escape
KeyCode::CTRL('l'), KeyCode::ESCAPE => KeyType::ESCAPE,
// Home Key
"\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::HOME_KEY,
// End Key
"\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::END_KEY,
default => $c,
};
}
// ------------------------------------------------------------------------
// ! Row Operations
// ------------------------------------------------------------------------
@ -213,29 +177,6 @@ class Editor {
}
protected function deleteRow(int $at): void
{
if ($at < 0 || $at >= $this->document->numRows)
{
return;
}
// Remove the row
unset($this->document->rows[$at]);
// Re-index the array of rows
$this->document->rows = array_values($this->document->rows);
for ($i = $at; $i < $this->document->numRows; $i++)
{
$this->document->rows[$i]->idx = $i;
}
// Re-tokenize the file
$this->refreshPHPSyntax();
$this->dirty = true;
}
// ------------------------------------------------------------------------
// ! Editor Operations
// ------------------------------------------------------------------------
@ -246,71 +187,10 @@ class Editor {
$this->moveCursor(KeyType::ARROW_RIGHT);
}
protected function insertNewline(): void
{
// @TODO attempt smart indentation on newline?
if ($this->cursor->x === 0)
{
$this->document->insert(Point::new(0, $this->cursor->y), '');
}
else
{
$row = $this->document->rows[$this->cursor->y];
$chars = $row->chars;
$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->document->insert(Point::new(0, $this->cursor->y + 1), substr($chars, $this->cursor->x));
// $this->insertRow($this->cursor->y + 1, substr($chars, $this->cursor->x));
}
$this->cursor->y++;
$this->cursor->x = 0;
// Re-tokenize the file
// $this->refreshPHPSyntax();
}
protected function deleteChar(): void
{
if ($this->cursor->y === $this->document->numRows || ($this->cursor->x === 0 && $this->cursor->y === 0))
{
return;
}
$row = $this->document->rows[$this->cursor->y];
if ($this->cursor->x > 0)
{
$row->deleteChar($this->cursor->x - 1);
$this->cursor->x--;
}
else
{
$this->cursor->x = $this->document->rows[$this->cursor->y - 1]->size;
$this->document->rows[$this->cursor->y -1]->appendString($row->chars);
$this->deleteRow($this->cursor->y);
$this->cursor->y--;
}
// Re-tokenize the file
$this->refreshPHPSyntax();
}
// ------------------------------------------------------------------------
// ! File I/O
// ------------------------------------------------------------------------
protected function rowsToString(): string
{
$lines = array_map(fn (Row $row) => (string)$row, $this->document->rows);
return implode('', $lines);
}
protected function save(): void
{
if ($this->document->filename === '')
@ -474,13 +354,13 @@ class Editor {
{
for ($y = 0; $y < $this->terminalSize->rows; $y++)
{
$filerow = $y + $this->offset->y;
$fileRow = $y + $this->offset->y;
$this->outputBuffer .= ANSI::CLEAR_LINE;
($filerow >= $this->document->numRows)
($fileRow >= $this->document->numRows)
? $this->drawPlaceholderRow($y)
: $this->drawRow($filerow);
: $this->drawRow($fileRow);
$this->outputBuffer .= "\r\n";
}
@ -582,9 +462,9 @@ class Editor {
{
$this->outputBuffer .= ANSI::color(Color::INVERT);
$statusFilename = $this->filename !== '' ? $this->filename : '[No Name]';
$syntaxType = ($this->syntax !== NULL) ? $this->syntax->filetype : 'no ft';
$isDirty = $this->dirty ? '(modified)' : '';
$statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]';
$syntaxType = $this->document->fileType->name;
$isDirty = $this->document->dirty ? '(modified)' : '';
$status = sprintf('%.20s - %d lines %s', $statusFilename, $this->document->numRows, $isDirty);
$rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursor->y + 1, $this->document->numRows);
$len = strlen($status);
@ -784,7 +664,7 @@ class Editor {
protected function processKeypress(): void
{
$c = $this->readKey();
$c = Terminal::readKey();
if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
{
@ -805,17 +685,16 @@ class Editor {
$this->find();
break;
case KeyType::ENTER:
$this->insertNewline();
case KeyType::DEL_KEY:
$this->document->delete($this->cursor);
break;
case KeyType::BACKSPACE:
case KeyType::DEL_KEY:
if ($c === KeyType::DEL_KEY)
if ($this->cursor->x > 0 || $this->cursor->y > 0)
{
$this->moveCursor(KeyType::ARROW_RIGHT);
$this->moveCursor(KeyType::ARROW_LEFT);
$this->document->delete($this->cursor);
}
$this->deleteChar();
break;
case KeyType::ARROW_UP:

View File

@ -18,7 +18,6 @@ class KeyType {
public const BACKSPACE = 'BACKSPACE';
public const DEL_KEY = 'DELETE';
public const END_KEY = 'END';
public const ENTER = 'ENTER';
public const ESCAPE = 'ESCAPE';
public const HOME_KEY = 'HOME';
public const PAGE_DOWN = 'PAGE_DOWN';

View File

@ -56,7 +56,7 @@ class Row {
if ($name === 'chars')
{
$this->chars = $value;
$this->update();
$this->highlight();
}
}
@ -77,30 +77,30 @@ class Row {
];
}
public function insertChar(int $at, string $c): void
public function insert(int $at, string $c): void
{
if ($at < 0 || $at > $this->size)
{
$this->appendString($c);
$this->append($c);
return;
}
// Safely insert into arbitrary position in the existing string
$this->chars = substr($this->chars, 0, $at) . $c . substr($this->chars, $at);
$this->update();
$this->highlight();
$this->parent->dirty = true;
}
public function appendString(string $s): void
public function append(string $s): void
{
$this->chars .= $s;
$this->update();
$this->highlight();
$this->parent->dirty = true;
}
public function deleteChar(int $at): void
public function delete(int $at): void
{
if ($at < 0 || $at >= $this->size)
{
@ -108,28 +108,22 @@ class Row {
}
$this->chars = substr_replace($this->chars, '', $at, 1);
$this->update();
$this->highlight();
$this->parent->dirty = true;
}
public function highlight(): void
{
// $this->update();
$this->highlightGeneral();
}
public function update(): void
{
$this->render = tabs_to_spaces($this->chars);
$this->highlight();
}
// ------------------------------------------------------------------------
// ! Syntax Highlighting
// ------------------------------------------------------------------------
public function highlightGeneral(): void
public function highlight(): void
{
$this->render = tabs_to_spaces($this->chars);
$this->highlightGeneral();
}
protected function highlightGeneral(): void
{
$this->hl = array_fill(0, $this->rsize, Highlight::NORMAL);
@ -279,7 +273,7 @@ class Row {
if ($changed && $this->idx + 1 < $this->parent->numRows)
{
// @codeCoverageIgnoreStart
$this->parent->rows[$this->idx + 1]->updateSyntax();
$this->parent->rows[$this->idx + 1]->highlight();
// @codeCoverageIgnoreEnd
}
}
@ -420,7 +414,7 @@ class Row {
if ($changed && ($this->idx + 1) < $this->parent->numRows)
{
// @codeCoverageIgnoreStart
$this->parent->rows[$this->idx + 1]->updateSyntax();
$this->parent->rows[$this->idx + 1]->highlight();
// @codeCoverageIgnoreEnd
}
}

View File

@ -2,6 +2,8 @@
namespace Aviat\Kilo;
use Aviat\Kilo\Enum\KeyCode;
use Aviat\Kilo\Enum\KeyType;
use Aviat\Kilo\Type\TerminalSize;
class Terminal {
@ -75,6 +77,43 @@ class Terminal {
return (is_string($input)) ? $input : '';
}
/**
* Get the last key input from the terminal and convert to a
* more useful format
*
* @return string
*/
public static function readKey(): string
{
$c = Terminal::read();
return match($c)
{
// Unambiguous mappings
KeyCode::ARROW_DOWN => KeyType::ARROW_DOWN,
KeyCode::ARROW_LEFT => KeyType::ARROW_LEFT,
KeyCode::ARROW_RIGHT => KeyType::ARROW_RIGHT,
KeyCode::ARROW_UP => KeyType::ARROW_UP,
KeyCode::DEL_KEY => KeyType::DEL_KEY,
KeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
KeyCode::PAGE_UP => KeyType::PAGE_UP,
// Backspace
KeyCode::CTRL('h'), KeyCode::BACKSPACE => KeyType::BACKSPACE,
// Escape
KeyCode::CTRL('l'), KeyCode::ESCAPE => KeyType::ESCAPE,
// Home Key
"\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::HOME_KEY,
// End Key
"\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::END_KEY,
default => $c,
};
}
/**
* Write to the stdout stream
*