diff --git a/kilo b/kilo index 304f9f8..b46843b 100755 --- a/kilo +++ b/kilo @@ -13,24 +13,25 @@ set_error_handler(static function ( $errline ) { $msg = print_r([ - 'code' => $errno, + 'code' => error_code_name($errno), 'message' => $errstr, 'file' => $errfile, 'line' => $errline, ], TRUE); - file_put_contents('error.log', $msg, FILE_APPEND); + file_put_contents('kilo.log', $msg, FILE_APPEND); return true; }, -1); set_exception_handler(static function (mixed $e) { $msg = print_r([ 'code' => $e->getCode(), + 'codeName' => error_code_name($e->getCode()), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString(), ], TRUE); - file_put_contents('exception.log', $msg, FILE_APPEND); + file_put_contents('kilo.log', $msg, FILE_APPEND); }); // ! Init with an IIFE diff --git a/src/Document.php b/src/Document.php index 737bfd7..5b74987 100644 --- a/src/Document.php +++ b/src/Document.php @@ -2,6 +2,7 @@ namespace Aviat\Kilo; +use Aviat\Kilo\Enum\KeyCode; use Aviat\Kilo\Tokens\PHP8; use Aviat\Kilo\Type\Point; @@ -39,13 +40,13 @@ class Document { return new self(); } - protected function rowsToString(): string + public function row(int $index): ?Row { - $lines = array_map(fn (Row $row) => (string)$row, $this->rows); - - return implode('', $lines); + return (array_key_exists($index, $this->rows)) ? $this->rows[$index] : null; } + + // ------------------------------------------------------------------------ // ! File I/O // ------------------------------------------------------------------------ @@ -97,11 +98,13 @@ class Document { $this->dirty = true; - if ($c === "\n") + if ($c === KeyCode::ENTER || $c === KeyCode::CARRIAGE_RETURN) { $this->insertNewline($at); + return; } - else if ($at->y === $this->numRows) + + if ($at->y === $this->numRows) { $this->insertRow($this->numRows, ''); } @@ -219,14 +222,14 @@ class Document { $newChars = substr($chars, 0, $at->x); // Truncate the previous row - $row->chars = $newChars; + $row->chars = $newChars; // 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)); } } - public function selectSyntaxHighlight(): void + protected function selectSyntaxHighlight(): void { if (empty($this->filename)) { @@ -241,6 +244,13 @@ class Document { $this->refreshSyntax(); } + protected function rowsToString(): string + { + $lines = array_map(fn (Row $row) => (string)$row, $this->rows); + + return implode('', $lines); + } + public function refreshSyntax(): void { // Update the syntax highlighting for all the rows of the file diff --git a/src/Editor.php b/src/Editor.php index e55843d..3125732 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -97,6 +97,10 @@ class Editor { $this->document = $maybeDocument; } } + else + { + $this->document = Document::new(); + } } public function __debugInfo(): array @@ -230,7 +234,13 @@ class Editor { if ( ! empty($savedHl)) { - $this->document->rows[$savedHlLine]->hl = $savedHl; + $row = $this->document->row($savedHlLine); + + if ($row !== null) + { + $row->hl = $savedHl; + } + $savedHl = []; } @@ -281,7 +291,11 @@ class Editor { $current = 0; } - $row =& $this->document->rows[$current]; + $row = $this->document->row($current); + if ($row === null) + { + break; + } $match = strpos($row->render, $query); if ($match !== FALSE) @@ -326,7 +340,13 @@ class Editor { $this->renderX = 0; if ($this->cursor->y < $this->document->numRows) { - $this->renderX = $this->rowCxToRx($this->document->rows[$this->cursor->y], $this->cursor->x); + $row = $this->document->row($this->cursor->y); + + if ($row !== null) + { + $this->renderX = $this->rowCxToRx($row, $this->cursor->x); + } + } // Vertical Scrolling @@ -368,7 +388,13 @@ class Editor { protected function drawRow(int $rowIdx): void { - $len = $this->document->rows[$rowIdx]->rsize - $this->offset->x; + $row = $this->document->row($rowIdx); + if ($row === null) + { + return; + } + + $len = $row->rsize - $this->offset->x; if ($len < 0) { $len = 0; @@ -378,8 +404,8 @@ class Editor { $len = $this->terminalSize->cols; } - $chars = substr($this->document->rows[$rowIdx]->render, $this->offset->x, (int)$len); - $hl = array_slice($this->document->rows[$rowIdx]->hl, $this->offset->x, (int)$len); + $chars = substr($row->render, $this->offset->x, (int)$len); + $hl = array_slice($row->hl, $this->offset->x, (int)$len); $currentColor = -1; @@ -464,7 +490,7 @@ class Editor { $statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]'; $syntaxType = $this->document->fileType->name; - $isDirty = $this->document->dirty ? '(modified)' : ''; + $isDirty = $this->document->isDirty() ? '(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); @@ -492,12 +518,14 @@ class Editor { protected function drawMessageBar(): void { $this->outputBuffer .= ANSI::CLEAR_LINE; - $len = strlen($this->statusMessage->text); + $len = $this->statusMessage->len; if ($len > $this->terminalSize->cols) { $len = $this->terminalSize->cols; } + // If there is a message, and it's been less than 5 seconds since + // last screen update, show the message if ($len > 0 && (time() - $this->statusMessage->time) < 5) { $this->outputBuffer .= substr($this->statusMessage->text, 0, $len); @@ -530,7 +558,11 @@ class Editor { public function setStatusMessage(string $fmt, mixed ...$args): void { - $this->statusMessage = StatusMessage::from($fmt, ...$args); + $text = func_num_args() > 1 + ? sprintf($fmt, ...$args) + : $fmt; + + $this->statusMessage = StatusMessage::from($text); } // ------------------------------------------------------------------------ @@ -546,17 +578,17 @@ class Editor { $this->setStatusMessage($prompt, $buffer); $this->refreshScreen(); - $c = $this->readKey(); + $c = Terminal::readKey(); $isModifier = in_array($c, $modifiers, TRUE); - if ($c === KeyType::ESCAPE || ($c === KeyType::ENTER && $buffer !== '')) + if ($c === KeyType::ESCAPE || ($c === KeyCode::ENTER && $buffer !== '')) { $this->setStatusMessage(''); if ($callback !== NULL) { $callback($buffer, $c); } - return ($c === KeyType::ENTER) ? $buffer : ''; + return ($c === KeyCode::ENTER) ? $buffer : ''; } if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE) @@ -579,7 +611,11 @@ class Editor { { $x = $this->cursor->x; $y = $this->cursor->y; - $row = $this->document->rows[$y]; + $row = $this->document->row($y); + if ($row === NULL) + { + return; + } switch ($key) { @@ -592,16 +628,16 @@ class Editor { { // Beginning of a line, go to end of previous line $y--; - $x = $this->document->rows[$y]->size - 1; + $x = $row->size - 1; } break; case KeyType::ARROW_RIGHT: - if ($row && $x < $row->size) + if ($x < $row->size) { $x++; } - else if ($row && $x === $row->size) + else if ($x === $row->size) { $y++; $x = 0; @@ -641,7 +677,7 @@ class Editor { case KeyType::END_KEY: if ($y < $this->document->numRows) { - $x = $this->document->rows[$y]->size; + $x = $row->size; } break; @@ -651,15 +687,17 @@ class Editor { // Snap cursor to the end of a row when moving // from a longer row to a shorter one - $row = $this->document->rows[$y]; - $rowLen = ($row !== NULL) ? $row->size : 0; - if ($x > $rowLen) + $row = $this->document->row($y); + if ($row !== null) { - $x = $rowLen; - } + if ($x > $row->size) + { + $x = $row->size; + } - $this->cursor->x = $x; - $this->cursor->y = $y; + $this->cursor->x = $x; + $this->cursor->y = $y; + } } protected function processKeypress(): void @@ -713,6 +751,10 @@ class Editor { // Do nothing break; + case KeyType::ENTER: + $this->insertChar("\n"); + break; + default: $this->insertChar($c); break; @@ -728,7 +770,7 @@ class Editor { protected function quitAttempt(): void { - if ($this->document->dirty && $this->quitTimes > 0) + if ($this->document->isDirty() && $this->quitTimes > 0) { $this->setStatusMessage( 'WARNING!!! File has unsaved changes. Press Ctrl-Q %d more times to quit.', diff --git a/src/Enum/KeyCode.php b/src/Enum/KeyCode.php index 3d2fdc0..faa0806 100644 --- a/src/Enum/KeyCode.php +++ b/src/Enum/KeyCode.php @@ -17,6 +17,7 @@ class KeyCode { public const ARROW_RIGHT = "\e[C"; public const ARROW_UP = "\e[A"; public const BACKSPACE = "\x7f"; + public const BELL = "\a"; public const CARRIAGE_RETURN = "\r"; public const DEL_KEY = "\e[3~"; public const EMPTY = ''; diff --git a/src/Enum/KeyType.php b/src/Enum/KeyType.php index 65540ea..25cbbe6 100644 --- a/src/Enum/KeyType.php +++ b/src/Enum/KeyType.php @@ -11,15 +11,16 @@ use Aviat\Kilo\Traits; class KeyType { use Traits\ConstList; - public const ARROW_DOWN = 'ARROW_DOWN'; - public const ARROW_LEFT = 'ARROW_LEFT'; - public const ARROW_RIGHT = 'ARROW_RIGHT'; - public const ARROW_UP = 'ARROW_UP'; - public const BACKSPACE = 'BACKSPACE'; - public const DEL_KEY = 'DELETE'; - public const END_KEY = 'END'; - public const ESCAPE = 'ESCAPE'; - public const HOME_KEY = 'HOME'; - public const PAGE_DOWN = 'PAGE_DOWN'; - public const PAGE_UP = 'PAGE_UP'; + public const ARROW_DOWN = 'KEY_ARROW_DOWN'; + public const ARROW_LEFT = 'KEY_ARROW_LEFT'; + public const ARROW_RIGHT = 'KEY_ARROW_RIGHT'; + public const ARROW_UP = 'KEY_ARROW_UP'; + public const BACKSPACE = 'KEY_BACKSPACE'; + public const DEL_KEY = 'KEY_DELETE'; + public const END_KEY = 'KEY_END'; + public const ENTER = 'KEY_ENTER'; + public const ESCAPE = 'KEY_ESCAPE'; + public const HOME_KEY = 'KEY_HOME'; + public const PAGE_DOWN = 'KEY_PAGE_DOWN'; + public const PAGE_UP = 'KEY_PAGE_UP'; } \ No newline at end of file diff --git a/src/FileType.php b/src/FileType.php index 149fab0..2436f44 100644 --- a/src/FileType.php +++ b/src/FileType.php @@ -13,15 +13,14 @@ class FileType { private static function getSyntaxFromFilename(string $filename): Syntax { - $ext = (string)strstr(basename($filename), '.'); + $ext = strstr(basename($filename), '.'); + $ext = ($ext !== FALSE) ? $ext : ''; return match ($ext) { '.php', 'kilo' => Syntax::new( 'PHP', - ['.php', 'kilo'], ), '.c', '.h', '.cpp', '.cxx', '.cc', '.hpp' => Syntax::new( 'C', - ['.c', '.h', '.cpp'], [ 'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct', 'union', 'class', 'else', 'enum', 'for', 'case', 'if', @@ -33,12 +32,10 @@ class FileType { ), '.css', '.less', '.sass', '.scss' => Syntax::new( 'CSS', - ['.css', '.less', '.sass', 'scss'], slcs: '', ), '.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es' => Syntax::new( 'JavaScript', - ['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'], [ 'instanceof', 'continue', 'debugger', 'function', 'default', 'extends', 'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof', @@ -52,7 +49,6 @@ class FileType { ), '.rs' => Syntax::new( 'Rust', - ['.rs'], [ 'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate', 'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl', diff --git a/src/Syntax.php b/src/Syntax.php index 0569c36..5f2f933 100644 --- a/src/Syntax.php +++ b/src/Syntax.php @@ -11,7 +11,6 @@ class Syntax { public static function new( string $name, - array $extList = [], array $keywords1 = [], array $keywords2 = [], string $slcs = '//', @@ -20,7 +19,7 @@ class Syntax { int $flags = self::HIGHLIGHT_NUMBERS | self::HIGHLIGHT_STRINGS, ): self { - return new self($name, $extList, $keywords1, $keywords2, $slcs, $mcs, $mce, $flags); + return new self($name, $keywords1, $keywords2, $slcs, $mcs, $mce, $flags); } public static function default(): self @@ -31,8 +30,6 @@ class Syntax { private function __construct( /** The name of the programming language */ public string $filetype, - /** Relevant file extensions for the specified language */ - public array $filematch, /** Primary set of language keywords */ public array $keywords1, /** Secondary set of language keywords */ diff --git a/src/Terminal.php b/src/Terminal.php index 963853c..25bb978 100644 --- a/src/Terminal.php +++ b/src/Terminal.php @@ -95,6 +95,7 @@ class Terminal { 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, diff --git a/src/Type/StatusMessage.php b/src/Type/StatusMessage.php index 8963fac..45426b4 100644 --- a/src/Type/StatusMessage.php +++ b/src/Type/StatusMessage.php @@ -5,11 +5,12 @@ namespace Aviat\Kilo\Type; class StatusMessage { private function __construct( public string $text, + public int $len, public int $time, ) {} - public static function from(string $text, mixed ...$args): self + public static function from(string $text): self { - return new self(sprintf($text, ...$args), time()); + return new self($text, strlen($text), time()); } } \ No newline at end of file diff --git a/src/functions.php b/src/functions.php index 9dbd510..9893a86 100644 --- a/src/functions.php +++ b/src/functions.php @@ -187,3 +187,24 @@ function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string { return str_replace(KeyCode::TAB, str_repeat(KeyCode::SPACE, $number), $str); } + +function error_code_name(int $code): string +{ + return match ($code) { + E_ERROR => 'Error', + E_WARNING => 'Warning', + E_PARSE => 'Parse Error', + E_NOTICE => 'Notice', + E_CORE_ERROR => 'Core Error', + E_CORE_WARNING => 'Core Warning', + E_COMPILE_ERROR => 'Compile Error', + E_COMPILE_WARNING => 'Compile Warning', + E_USER_ERROR => 'User Error', + E_USER_WARNING => 'User Warning', + E_USER_NOTICE => 'User Notice', + E_RECOVERABLE_ERROR => 'Recoverable Error', + E_DEPRECATED => 'Deprecated', + E_USER_DEPRECATED => 'User Deprecated', + default => 'Unknown', + }; +} \ No newline at end of file diff --git a/tests/EditorTest.php b/tests/EditorTest.php index 81214a7..1875b33 100644 --- a/tests/EditorTest.php +++ b/tests/EditorTest.php @@ -29,14 +29,6 @@ class MockEditor extends Editor { class EditorTest extends TestCase { use MatchesSnapshots; - public function testSanity(): void - { - $editor = MockEditor::mock(); - - $this->assertEquals(0, $editor->numRows); - $this->assertNull($editor->syntax); - } - public function test__debugInfo(): void { $editor = MockEditor::mock(); diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index 06f9246..3921475 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase; use function Aviat\Kilo\array_replace_range; use function Aviat\Kilo\ctrl_key; -use function Aviat\Kilo\get_file_syntax_map; use function Aviat\Kilo\is_ascii; use function Aviat\Kilo\is_ctrl; use function Aviat\Kilo\is_digit; @@ -97,11 +96,6 @@ class FunctionTest extends TestCase { $this->assertNotEquals(syntax_to_color(Highlight::OPERATOR), Color::FG_WHITE); } - public function test_get_file_syntax_map(): void - { - $this->assertNotEmpty(get_file_syntax_map()); - } - public function test_str_contains(): void { // Search from string offset diff --git a/tests/RowTest.php b/tests/RowTest.php index 9466dfc..b073e78 100644 --- a/tests/RowTest.php +++ b/tests/RowTest.php @@ -2,19 +2,22 @@ namespace Aviat\Kilo\Tests\Traits; -use Aviat\Kilo\{Editor, Row}; +use Aviat\Kilo\ +{ + Document, + Row}; use PHPUnit\Framework\TestCase; class RowTest extends TestCase { - protected Editor $editor; + protected Document $document; protected Row $row; public function setUp(): void { parent::setUp(); - $this->editor = Editor::new(); - $this->row = Row::new($this->editor, '', 0); + $this->document = Document::new(); + $this->row = Row::new($this->document, '', 0); } public function testSanity(): void @@ -53,42 +56,42 @@ class RowTest extends TestCase { $this->assertEquals($expected, $actual); } - public function testInsertChar(): void + public function testInsert(): void { $this->row->chars = 'abde'; - $this->row->insertChar(2, 'c'); + $this->row->insert(2, 'c'); $this->assertEquals('abcde', $this->row->chars); $this->assertEquals('abcde', $this->row->render); - $this->assertEquals(true, $this->editor->dirty); + $this->assertEquals(true, $this->document->dirty); } - public function testInsertCharBadOffset(): void + public function testInsertBadOffset(): void { $this->row->chars = 'ab'; - $this->row->insertChar(5, 'c'); + $this->row->insert(5, 'c'); $this->assertEquals('abc', $this->row->chars); $this->assertEquals('abc', $this->row->render); - $this->assertEquals(true, $this->editor->dirty); + $this->assertEquals(true, $this->document->dirty); } - public function testDeleteChar(): void + public function testDelete(): void { $this->row->chars = 'abcdef'; - $this->row->deleteChar(5); + $this->row->delete(5); $this->assertEquals('abcde', $this->row->chars); $this->assertEquals('abcde', $this->row->render); - $this->assertEquals(true, $this->editor->dirty); + $this->assertEquals(true, $this->document->dirty); } - public function testDeleteCharBadOffset(): void + public function testDeleteBadOffset(): void { $this->row->chars = 'ab'; - $this->row->deleteChar(5); + $this->row->delete(5); $this->assertEquals('ab', $this->row->chars); - $this->assertEquals(false, $this->editor->dirty); + $this->assertEquals(false, $this->document->dirty); } } \ No newline at end of file