From 85e96264a88b108b8c2d91ab3f569dd88bfef6b3 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Wed, 17 Mar 2021 13:14:16 -0400 Subject: [PATCH] Get syntax highlighting and keyboard natvigation working again...buggy editing functionality --- kilo | 18 +++++- src/Document.php | 123 +++++++++++++++++++++++++---------- src/Editor.php | 149 ++++--------------------------------------- src/Enum/KeyType.php | 1 - src/Row.php | 40 +++++------- src/Terminal.php | 39 +++++++++++ 6 files changed, 176 insertions(+), 194 deletions(-) diff --git a/kilo b/kilo index 79a105d..304f9f8 100755 --- a/kilo +++ b/kilo @@ -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 diff --git a/src/Document.php b/src/Document.php index 9bf3f4f..737bfd7 100644 --- a/src/Document.php +++ b/src/Document.php @@ -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 diff --git a/src/Editor.php b/src/Editor.php index 66e501a..e55843d 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -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: diff --git a/src/Enum/KeyType.php b/src/Enum/KeyType.php index dee5c4e..65540ea 100644 --- a/src/Enum/KeyType.php +++ b/src/Enum/KeyType.php @@ -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'; diff --git a/src/Row.php b/src/Row.php index 3ff9bac..292c543 100644 --- a/src/Row.php +++ b/src/Row.php @@ -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 } } diff --git a/src/Terminal.php b/src/Terminal.php index 0cb9cbf..963853c 100644 --- a/src/Terminal.php +++ b/src/Terminal.php @@ -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 *