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->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)
|
||||||
|
135
src/Editor.php
135
src/Editor.php
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user