Finish chapter 6, adding incremental search
This commit is contained in:
parent
6d0074ffd3
commit
0b66606e6c
2
kilo
2
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)
|
||||
|
135
src/Editor.php
135
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:
|
||||
|
Loading…
Reference in New Issue
Block a user