diff --git a/kilo b/kilo index 2b8ca1b..cde26ce 100755 --- a/kilo +++ b/kilo @@ -20,7 +20,7 @@ function main(int $argc, array $argv): int $editor->open($argv[1]); } - $editor->setStatusMessage('HELP: Ctrl-S = save | Ctrl-Q = quit'); + $editor->setStatusMessage('HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find'); // Input Loop while (true) diff --git a/src/Editor.php b/src/Editor.php index e8fdbba..6783f23 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -34,6 +34,19 @@ class Key { public const HOME_KEY = 'HOME'; public const PAGE_DOWN = 'PAGE_DOWN'; public const PAGE_UP = 'PAGE_UP'; + + public static function getConstList(): array + { + static $self; + + if ($self === NULL) + { + $class = static::class; + $self = new $class; + } + + return (new \ReflectionClass($self))->getConstants(); + } } /** @@ -259,6 +272,26 @@ class Editor { return $rx; } + protected function rowRxToCx(Row $row, int $rx): int + { + $cur_rx = 0; + for ($cx = 0; $cx < $row->size; $cx++) + { + if ($row->chars[$cx] === "\t") + { + $cur_rx += (KILO_TAB_STOP - 1) - ($cur_rx % KILO_TAB_STOP); + } + $cur_rx++; + + if ($cur_rx > $rx) + { + return $cx; + } + } + + return $cx; + } + protected function insertRow(int $at, string $s): void { if ($at < 0 || $at > $this->numRows) @@ -468,6 +501,86 @@ class Editor { $this->setStatusMessage('Failed to save! I/O error: %s', error_get_last()['message']); } + // ------------------------------------------------------------------------ + // ! Find + // ------------------------------------------------------------------------ + + protected function findCallback(string $query, string $key): void + { + static $lastMatch = -1; + static $direction = 1; + + if ($key === "\r" || $key === "\x1b") + { + $lastMatch = -1; + $direction = 1; + return; + } + + if ($key === Key::ARROW_RIGHT || $key === Key::ARROW_DOWN) + { + $direction = 1; + } + else if ($key === Key::ARROW_LEFT || $key === Key::ARROW_UP) + { + $direction = -1; + } + else + { + $lastMatch = -1; + $direction = 1; + } + + if ($lastMatch === -1) + { + $direction = 1; + } + $current = $lastMatch; + + for ($i = 0; $i < $this->numRows; $i++) + { + $current += $direction; + if ($current === -1) + { + $current = $this->numRows - 1; + } + else if ($current === $this->numRows) + { + $current = 0; + } + + $match = strpos($this->rows[$current]->render, $query); + if ($match !== FALSE) + { + $lastMatch = $current; + $this->cursorY = $current; + $this->cursorX = $this->rowRxToCx($this->rows[$current], $match); + $this->rowOffset = $this->numRows; + break; + } + } + } + + protected function find(): void + { + $savedCx = $this->cursorX; + $savedCy = $this->cursorY; + $savedColOff = $this->colOffset; + $savedRowOff = $this->rowOffset; + + $query = $this->prompt('Search: %s (Use ESC/Arrows/Enter)', [$this, 'findCallback']); + + // If they pressed escape, the query will be empty, + // restore original cursor and scroll locations + if ($query === '') + { + $this->cursorX = $savedCx; + $this->cursorY = $savedCy; + $this->colOffset = $savedColOff; + $this->rowOffset = $savedRowOff; + } + } + // ------------------------------------------------------------------------ // ! Output // ------------------------------------------------------------------------ @@ -636,9 +749,10 @@ class Editor { // ! Input // ------------------------------------------------------------------------ - protected function prompt(string $prompt): string + protected function prompt(string $prompt, ?callable $callback = NULL): string { $buffer = ''; + $modifiers = Key::getConstList(); while (TRUE) { $this->setStatusMessage($prompt, $buffer); @@ -650,12 +764,20 @@ class Editor { if ($c === Key::ESCAPE) { $this->setStatusMessage(''); + if ($callback !== NULL) + { + $callback($buffer, $c); + } return ''; } if ($c === Key::ENTER && $buffer !== '') { $this->setStatusMessage(''); + if ($callback !== NULL) + { + $callback($buffer, $c); + } return $buffer; } @@ -663,10 +785,15 @@ class Editor { { $buffer = substr($buffer, 0, -1); } - else if ($cord < 128 && $this->ffi->iscntrl($cord) === 0) + else if ($cord < 128 && $this->ffi->iscntrl($cord) === 0 && ! in_array($c, $modifiers, TRUE)) { $buffer .= $c; } + + if ($callback !== NULL) + { + $callback($buffer, $c); + } } } @@ -772,6 +899,10 @@ class Editor { } break; + case chr(ctrl_key('f')): + $this->find(); + break; + case Key::BACKSPACE: case chr(ctrl_key('h')): case Key::DEL_KEY: