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

View File

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

View File

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

View File

@ -11,15 +11,16 @@ use Aviat\Kilo\Traits;
class KeyType { class KeyType {
use Traits\ConstList; use Traits\ConstList;
public const ARROW_DOWN = 'ARROW_DOWN'; public const ARROW_DOWN = 'KEY_ARROW_DOWN';
public const ARROW_LEFT = 'ARROW_LEFT'; public const ARROW_LEFT = 'KEY_ARROW_LEFT';
public const ARROW_RIGHT = 'ARROW_RIGHT'; public const ARROW_RIGHT = 'KEY_ARROW_RIGHT';
public const ARROW_UP = 'ARROW_UP'; public const ARROW_UP = 'KEY_ARROW_UP';
public const BACKSPACE = 'BACKSPACE'; public const BACKSPACE = 'KEY_BACKSPACE';
public const DEL_KEY = 'DELETE'; public const DEL_KEY = 'KEY_DELETE';
public const END_KEY = 'END'; public const END_KEY = 'KEY_END';
public const ESCAPE = 'ESCAPE'; public const ENTER = 'KEY_ENTER';
public const HOME_KEY = 'HOME'; public const ESCAPE = 'KEY_ESCAPE';
public const PAGE_DOWN = 'PAGE_DOWN'; public const HOME_KEY = 'KEY_HOME';
public const PAGE_UP = 'PAGE_UP'; 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 private static function getSyntaxFromFilename(string $filename): Syntax
{ {
$ext = (string)strstr(basename($filename), '.'); $ext = strstr(basename($filename), '.');
$ext = ($ext !== FALSE) ? $ext : '';
return match ($ext) { return match ($ext) {
'.php', 'kilo' => Syntax::new( '.php', 'kilo' => Syntax::new(
'PHP', 'PHP',
['.php', 'kilo'],
), ),
'.c', '.h', '.cpp', '.cxx', '.cc', '.hpp' => Syntax::new( '.c', '.h', '.cpp', '.cxx', '.cc', '.hpp' => Syntax::new(
'C', 'C',
['.c', '.h', '.cpp'],
[ [
'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct', 'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct',
'union', 'class', 'else', 'enum', 'for', 'case', 'if', 'union', 'class', 'else', 'enum', 'for', 'case', 'if',
@ -33,12 +32,10 @@ class FileType {
), ),
'.css', '.less', '.sass', '.scss' => Syntax::new( '.css', '.less', '.sass', '.scss' => Syntax::new(
'CSS', 'CSS',
['.css', '.less', '.sass', 'scss'],
slcs: '', slcs: '',
), ),
'.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es' => Syntax::new( '.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es' => Syntax::new(
'JavaScript', 'JavaScript',
['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'],
[ [
'instanceof', 'continue', 'debugger', 'function', 'default', 'extends', 'instanceof', 'continue', 'debugger', 'function', 'default', 'extends',
'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof', 'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof',
@ -52,7 +49,6 @@ class FileType {
), ),
'.rs' => Syntax::new( '.rs' => Syntax::new(
'Rust', 'Rust',
['.rs'],
[ [
'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate', 'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate',
'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl', 'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl',

View File

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

View File

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

View File

@ -5,11 +5,12 @@ namespace Aviat\Kilo\Type;
class StatusMessage { class StatusMessage {
private function __construct( private function __construct(
public string $text, public string $text,
public int $len,
public int $time, 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); 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 { class EditorTest extends TestCase {
use MatchesSnapshots; use MatchesSnapshots;
public function testSanity(): void
{
$editor = MockEditor::mock();
$this->assertEquals(0, $editor->numRows);
$this->assertNull($editor->syntax);
}
public function test__debugInfo(): void public function test__debugInfo(): void
{ {
$editor = MockEditor::mock(); $editor = MockEditor::mock();

View File

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

View File

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