Complete chapter 5

This commit is contained in:
Timothy Warren 2019-10-22 16:16:28 -04:00
parent 1522544fc9
commit 98a3b8b691
3 changed files with 126 additions and 33 deletions

View File

@ -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. * 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 * 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. * Generally, if a function exists in PHP, with the same name as the C function, the PHP version will be used.

View File

@ -27,7 +27,7 @@ class Key {
public const ARROW_RIGHT = 'ARROW_RIGHT'; public const ARROW_RIGHT = 'ARROW_RIGHT';
public const ARROW_UP = 'ARROW_UP'; public const ARROW_UP = 'ARROW_UP';
public const BACKSPACE = 'BACKSPACE'; public const BACKSPACE = 'BACKSPACE';
public const DEL_KEY = 'DEL'; public const DEL_KEY = 'DELETE';
public const END_KEY = 'END'; public const END_KEY = 'END';
public const ENTER = 'ENTER'; public const ENTER = 'ENTER';
public const ESCAPE = 'ESCAPE'; public const ESCAPE = 'ESCAPE';
@ -70,6 +70,27 @@ class Row {
return NULL; 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; return $rx;
} }
protected function updateRow(Row $row): void protected function insertRow(int $at, string $s): void
{ {
$idx = 0; if ($at < 0 || $at > $this->numRows)
for ($i = 0; $i < $row->size; $i++)
{ {
if ($row->chars[$i] === "\t") return;
{
$row->render[$idx++] = ' ';
while ($idx % KILO_TAB_STOP !== 0)
{
$row->render[$idx++] = ' ';
}
}
else
{
$row->render[$idx++] = $row->chars[$i];
}
} }
}
protected function appendRow(string $s): void $row = Row::new($s);
{
$at = $this->numRows; if ($at === $this->numRows)
$this->rows[$at] = Row::new($s); {
$this->updateRow($this->rows[$at]); $this->rows[] = $row;
}
else
{
$this->rows = [
...array_slice($this->rows, 0, $at),
$row,
...array_slice($this->rows, $at),
];
}
$this->rows[$at]->update();
$this->dirty++; $this->dirty++;
} }
@ -288,15 +306,14 @@ class Editor {
// Safely insert into arbitrary position in the existing string // Safely insert into arbitrary position in the existing string
$row->chars = substr($row->chars, 0, $at) . $c . substr($row->chars, $at); $row->chars = substr($row->chars, 0, $at) . $c . substr($row->chars, $at);
$row->update();
$this->updateRow($row);
$this->dirty++; $this->dirty++;
} }
protected function rowAppendString(Row $row, string $s): void protected function rowAppendString(Row $row, string $s): void
{ {
$row->chars .= $s; $row->chars .= $s;
$this->updateRow($row); $row->update();
$this->dirty++; $this->dirty++;
} }
@ -308,7 +325,7 @@ class Editor {
} }
$row->chars = substr_replace($row->chars, '', $at, 1); $row->chars = substr_replace($row->chars, '', $at, 1);
$this->updateRow($row); $row->update();
$this->dirty++; $this->dirty++;
} }
@ -320,12 +337,34 @@ class Editor {
{ {
if ($this->cursorY === $this->numRows) if ($this->cursorY === $this->numRows)
{ {
$this->appendRow(''); $this->insertRow($this->numRows, '');
} }
$this->rowInsertChar($this->rows[$this->cursorY], $this->cursorX, $c); $this->rowInsertChar($this->rows[$this->cursorY], $this->cursorX, $c);
$this->cursorX++; $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 protected function deleteChar(): void
{ {
if ($this->cursorY === $this->numRows || ($this->cursorX === 0 && $this->cursorY === 0)) 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 // #TODO gracefully handle issues with loading a file
$handle = fopen($fullname, 'rb'); $handle = fopen($fullname, 'rb');
if ($handle === FALSE)
{
disableRawMode();
print_r(error_clear_last());
die();
}
while (($line = fgets($handle)) !== FALSE) while (($line = fgets($handle)) !== FALSE)
{ {
// Remove line endings when reading the file // Remove line endings when reading the file
$this->appendRow(rtrim($line, "\n\r\0")); $this->insertRow($this->numRows, rtrim($line));
} }
fclose($handle); fclose($handle);
@ -393,7 +438,12 @@ class Editor {
{ {
if ($this->filename === '') if ($this->filename === '')
{ {
return; $this->filename = $this->prompt('Save as: %s');
if ($this->filename === '')
{
$this->setStatusMessage('Save aborted');
return;
}
} }
$contents = $this->rowsToString(); $contents = $this->rowsToString();
@ -577,6 +627,40 @@ class Editor {
// ! Input // ! 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 protected function moveCursor(string $key): void
{ {
$row = ($this->cursorY >= $this->numRows) $row = ($this->cursorY >= $this->numRows)
@ -648,7 +732,7 @@ class Editor {
switch ($c) switch ($c)
{ {
case Key::ENTER: case Key::ENTER:
// TODO $this->insertNewline();
break; break;
case chr(ctrl_key('q')): case chr(ctrl_key('q')):

View File

@ -57,8 +57,6 @@ function read_stdin(int $len = 128): string
{ {
$handle = fopen('php://stdin', 'rb'); $handle = fopen('php://stdin', 'rb');
$input = fread($handle, $len); $input = fread($handle, $len);
$input = rtrim($input);
fclose($handle); fclose($handle);
return $input; return $input;
@ -87,7 +85,16 @@ function read_stdout(int $len = 128): string
return $input; 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 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; return ord($char) & 0x1f;
} }