diff --git a/README.md b/README.md index 22b0b2e..8f7735d 100644 --- a/README.md +++ b/README.md @@ -7,4 +7,6 @@ due to requiring the `FFI` extension. * The `editor` prefix has been removed from all the relevant functions, instead they are methods on the `Editor` class. * Enums are faked with class constants +* Properties that must be manually updated in the C version (like counts/string length) are implemented with magic methods, +so they are essentially calculated on read. * Generally, if a function exists in PHP, with the same name as the C function, the PHP version will be used. \ No newline at end of file diff --git a/src/Editor.php b/src/Editor.php index a21c093..90b5529 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -27,7 +27,7 @@ class Key { public const ARROW_RIGHT = 'ARROW_RIGHT'; public const ARROW_UP = 'ARROW_UP'; public const BACKSPACE = 'BACKSPACE'; - public const DEL_KEY = 'DEL'; + public const DEL_KEY = 'DELETE'; public const END_KEY = 'END'; public const ENTER = 'ENTER'; public const ESCAPE = 'ESCAPE'; @@ -70,6 +70,27 @@ class Row { return NULL; } } + + public function update(): void + { + $idx = 0; + + for ($i = 0; $i < $this->size; $i++) + { + if ($this->chars[$i] === "\t") + { + $this->render[$idx++] = ' '; + while ($idx % KILO_TAB_STOP !== 0) + { + $this->render[$idx++] = ' '; + } + } + else + { + $this->render[$idx++] = $this->chars[$i]; + } + } + } } /** @@ -233,32 +254,29 @@ class Editor { return $rx; } - protected function updateRow(Row $row): void + protected function insertRow(int $at, string $s): void { - $idx = 0; - - for ($i = 0; $i < $row->size; $i++) + if ($at < 0 || $at > $this->numRows) { - if ($row->chars[$i] === "\t") - { - $row->render[$idx++] = ' '; - while ($idx % KILO_TAB_STOP !== 0) - { - $row->render[$idx++] = ' '; - } - } - else - { - $row->render[$idx++] = $row->chars[$i]; - } + return; } - } - protected function appendRow(string $s): void - { - $at = $this->numRows; - $this->rows[$at] = Row::new($s); - $this->updateRow($this->rows[$at]); + $row = Row::new($s); + + if ($at === $this->numRows) + { + $this->rows[] = $row; + } + else + { + $this->rows = [ + ...array_slice($this->rows, 0, $at), + $row, + ...array_slice($this->rows, $at), + ]; + } + + $this->rows[$at]->update(); $this->dirty++; } @@ -288,15 +306,14 @@ class Editor { // Safely insert into arbitrary position in the existing string $row->chars = substr($row->chars, 0, $at) . $c . substr($row->chars, $at); - - $this->updateRow($row); + $row->update(); $this->dirty++; } protected function rowAppendString(Row $row, string $s): void { $row->chars .= $s; - $this->updateRow($row); + $row->update(); $this->dirty++; } @@ -308,7 +325,7 @@ class Editor { } $row->chars = substr_replace($row->chars, '', $at, 1); - $this->updateRow($row); + $row->update(); $this->dirty++; } @@ -320,12 +337,34 @@ class Editor { { if ($this->cursorY === $this->numRows) { - $this->appendRow(''); + $this->insertRow($this->numRows, ''); } $this->rowInsertChar($this->rows[$this->cursorY], $this->cursorX, $c); $this->cursorX++; } + protected function insertNewline(): void + { + if ($this->cursorX === 0) + { + $this->insertRow($this->cursorY, ''); + } + else + { + $row = $this->rows[$this->cursorY]; + + // Add a new row, with the contents from the cursor to the end of the line + $this->insertRow($this->cursorY + 1, substr($row->chars, $this->cursorX)); + + // Update the (now previous) row + $row->chars = substr($row->chars, 0, $this->cursorX); + $row->update(); + } + + $this->cursorY++; + $this->cursorX = 0; + } + protected function deleteChar(): void { if ($this->cursorY === $this->numRows || ($this->cursorX === 0 && $this->cursorY === 0)) @@ -377,11 +416,17 @@ class Editor { // #TODO gracefully handle issues with loading a file $handle = fopen($fullname, 'rb'); + if ($handle === FALSE) + { + disableRawMode(); + print_r(error_clear_last()); + die(); + } while (($line = fgets($handle)) !== FALSE) { // Remove line endings when reading the file - $this->appendRow(rtrim($line, "\n\r\0")); + $this->insertRow($this->numRows, rtrim($line)); } fclose($handle); @@ -393,7 +438,12 @@ class Editor { { if ($this->filename === '') { - return; + $this->filename = $this->prompt('Save as: %s'); + if ($this->filename === '') + { + $this->setStatusMessage('Save aborted'); + return; + } } $contents = $this->rowsToString(); @@ -577,6 +627,40 @@ class Editor { // ! Input // ------------------------------------------------------------------------ + protected function prompt(string $prompt): string + { + $buffer = ''; + while (TRUE) + { + $this->setStatusMessage($prompt, $buffer); + $this->refreshScreen(); + + $c = $this->readKey(); + $cord = ord($c); + + if ($c === Key::ESCAPE) + { + $this->setStatusMessage(''); + return ''; + } + + if ($c === Key::ENTER && $buffer !== '') + { + $this->setStatusMessage(''); + return $buffer; + } + + if ($c === Key::DEL_KEY || $c === Key::BACKSPACE || $c === chr(ctrl_key('h'))) + { + $buffer = substr($buffer, 0, -1); + } + else if ($cord < 128 && $this->ffi->iscntrl($cord) === 0) + { + $buffer .= $c; + } + } + } + protected function moveCursor(string $key): void { $row = ($this->cursorY >= $this->numRows) @@ -648,7 +732,7 @@ class Editor { switch ($c) { case Key::ENTER: - // TODO + $this->insertNewline(); break; case chr(ctrl_key('q')): diff --git a/src/functions.php b/src/functions.php index 62aa542..c3b17cc 100644 --- a/src/functions.php +++ b/src/functions.php @@ -57,8 +57,6 @@ function read_stdin(int $len = 128): string { $handle = fopen('php://stdin', 'rb'); $input = fread($handle, $len); - - $input = rtrim($input); fclose($handle); return $input; @@ -87,7 +85,16 @@ function read_stdout(int $len = 128): string return $input; } +/** + * Do bit twiddling to convert a letter into + * its Ctrl-letter equivalent + * + * @param string $char + * @return int + */ function ctrl_key(string $char): int { + // b1,100,001 (a) & b0,011,111 = b0,000,001 + // b1,100,010 (b) & b0,011,111 = b0,000,010 return ord($char) & 0x1f; } \ No newline at end of file