diff --git a/.gitignore b/.gitignore index e19d8cb..e4479f2 100644 --- a/.gitignore +++ b/.gitignore @@ -169,5 +169,6 @@ tags # End of https://www.gitignore.io/api/vim,emacs,composer,jetbrains+all kilo.log +kilo.exception.log .phpunit.result.cache coverage/ diff --git a/kilo b/kilo index 4f91242..daa5c7d 100755 --- a/kilo +++ b/kilo @@ -31,16 +31,31 @@ return (static function (int $argc, array $argv): int { $editor->setStatusMessage('HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find'); - // Input Loop - while (true) + try { - $editor->refreshScreen(); - $char = $editor->processKeypress(); - if ($char === NULL) + // Input Loop + while (true) { - break; + $editor->refreshScreen(); + $char = $editor->processKeypress(); + if ($char === NULL) + { + break; + } } } + catch (\Throwable $e) + { + $msg = print_r([ + 'code' => $e->getCode(), + 'message' => $e->getMessage(), + 'file' => $e->getFile(), + 'line' => $e->getLine(), + 'trace' => $e->getTraceAsString(), + ], TRUE); + file_put_contents('kilo.exception.log', $msg, FILE_APPEND); + } return 0; })($argc, $argv); + diff --git a/src/Editor.php b/src/Editor.php index f569760..88dfc5a 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -88,42 +88,38 @@ class Editor { { $c = read_stdin(); - $simpleMap = [ + $map = [ + // 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::BACKSPACE => KeyType::BACKSPACE, KeyCode::DEL_KEY => KeyType::DEL_KEY, KeyCode::ENTER => KeyType::ENTER, KeyCode::ESCAPE => KeyType::ESCAPE, KeyCode::PAGE_DOWN => KeyType::PAGE_DOWN, KeyCode::PAGE_UP => KeyType::PAGE_UP, - ]; - $multiMap = [ + // Backspace + KeyCode::CTRL('h') => KeyType::BACKSPACE, + KeyCode::BACKSPACE => KeyType::BACKSPACE, + + // Home Key "\eOH" => KeyType::HOME_KEY, "\e[1~" => KeyType::HOME_KEY, "\e[7~" => KeyType::HOME_KEY, ANSI::RESET_CURSOR => KeyType::HOME_KEY, + // End Key "\eOF" => KeyType::END_KEY, "\e[4~" => KeyType::END_KEY, "\e[8~" => KeyType::END_KEY, "\e[F" => KeyType::END_KEY, ]; - if (array_key_exists($c, $simpleMap)) - { - return $simpleMap[$c]; - } - - if (array_key_exists($c, $multiMap)) - { - return $multiMap[$c]; - } - - return $c; + return (array_key_exists($c, $map)) + ? $map[$c] + : $c; } protected function selectSyntaxHighlight(): void @@ -448,18 +444,20 @@ class Editor { $current = 0; } - $match = strpos($this->rows[$current]->render, $query); + $row =& $this->rows[$current]; + + $match = strpos($row->render, $query); if ($match !== FALSE) { $lastMatch = $current; $this->cursorY = $current; - $this->cursorX = $this->rowRxToCx($this->rows[$current], $match); + $this->cursorX = $this->rowRxToCx($row, $match); $this->rowOffset = $this->numRows; $savedHlLine = $current; - $savedHl = $this->rows[$current]->hl; + $savedHl = $row->hl; // Update the highlight array of the relevant row with the 'MATCH' type - array_replace_range($this->rows[$current]->hl, $match, strlen($query), Highlight::MATCH); + array_replace_range($row->hl, $match, strlen($query), Highlight::MATCH); break; } @@ -710,6 +708,7 @@ class Editor { $this->refreshScreen(); $c = $this->readKey(); + $isModifier = in_array($c, $modifiers, TRUE); if ($c === KeyType::ESCAPE || ($c === KeyType::ENTER && $buffer !== '')) { @@ -721,11 +720,11 @@ class Editor { return ''; } - if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE || KeyType::CTRL('h')) + if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE) { $buffer = substr($buffer, 0, -1); } - else if (is_ascii($c) && ( ! is_ctrl($c)) && ! in_array($c, $modifiers, TRUE)) + else if (is_ascii($c) && ( ! (is_ctrl($c) || $isModifier))) { $buffer .= $c; } @@ -811,7 +810,7 @@ class Editor { $this->insertNewline(); break; - case KeyType::CTRL('q'): + case KeyCode::CTRL('q'): if ($this->dirty > 0 && $quit_times > 0) { $this->setStatusMessage('WARNING!!! File has unsaved changes.' . @@ -824,7 +823,7 @@ class Editor { return NULL; break; - case KeyType::CTRL('s'): + case KeyCode::CTRL('s'): $this->save(); break; @@ -839,12 +838,11 @@ class Editor { } break; - case KeyType::CTRL('f'): + case KeyCode::CTRL('f'): $this->find(); break; case KeyType::BACKSPACE: - case KeyType::CTRL('h'): case KeyType::DEL_KEY: if ($c === KeyType::DEL_KEY) { @@ -865,7 +863,7 @@ class Editor { $this->moveCursor($c); break; - case KeyType::CTRL('l'): + case KeyCode::CTRL('l'): case KeyType::ESCAPE: // Do nothing break; diff --git a/src/Enum/KeyCode.php b/src/Enum/KeyCode.php index 5565c24..5fd94df 100644 --- a/src/Enum/KeyCode.php +++ b/src/Enum/KeyCode.php @@ -3,6 +3,7 @@ namespace Aviat\Kilo\Enum; use Aviat\Kilo\Traits; +use function Aviat\Kilo\ctrl_key; /** * 'Raw' input from stdin @@ -28,4 +29,28 @@ class KeyCode { public const SPACE = ' '; public const TAB = "\t"; public const VERTICAL_TAB = "\v"; + + /** + * Returns the ascii character for the specified + * ctrl + letter combo + * + * @param string $char + * @return string + */ + public static function CTRL(string $char): ?string + { + $char = strtolower($char); + $ord = ord($char); + + // a = 0x61 + // z = 0x7a + // So, 0x60 < $ord < 0x7b + if ($ord > 0x60 && $ord < 0x7b) + { + return chr(ctrl_key($char)); + } + + // Invalid input, not an ascii letter + return NULL; + } } diff --git a/src/Enum/KeyType.php b/src/Enum/KeyType.php index 235771f..ef1fd90 100644 --- a/src/Enum/KeyType.php +++ b/src/Enum/KeyType.php @@ -3,7 +3,6 @@ namespace Aviat\Kilo\Enum; use Aviat\Kilo\Traits; -use function Aviat\Kilo\ctrl_key; /** * Constants representing various control keys @@ -23,28 +22,4 @@ class KeyType { public const HOME_KEY = 'HOME'; public const PAGE_DOWN = 'PAGE_DOWN'; public const PAGE_UP = 'PAGE_UP'; - - /** - * Returns the ascii character for the specified - * ctrl + letter combo - * - * @param string $char - * @return string - */ - public static function CTRL(string $char): ?string - { - $char = strtolower($char); - $ord = ord($char); - - // a = 0x61 - // z = 0x7a - // So, 0x60 < $ord < 0x7b - if ($ord > 0x60 && $ord < 0x7b) - { - return chr(ctrl_key($char)); - } - - // Invalid input, not an ascii letter - return NULL; - } } \ No newline at end of file diff --git a/tests/Enum/KeyTypeTest.php b/tests/Enum/KeyCodeTest.php similarity index 50% rename from tests/Enum/KeyTypeTest.php rename to tests/Enum/KeyCodeTest.php index c787548..1603e0a 100644 --- a/tests/Enum/KeyTypeTest.php +++ b/tests/Enum/KeyCodeTest.php @@ -2,10 +2,12 @@ namespace Aviat\Kilo\Tests\Enum; -use Aviat\Kilo\Enum\KeyType; +use function Aviat\Kilo\ctrl_key; + +use Aviat\Kilo\Enum\KeyCode; use PHPUnit\Framework\TestCase; -class KeyTypeTest extends TestCase { +class KeyCodeTest extends TestCase { public function testSanityCheck(): void { for ($i = 1; $i < 27; $i++) @@ -14,21 +16,22 @@ class KeyTypeTest extends TestCase { $ord = $i; $expected = chr($ord); - $actual = KeyType::CTRL($char); + $actual = KeyCode::CTRL($char); - $this->assertEquals($expected, $actual, "CTRL + '{$char}' should return chr($ord)"); + $this->assertEquals(ctrl_key($char), $ord, "chr(ctrl_key) !== CTRL"); + $this->assertEquals($expected, $actual, "CTRL+'{$char}' should return chr($ord)"); } } public function testNullOnInvalidChar(): void { - $this->assertNull(KeyType::CTRL("\t")); + $this->assertNull(KeyCode::CTRL("\t")); } public function testSameOutputOnUpperOrLower(): void { - $lower = KeyType::CTRL('v'); - $upper = KeyType::CTRL('V'); + $lower = KeyCode::CTRL('v'); + $upper = KeyCode::CTRL('V'); $this->assertEquals($lower, $upper); }