Finish chapter 6, adding incremental search

This commit is contained in:
Timothy Warren 2019-10-23 10:36:04 -04:00
parent 6d0074ffd3
commit 0b66606e6c
2 changed files with 134 additions and 3 deletions

2
kilo
View File

@ -20,7 +20,7 @@ function main(int $argc, array $argv): int
$editor->open($argv[1]); $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 // Input Loop
while (true) while (true)

View File

@ -34,6 +34,19 @@ class Key {
public const HOME_KEY = 'HOME'; public const HOME_KEY = 'HOME';
public const PAGE_DOWN = 'PAGE_DOWN'; public const PAGE_DOWN = 'PAGE_DOWN';
public const PAGE_UP = 'PAGE_UP'; 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; 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 protected function insertRow(int $at, string $s): void
{ {
if ($at < 0 || $at > $this->numRows) 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']); $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 // ! Output
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
@ -636,9 +749,10 @@ class Editor {
// ! Input // ! Input
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
protected function prompt(string $prompt): string protected function prompt(string $prompt, ?callable $callback = NULL): string
{ {
$buffer = ''; $buffer = '';
$modifiers = Key::getConstList();
while (TRUE) while (TRUE)
{ {
$this->setStatusMessage($prompt, $buffer); $this->setStatusMessage($prompt, $buffer);
@ -650,12 +764,20 @@ class Editor {
if ($c === Key::ESCAPE) if ($c === Key::ESCAPE)
{ {
$this->setStatusMessage(''); $this->setStatusMessage('');
if ($callback !== NULL)
{
$callback($buffer, $c);
}
return ''; return '';
} }
if ($c === Key::ENTER && $buffer !== '') if ($c === Key::ENTER && $buffer !== '')
{ {
$this->setStatusMessage(''); $this->setStatusMessage('');
if ($callback !== NULL)
{
$callback($buffer, $c);
}
return $buffer; return $buffer;
} }
@ -663,10 +785,15 @@ class Editor {
{ {
$buffer = substr($buffer, 0, -1); $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; $buffer .= $c;
} }
if ($callback !== NULL)
{
$callback($buffer, $c);
}
} }
} }
@ -772,6 +899,10 @@ class Editor {
} }
break; break;
case chr(ctrl_key('f')):
$this->find();
break;
case Key::BACKSPACE: case Key::BACKSPACE:
case chr(ctrl_key('h')): case chr(ctrl_key('h')):
case Key::DEL_KEY: case Key::DEL_KEY: