Fix more things that were broken

This commit is contained in:
Timothy Warren 2021-03-17 15:38:52 -04:00
parent 85e96264a8
commit d7081d2b4e
13 changed files with 149 additions and 89 deletions

7
kilo
View File

@ -13,24 +13,25 @@ set_error_handler(static function (
$errline
) {
$msg = print_r([
'code' => $errno,
'code' => error_code_name($errno),
'message' => $errstr,
'file' => $errfile,
'line' => $errline,
], TRUE);
file_put_contents('error.log', $msg, FILE_APPEND);
file_put_contents('kilo.log', $msg, FILE_APPEND);
return true;
}, -1);
set_exception_handler(static function (mixed $e) {
$msg = print_r([
'code' => $e->getCode(),
'codeName' => error_code_name($e->getCode()),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
], TRUE);
file_put_contents('exception.log', $msg, FILE_APPEND);
file_put_contents('kilo.log', $msg, FILE_APPEND);
});
// ! Init with an IIFE

View File

@ -2,6 +2,7 @@
namespace Aviat\Kilo;
use Aviat\Kilo\Enum\KeyCode;
use Aviat\Kilo\Tokens\PHP8;
use Aviat\Kilo\Type\Point;
@ -39,13 +40,13 @@ class Document {
return new self();
}
protected function rowsToString(): string
public function row(int $index): ?Row
{
$lines = array_map(fn (Row $row) => (string)$row, $this->rows);
return implode('', $lines);
return (array_key_exists($index, $this->rows)) ? $this->rows[$index] : null;
}
// ------------------------------------------------------------------------
// ! File I/O
// ------------------------------------------------------------------------
@ -97,11 +98,13 @@ class Document {
$this->dirty = true;
if ($c === "\n")
if ($c === KeyCode::ENTER || $c === KeyCode::CARRIAGE_RETURN)
{
$this->insertNewline($at);
return;
}
else if ($at->y === $this->numRows)
if ($at->y === $this->numRows)
{
$this->insertRow($this->numRows, '');
}
@ -219,14 +222,14 @@ class Document {
$newChars = substr($chars, 0, $at->x);
// Truncate the previous row
$row->chars = $newChars;
$row->chars = $newChars;
// Add a new row with the contents of the previous row at the point of the split
$this->insertRow($at->y + 1, substr($chars, $at->x));
}
}
public function selectSyntaxHighlight(): void
protected function selectSyntaxHighlight(): void
{
if (empty($this->filename))
{
@ -241,6 +244,13 @@ class Document {
$this->refreshSyntax();
}
protected function rowsToString(): string
{
$lines = array_map(fn (Row $row) => (string)$row, $this->rows);
return implode('', $lines);
}
public function refreshSyntax(): void
{
// Update the syntax highlighting for all the rows of the file

View File

@ -97,6 +97,10 @@ class Editor {
$this->document = $maybeDocument;
}
}
else
{
$this->document = Document::new();
}
}
public function __debugInfo(): array
@ -230,7 +234,13 @@ class Editor {
if ( ! empty($savedHl))
{
$this->document->rows[$savedHlLine]->hl = $savedHl;
$row = $this->document->row($savedHlLine);
if ($row !== null)
{
$row->hl = $savedHl;
}
$savedHl = [];
}
@ -281,7 +291,11 @@ class Editor {
$current = 0;
}
$row =& $this->document->rows[$current];
$row = $this->document->row($current);
if ($row === null)
{
break;
}
$match = strpos($row->render, $query);
if ($match !== FALSE)
@ -326,7 +340,13 @@ class Editor {
$this->renderX = 0;
if ($this->cursor->y < $this->document->numRows)
{
$this->renderX = $this->rowCxToRx($this->document->rows[$this->cursor->y], $this->cursor->x);
$row = $this->document->row($this->cursor->y);
if ($row !== null)
{
$this->renderX = $this->rowCxToRx($row, $this->cursor->x);
}
}
// Vertical Scrolling
@ -368,7 +388,13 @@ class Editor {
protected function drawRow(int $rowIdx): void
{
$len = $this->document->rows[$rowIdx]->rsize - $this->offset->x;
$row = $this->document->row($rowIdx);
if ($row === null)
{
return;
}
$len = $row->rsize - $this->offset->x;
if ($len < 0)
{
$len = 0;
@ -378,8 +404,8 @@ class Editor {
$len = $this->terminalSize->cols;
}
$chars = substr($this->document->rows[$rowIdx]->render, $this->offset->x, (int)$len);
$hl = array_slice($this->document->rows[$rowIdx]->hl, $this->offset->x, (int)$len);
$chars = substr($row->render, $this->offset->x, (int)$len);
$hl = array_slice($row->hl, $this->offset->x, (int)$len);
$currentColor = -1;
@ -464,7 +490,7 @@ class Editor {
$statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]';
$syntaxType = $this->document->fileType->name;
$isDirty = $this->document->dirty ? '(modified)' : '';
$isDirty = $this->document->isDirty() ? '(modified)' : '';
$status = sprintf('%.20s - %d lines %s', $statusFilename, $this->document->numRows, $isDirty);
$rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursor->y + 1, $this->document->numRows);
$len = strlen($status);
@ -492,12 +518,14 @@ class Editor {
protected function drawMessageBar(): void
{
$this->outputBuffer .= ANSI::CLEAR_LINE;
$len = strlen($this->statusMessage->text);
$len = $this->statusMessage->len;
if ($len > $this->terminalSize->cols)
{
$len = $this->terminalSize->cols;
}
// If there is a message, and it's been less than 5 seconds since
// last screen update, show the message
if ($len > 0 && (time() - $this->statusMessage->time) < 5)
{
$this->outputBuffer .= substr($this->statusMessage->text, 0, $len);
@ -530,7 +558,11 @@ class Editor {
public function setStatusMessage(string $fmt, mixed ...$args): void
{
$this->statusMessage = StatusMessage::from($fmt, ...$args);
$text = func_num_args() > 1
? sprintf($fmt, ...$args)
: $fmt;
$this->statusMessage = StatusMessage::from($text);
}
// ------------------------------------------------------------------------
@ -546,17 +578,17 @@ class Editor {
$this->setStatusMessage($prompt, $buffer);
$this->refreshScreen();
$c = $this->readKey();
$c = Terminal::readKey();
$isModifier = in_array($c, $modifiers, TRUE);
if ($c === KeyType::ESCAPE || ($c === KeyType::ENTER && $buffer !== ''))
if ($c === KeyType::ESCAPE || ($c === KeyCode::ENTER && $buffer !== ''))
{
$this->setStatusMessage('');
if ($callback !== NULL)
{
$callback($buffer, $c);
}
return ($c === KeyType::ENTER) ? $buffer : '';
return ($c === KeyCode::ENTER) ? $buffer : '';
}
if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE)
@ -579,7 +611,11 @@ class Editor {
{
$x = $this->cursor->x;
$y = $this->cursor->y;
$row = $this->document->rows[$y];
$row = $this->document->row($y);
if ($row === NULL)
{
return;
}
switch ($key)
{
@ -592,16 +628,16 @@ class Editor {
{
// Beginning of a line, go to end of previous line
$y--;
$x = $this->document->rows[$y]->size - 1;
$x = $row->size - 1;
}
break;
case KeyType::ARROW_RIGHT:
if ($row && $x < $row->size)
if ($x < $row->size)
{
$x++;
}
else if ($row && $x === $row->size)
else if ($x === $row->size)
{
$y++;
$x = 0;
@ -641,7 +677,7 @@ class Editor {
case KeyType::END_KEY:
if ($y < $this->document->numRows)
{
$x = $this->document->rows[$y]->size;
$x = $row->size;
}
break;
@ -651,15 +687,17 @@ class Editor {
// Snap cursor to the end of a row when moving
// from a longer row to a shorter one
$row = $this->document->rows[$y];
$rowLen = ($row !== NULL) ? $row->size : 0;
if ($x > $rowLen)
$row = $this->document->row($y);
if ($row !== null)
{
$x = $rowLen;
}
if ($x > $row->size)
{
$x = $row->size;
}
$this->cursor->x = $x;
$this->cursor->y = $y;
$this->cursor->x = $x;
$this->cursor->y = $y;
}
}
protected function processKeypress(): void
@ -713,6 +751,10 @@ class Editor {
// Do nothing
break;
case KeyType::ENTER:
$this->insertChar("\n");
break;
default:
$this->insertChar($c);
break;
@ -728,7 +770,7 @@ class Editor {
protected function quitAttempt(): void
{
if ($this->document->dirty && $this->quitTimes > 0)
if ($this->document->isDirty() && $this->quitTimes > 0)
{
$this->setStatusMessage(
'WARNING!!! File has unsaved changes. Press Ctrl-Q %d more times to quit.',

View File

@ -17,6 +17,7 @@ class KeyCode {
public const ARROW_RIGHT = "\e[C";
public const ARROW_UP = "\e[A";
public const BACKSPACE = "\x7f";
public const BELL = "\a";
public const CARRIAGE_RETURN = "\r";
public const DEL_KEY = "\e[3~";
public const EMPTY = '';

View File

@ -11,15 +11,16 @@ use Aviat\Kilo\Traits;
class KeyType {
use Traits\ConstList;
public const ARROW_DOWN = 'ARROW_DOWN';
public const ARROW_LEFT = 'ARROW_LEFT';
public const ARROW_RIGHT = 'ARROW_RIGHT';
public const ARROW_UP = 'ARROW_UP';
public const BACKSPACE = 'BACKSPACE';
public const DEL_KEY = 'DELETE';
public const END_KEY = 'END';
public const ESCAPE = 'ESCAPE';
public const HOME_KEY = 'HOME';
public const PAGE_DOWN = 'PAGE_DOWN';
public const PAGE_UP = 'PAGE_UP';
public const ARROW_DOWN = 'KEY_ARROW_DOWN';
public const ARROW_LEFT = 'KEY_ARROW_LEFT';
public const ARROW_RIGHT = 'KEY_ARROW_RIGHT';
public const ARROW_UP = 'KEY_ARROW_UP';
public const BACKSPACE = 'KEY_BACKSPACE';
public const DEL_KEY = 'KEY_DELETE';
public const END_KEY = 'KEY_END';
public const ENTER = 'KEY_ENTER';
public const ESCAPE = 'KEY_ESCAPE';
public const HOME_KEY = 'KEY_HOME';
public const PAGE_DOWN = 'KEY_PAGE_DOWN';
public const PAGE_UP = 'KEY_PAGE_UP';
}

View File

@ -13,15 +13,14 @@ class FileType {
private static function getSyntaxFromFilename(string $filename): Syntax
{
$ext = (string)strstr(basename($filename), '.');
$ext = strstr(basename($filename), '.');
$ext = ($ext !== FALSE) ? $ext : '';
return match ($ext) {
'.php', 'kilo' => Syntax::new(
'PHP',
['.php', 'kilo'],
),
'.c', '.h', '.cpp', '.cxx', '.cc', '.hpp' => Syntax::new(
'C',
['.c', '.h', '.cpp'],
[
'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct',
'union', 'class', 'else', 'enum', 'for', 'case', 'if',
@ -33,12 +32,10 @@ class FileType {
),
'.css', '.less', '.sass', '.scss' => Syntax::new(
'CSS',
['.css', '.less', '.sass', 'scss'],
slcs: '',
),
'.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es' => Syntax::new(
'JavaScript',
['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'],
[
'instanceof', 'continue', 'debugger', 'function', 'default', 'extends',
'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof',
@ -52,7 +49,6 @@ class FileType {
),
'.rs' => Syntax::new(
'Rust',
['.rs'],
[
'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate',
'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl',

View File

@ -11,7 +11,6 @@ class Syntax {
public static function new(
string $name,
array $extList = [],
array $keywords1 = [],
array $keywords2 = [],
string $slcs = '//',
@ -20,7 +19,7 @@ class Syntax {
int $flags = self::HIGHLIGHT_NUMBERS | self::HIGHLIGHT_STRINGS,
): self
{
return new self($name, $extList, $keywords1, $keywords2, $slcs, $mcs, $mce, $flags);
return new self($name, $keywords1, $keywords2, $slcs, $mcs, $mce, $flags);
}
public static function default(): self
@ -31,8 +30,6 @@ class Syntax {
private function __construct(
/** The name of the programming language */
public string $filetype,
/** Relevant file extensions for the specified language */
public array $filematch,
/** Primary set of language keywords */
public array $keywords1,
/** Secondary set of language keywords */

View File

@ -95,6 +95,7 @@ class Terminal {
KeyCode::ARROW_RIGHT => KeyType::ARROW_RIGHT,
KeyCode::ARROW_UP => KeyType::ARROW_UP,
KeyCode::DEL_KEY => KeyType::DEL_KEY,
KeyCode::ENTER => KeyType::ENTER,
KeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
KeyCode::PAGE_UP => KeyType::PAGE_UP,

View File

@ -5,11 +5,12 @@ namespace Aviat\Kilo\Type;
class StatusMessage {
private function __construct(
public string $text,
public int $len,
public int $time,
) {}
public static function from(string $text, mixed ...$args): self
public static function from(string $text): self
{
return new self(sprintf($text, ...$args), time());
return new self($text, strlen($text), time());
}
}

View File

@ -187,3 +187,24 @@ function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string
{
return str_replace(KeyCode::TAB, str_repeat(KeyCode::SPACE, $number), $str);
}
function error_code_name(int $code): string
{
return match ($code) {
E_ERROR => 'Error',
E_WARNING => 'Warning',
E_PARSE => 'Parse Error',
E_NOTICE => 'Notice',
E_CORE_ERROR => 'Core Error',
E_CORE_WARNING => 'Core Warning',
E_COMPILE_ERROR => 'Compile Error',
E_COMPILE_WARNING => 'Compile Warning',
E_USER_ERROR => 'User Error',
E_USER_WARNING => 'User Warning',
E_USER_NOTICE => 'User Notice',
E_RECOVERABLE_ERROR => 'Recoverable Error',
E_DEPRECATED => 'Deprecated',
E_USER_DEPRECATED => 'User Deprecated',
default => 'Unknown',
};
}

View File

@ -29,14 +29,6 @@ class MockEditor extends Editor {
class EditorTest extends TestCase {
use MatchesSnapshots;
public function testSanity(): void
{
$editor = MockEditor::mock();
$this->assertEquals(0, $editor->numRows);
$this->assertNull($editor->syntax);
}
public function test__debugInfo(): void
{
$editor = MockEditor::mock();

View File

@ -8,7 +8,6 @@ use PHPUnit\Framework\TestCase;
use function Aviat\Kilo\array_replace_range;
use function Aviat\Kilo\ctrl_key;
use function Aviat\Kilo\get_file_syntax_map;
use function Aviat\Kilo\is_ascii;
use function Aviat\Kilo\is_ctrl;
use function Aviat\Kilo\is_digit;
@ -97,11 +96,6 @@ class FunctionTest extends TestCase {
$this->assertNotEquals(syntax_to_color(Highlight::OPERATOR), Color::FG_WHITE);
}
public function test_get_file_syntax_map(): void
{
$this->assertNotEmpty(get_file_syntax_map());
}
public function test_str_contains(): void
{
// Search from string offset

View File

@ -2,19 +2,22 @@
namespace Aviat\Kilo\Tests\Traits;
use Aviat\Kilo\{Editor, Row};
use Aviat\Kilo\
{
Document,
Row};
use PHPUnit\Framework\TestCase;
class RowTest extends TestCase {
protected Editor $editor;
protected Document $document;
protected Row $row;
public function setUp(): void
{
parent::setUp();
$this->editor = Editor::new();
$this->row = Row::new($this->editor, '', 0);
$this->document = Document::new();
$this->row = Row::new($this->document, '', 0);
}
public function testSanity(): void
@ -53,42 +56,42 @@ class RowTest extends TestCase {
$this->assertEquals($expected, $actual);
}
public function testInsertChar(): void
public function testInsert(): void
{
$this->row->chars = 'abde';
$this->row->insertChar(2, 'c');
$this->row->insert(2, 'c');
$this->assertEquals('abcde', $this->row->chars);
$this->assertEquals('abcde', $this->row->render);
$this->assertEquals(true, $this->editor->dirty);
$this->assertEquals(true, $this->document->dirty);
}
public function testInsertCharBadOffset(): void
public function testInsertBadOffset(): void
{
$this->row->chars = 'ab';
$this->row->insertChar(5, 'c');
$this->row->insert(5, 'c');
$this->assertEquals('abc', $this->row->chars);
$this->assertEquals('abc', $this->row->render);
$this->assertEquals(true, $this->editor->dirty);
$this->assertEquals(true, $this->document->dirty);
}
public function testDeleteChar(): void
public function testDelete(): void
{
$this->row->chars = 'abcdef';
$this->row->deleteChar(5);
$this->row->delete(5);
$this->assertEquals('abcde', $this->row->chars);
$this->assertEquals('abcde', $this->row->render);
$this->assertEquals(true, $this->editor->dirty);
$this->assertEquals(true, $this->document->dirty);
}
public function testDeleteCharBadOffset(): void
public function testDeleteBadOffset(): void
{
$this->row->chars = 'ab';
$this->row->deleteChar(5);
$this->row->delete(5);
$this->assertEquals('ab', $this->row->chars);
$this->assertEquals(false, $this->editor->dirty);
$this->assertEquals(false, $this->document->dirty);
}
}