Compare commits
21 Commits
633094ea9f
...
d0817b62c4
Author | SHA1 | Date | |
---|---|---|---|
|
d0817b62c4 | ||
|
a50c22c0e0 | ||
|
8f5986e91e | ||
2956999737 | |||
5746d15117 | |||
|
52875f348f | ||
|
69ffb84b33 | ||
b980a6feb0 | |||
dcaf922796 | |||
ead70c33ac | |||
|
30b4c3818a | ||
|
644f27bb37 | ||
|
35ec8f27ad | ||
b63c3e49d8 | |||
|
6c568dfaac | ||
d92ca27880 | |||
50151b7e18 | |||
d7081d2b4e | |||
|
85e96264a8 | ||
d0aea78ac3 | |||
7d381d10e9 |
35
composer.lock
generated
35
composer.lock
generated
|
@ -528,16 +528,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpstan/phpstan",
|
"name": "phpstan/phpstan",
|
||||||
"version": "0.12.80",
|
"version": "0.12.81",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/phpstan/phpstan.git",
|
"url": "https://github.com/phpstan/phpstan.git",
|
||||||
"reference": "c6a1b17f22ecf708d434d6bee05092647ec7e686"
|
"reference": "0dd5b0ebeff568f7000022ea5f04aa86ad3124b8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/c6a1b17f22ecf708d434d6bee05092647ec7e686",
|
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/0dd5b0ebeff568f7000022ea5f04aa86ad3124b8",
|
||||||
"reference": "c6a1b17f22ecf708d434d6bee05092647ec7e686",
|
"reference": "0dd5b0ebeff568f7000022ea5f04aa86ad3124b8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -568,7 +568,7 @@
|
||||||
"description": "PHPStan - PHP Static Analysis Tool",
|
"description": "PHPStan - PHP Static Analysis Tool",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/phpstan/phpstan/issues",
|
"issues": "https://github.com/phpstan/phpstan/issues",
|
||||||
"source": "https://github.com/phpstan/phpstan/tree/0.12.80"
|
"source": "https://github.com/phpstan/phpstan/tree/0.12.81"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -584,7 +584,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2021-02-28T20:22:43+00:00"
|
"time": "2021-03-08T22:03:02+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
|
@ -2994,30 +2994,35 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "webmozart/assert",
|
"name": "webmozart/assert",
|
||||||
"version": "1.9.1",
|
"version": "1.10.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/webmozarts/assert.git",
|
"url": "https://github.com/webmozarts/assert.git",
|
||||||
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389"
|
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/webmozarts/assert/zipball/bafc69caeb4d49c39fd0779086c03a3738cbb389",
|
"url": "https://api.github.com/repos/webmozarts/assert/zipball/6964c76c7804814a842473e0c8fd15bab0f18e25",
|
||||||
"reference": "bafc69caeb4d49c39fd0779086c03a3738cbb389",
|
"reference": "6964c76c7804814a842473e0c8fd15bab0f18e25",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^5.3.3 || ^7.0 || ^8.0",
|
"php": "^7.2 || ^8.0",
|
||||||
"symfony/polyfill-ctype": "^1.8"
|
"symfony/polyfill-ctype": "^1.8"
|
||||||
},
|
},
|
||||||
"conflict": {
|
"conflict": {
|
||||||
"phpstan/phpstan": "<0.12.20",
|
"phpstan/phpstan": "<0.12.20",
|
||||||
"vimeo/psalm": "<3.9.1"
|
"vimeo/psalm": "<4.6.1 || 4.6.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^4.8.36 || ^7.5.13"
|
"phpunit/phpunit": "^8.5.13"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.10-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Webmozart\\Assert\\": "src/"
|
"Webmozart\\Assert\\": "src/"
|
||||||
|
@ -3041,9 +3046,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/webmozarts/assert/issues",
|
"issues": "https://github.com/webmozarts/assert/issues",
|
||||||
"source": "https://github.com/webmozarts/assert/tree/1.9.1"
|
"source": "https://github.com/webmozarts/assert/tree/1.10.0"
|
||||||
},
|
},
|
||||||
"time": "2020-07-08T17:02:28+00:00"
|
"time": "2021-03-09T10:59:23+00:00"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"aliases": [],
|
"aliases": [],
|
||||||
|
|
17
kilo
17
kilo
|
@ -6,9 +6,26 @@ namespace Aviat\Kilo;
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
// Log notices/errors/warnings to file
|
// Log notices/errors/warnings to file
|
||||||
|
set_error_handler(static function (
|
||||||
|
$errno,
|
||||||
|
$errstr,
|
||||||
|
$errfile,
|
||||||
|
$errline
|
||||||
|
) {
|
||||||
|
$msg = print_r([
|
||||||
|
'code' => error_code_name($errno),
|
||||||
|
'message' => $errstr,
|
||||||
|
'file' => $errfile,
|
||||||
|
'line' => $errline,
|
||||||
|
], TRUE);
|
||||||
|
file_put_contents('kilo.log', $msg, FILE_APPEND);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, -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(),
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
</coverage>
|
</coverage>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite name="PHPKilo">
|
<testsuite name="PHPKilo">
|
||||||
<directory phpVersion="7.4.0" phpVersionOperator=">=">tests</directory>
|
<directory phpVersion="8.0.0" phpVersionOperator=">=">tests</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
<logging/>
|
<logging/>
|
||||||
|
|
|
@ -27,7 +27,7 @@ class ANSI {
|
||||||
/**
|
/**
|
||||||
* Removes text attributes, such as bold, underline, blink, inverted colors
|
* Removes text attributes, such as bold, underline, blink, inverted colors
|
||||||
*/
|
*/
|
||||||
public const RESET_TEXT = "\e[0m";
|
public const RESET_TEXT = "\e[m";
|
||||||
|
|
||||||
public const BOLD_TEXT = "\e[1m";
|
public const BOLD_TEXT = "\e[1m";
|
||||||
|
|
||||||
|
|
236
src/Document.php
236
src/Document.php
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Enum\RawKeyCode;
|
||||||
|
use Aviat\Kilo\Enum\KeyType;
|
||||||
|
use Aviat\Kilo\Tokens\PHP8;
|
||||||
use Aviat\Kilo\Type\Point;
|
use Aviat\Kilo\Type\Point;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,16 +13,18 @@ use Aviat\Kilo\Type\Point;
|
||||||
* @property-read int $numRows
|
* @property-read int $numRows
|
||||||
*/
|
*/
|
||||||
class Document {
|
class Document {
|
||||||
public ?Syntax $syntax = NULL;
|
public FileType $fileType;
|
||||||
|
|
||||||
// Tokens for highlighting PHP
|
// Tokens for highlighting PHP
|
||||||
public array $tokens = [];
|
public array $tokens = [];
|
||||||
|
|
||||||
private function __construct(
|
private function __construct(
|
||||||
|
public string $filename = '',
|
||||||
public array $rows = [],
|
public array $rows = [],
|
||||||
public ?string $filename = NULL,
|
public bool $dirty = FALSE,
|
||||||
private bool $dirty = FALSE,
|
) {
|
||||||
) {}
|
$this->fileType = FileType::from($this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
public function __get(string $name): ?int
|
public function __get(string $name): ?int
|
||||||
{
|
{
|
||||||
|
@ -36,21 +41,162 @@ class Document {
|
||||||
return new self();
|
return new self();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function open(?string $filename = NULL): self
|
public function row(int $index): Row
|
||||||
{
|
{
|
||||||
// @TODO move logic from Editor
|
return (array_key_exists($index, $this->rows))
|
||||||
return new self(filename: $filename);
|
? $this->rows[$index]
|
||||||
|
: Row::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(): bool
|
public function isEmpty(): bool
|
||||||
{
|
{
|
||||||
// @TODO move logic
|
return empty($this->rows);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insertChar(Point $at, string $c): void
|
// ------------------------------------------------------------------------
|
||||||
{
|
// ! File I/O
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public function open(string $filename): ?self
|
||||||
|
{
|
||||||
|
$handle = fopen($filename, 'rb');
|
||||||
|
if ($handle === FALSE)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->__construct($filename);
|
||||||
|
|
||||||
|
while (($line = fgets($handle)) !== FALSE)
|
||||||
|
{
|
||||||
|
// Remove line endings when reading the file
|
||||||
|
$this->rows[] = Row::new($this, rtrim($line), $this->numRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose($handle);
|
||||||
|
|
||||||
|
$this->dirty = false;
|
||||||
|
$this->selectSyntaxHighlight();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): int|false
|
||||||
|
{
|
||||||
|
$contents = $this->rowsToString();
|
||||||
|
|
||||||
|
$res = file_put_contents($this->filename, $contents);
|
||||||
|
|
||||||
|
if ($res === strlen($contents))
|
||||||
|
{
|
||||||
|
$this->dirty = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insert(Point $at, string $c): void
|
||||||
|
{
|
||||||
|
if ($at->y > $this->numRows)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($c === KeyType::ENTER || $c === RawKeyCode::CARRIAGE_RETURN)
|
||||||
|
{
|
||||||
|
$this->insertNewline($at);
|
||||||
|
$this->dirty = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rows[$at->y]->insert($at->x, $c);
|
||||||
|
$this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(Point $at): void
|
||||||
|
{
|
||||||
|
if ($at->y > $this->numRows)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row =& $this->rows[$at->y];
|
||||||
|
|
||||||
|
if ($at->x === $this->rows[$at->y]->size && $at->y + 1 < $this->numRows)
|
||||||
|
{
|
||||||
|
$this->rows[$at->y]->append($this->rows[$at->y + 1]->chars);
|
||||||
|
$this->deleteRow($at->y + 1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$row->delete($at->x);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertRow(int $at, string $s, bool $updateSyntax = TRUE): void
|
||||||
|
{
|
||||||
|
if ($at > $this->numRows)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = Row::new($this, $s, $at);
|
||||||
|
|
||||||
|
if ($at === $this->numRows)
|
||||||
|
{
|
||||||
|
$this->rows[] = $row;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->rows = [
|
||||||
|
...array_slice($this->rows, 0, $at),
|
||||||
|
$row,
|
||||||
|
...array_slice($this->rows, $at),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Update indexes of each row so that correct highlighting is done
|
||||||
|
for ($idx = $at; $idx < $this->numRows; $idx++)
|
||||||
|
{
|
||||||
|
$this->rows[$idx]->idx = $idx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ksort($this->rows);
|
||||||
|
|
||||||
|
// $this->rows[$at]->highlight();
|
||||||
|
|
||||||
|
// Re-tokenize the file
|
||||||
|
if ($updateSyntax)
|
||||||
|
{
|
||||||
|
$this->refreshPHPSyntax();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function deleteRow(int $at): void
|
||||||
|
{
|
||||||
|
if ($at < 0 || $at >= $this->numRows)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the row
|
||||||
|
unset($this->rows[$at]);
|
||||||
|
|
||||||
|
// Re-index the array of rows
|
||||||
|
$this->rows = array_values($this->rows);
|
||||||
|
for ($i = $at; $i < $this->numRows; $i++)
|
||||||
|
{
|
||||||
|
$this->rows[$i]->idx = $i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-tokenize the file
|
||||||
|
$this->refreshPHPSyntax();
|
||||||
|
|
||||||
|
$this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function isDirty(): bool
|
public function isDirty(): bool
|
||||||
|
@ -58,13 +204,73 @@ class Document {
|
||||||
return $this->dirty;
|
return $this->dirty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteChar(Point $at): void
|
protected function insertNewline(Point $at): void
|
||||||
{
|
{
|
||||||
|
if ($at->y > $this->numRows)
|
||||||
|
{
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function insertNewline(Point $at): void
|
if ($at->y === $this->numRows)
|
||||||
{
|
{
|
||||||
|
$this->insertRow($this->numRows, '');
|
||||||
|
}
|
||||||
|
else if ($at->x === 1)
|
||||||
|
{
|
||||||
|
$this->insertRow($at->y, '');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$row = $this->rows[$at->y];
|
||||||
|
$chars = $row->chars;
|
||||||
|
$newChars = substr($chars, 0, $at->x);
|
||||||
|
|
||||||
|
// Truncate the previous row
|
||||||
|
$row->setChars($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));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function selectSyntaxHighlight(): void
|
||||||
|
{
|
||||||
|
if (empty($this->filename))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->fileType->name === 'PHP')
|
||||||
|
{
|
||||||
|
$this->tokens = PHP8::getFileTokens($this->filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
$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
|
||||||
|
array_walk($this->rows, static fn (Row $row) => $row->update());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function refreshPHPSyntax(): void
|
||||||
|
{
|
||||||
|
if ($this->fileType->name !== 'PHP')
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->tokens = PHP8::getTokens($this->rowsToString());
|
||||||
|
$this->refreshSyntax();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
695
src/Editor.php
695
src/Editor.php
|
@ -3,17 +3,19 @@
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
use Aviat\Kilo\Type\TerminalSize;
|
use Aviat\Kilo\Type\TerminalSize;
|
||||||
use Aviat\Kilo\Enum\{Color, KeyCode, KeyType, Highlight};
|
use Aviat\Kilo\Enum\{
|
||||||
use Aviat\Kilo\Tokens\PHP8;
|
Color,
|
||||||
|
RawKeyCode,
|
||||||
|
KeyType,
|
||||||
|
Highlight,
|
||||||
|
SearchDirection
|
||||||
|
};
|
||||||
use Aviat\Kilo\Type\{Point, StatusMessage};
|
use Aviat\Kilo\Type\{Point, StatusMessage};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // Don't highlight this!
|
* // Don't highlight this!
|
||||||
* @property-read int $numRows
|
|
||||||
*/
|
*/
|
||||||
class Editor {
|
class Editor {
|
||||||
use Traits\MagicProperties;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string The screen buffer
|
* @var string The screen buffer
|
||||||
*/
|
*/
|
||||||
|
@ -59,19 +61,6 @@ class Editor {
|
||||||
*/
|
*/
|
||||||
protected int $quitTimes = KILO_QUIT_TIMES;
|
protected int $quitTimes = KILO_QUIT_TIMES;
|
||||||
|
|
||||||
/**
|
|
||||||
* Array of Row objects
|
|
||||||
*/
|
|
||||||
public array $rows = [];
|
|
||||||
|
|
||||||
public bool $dirty = FALSE;
|
|
||||||
public string $filename = '';
|
|
||||||
|
|
||||||
public ?Syntax $syntax = NULL;
|
|
||||||
|
|
||||||
// Tokens for highlighting PHP
|
|
||||||
public array $tokens = [];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the Editor instance with CLI arguments
|
* Create the Editor instance with CLI arguments
|
||||||
*
|
*
|
||||||
|
@ -100,22 +89,24 @@ class Editor {
|
||||||
$this->cursor = Point::new();
|
$this->cursor = Point::new();
|
||||||
$this->offset = Point::new();
|
$this->offset = Point::new();
|
||||||
$this->terminalSize = Terminal::size();
|
$this->terminalSize = Terminal::size();
|
||||||
$this->document = Document::new();
|
|
||||||
|
|
||||||
if (is_string($filename))
|
if (is_string($filename))
|
||||||
{
|
{
|
||||||
$this->open($filename);
|
$maybeDocument = Document::new()->open($filename);
|
||||||
}
|
if ($maybeDocument === NULL)
|
||||||
}
|
|
||||||
|
|
||||||
public function __get(string $name): ?int
|
|
||||||
{
|
{
|
||||||
if ($name === 'numRows')
|
$this->document = Document::new();
|
||||||
{
|
$this->setStatusMessage("ERR: Could not open file: {}", $filename);
|
||||||
return count($this->rows);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->document = $maybeDocument;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$this->document = Document::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __debugInfo(): array
|
public function __debugInfo(): array
|
||||||
|
@ -124,14 +115,9 @@ class Editor {
|
||||||
'cursor' => $this->cursor,
|
'cursor' => $this->cursor,
|
||||||
'document' => $this->document,
|
'document' => $this->document,
|
||||||
'offset' => $this->offset,
|
'offset' => $this->offset,
|
||||||
'dirty' => $this->dirty,
|
|
||||||
'filename' => $this->filename,
|
|
||||||
'renderX' => $this->renderX,
|
'renderX' => $this->renderX,
|
||||||
'rows' => $this->rows,
|
|
||||||
'terminalSize' => $this->terminalSize,
|
'terminalSize' => $this->terminalSize,
|
||||||
'statusMessage' => $this->statusMessage,
|
'statusMessage' => $this->statusMessage,
|
||||||
'syntax' => $this->syntax,
|
|
||||||
'tokens' => $this->tokens,
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,71 +133,18 @@ class Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
/**
|
||||||
// ! Terminal
|
* Set a status message to be displayed, using printf formatting
|
||||||
// ------------------------------------------------------------------------
|
* @param string $fmt
|
||||||
protected function readKey(): string
|
* @param mixed ...$args
|
||||||
|
*/
|
||||||
|
public function setStatusMessage(string $fmt, mixed ...$args): void
|
||||||
{
|
{
|
||||||
$c = Terminal::read();
|
$text = func_num_args() > 1
|
||||||
|
? sprintf($fmt, ...$args)
|
||||||
|
: $fmt;
|
||||||
|
|
||||||
return match($c)
|
$this->statusMessage = StatusMessage::from($text);
|
||||||
{
|
|
||||||
// Unambiguous mappings
|
|
||||||
KeyCode::ARROW_DOWN => KeyType::ARROW_DOWN,
|
|
||||||
KeyCode::ARROW_LEFT => KeyType::ARROW_LEFT,
|
|
||||||
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,
|
|
||||||
|
|
||||||
// Backspace
|
|
||||||
KeyCode::CTRL('h'), KeyCode::BACKSPACE => KeyType::BACKSPACE,
|
|
||||||
|
|
||||||
// Escape
|
|
||||||
KeyCode::CTRL('l'), KeyCode::ESCAPE => KeyType::ESCAPE,
|
|
||||||
|
|
||||||
// Home Key
|
|
||||||
"\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::HOME_KEY,
|
|
||||||
|
|
||||||
// End Key
|
|
||||||
"\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::END_KEY,
|
|
||||||
|
|
||||||
default => $c,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function selectSyntaxHighlight(): void
|
|
||||||
{
|
|
||||||
$this->syntax = NULL;
|
|
||||||
if (empty($this->filename))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In PHP, `strchr` and `strstr` are the same function
|
|
||||||
$ext = (string)strstr(basename($this->filename), '.');
|
|
||||||
|
|
||||||
foreach (get_file_syntax_map() as $syntax)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
in_array($ext, $syntax->filematch, TRUE) ||
|
|
||||||
in_array(basename($this->filename), $syntax->filematch, TRUE)
|
|
||||||
) {
|
|
||||||
$this->syntax = $syntax;
|
|
||||||
|
|
||||||
// Pre-tokenize the file
|
|
||||||
if ($this->syntax->filetype === 'PHP')
|
|
||||||
{
|
|
||||||
$this->tokens = PHP8::getFileTokens($this->filename);
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->refreshSyntax();
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -230,7 +163,7 @@ class Editor {
|
||||||
$rx = 0;
|
$rx = 0;
|
||||||
for ($i = 0; $i < $cx; $i++)
|
for ($i = 0; $i < $cx; $i++)
|
||||||
{
|
{
|
||||||
if ($row->chars[$i] === KeyCode::TAB)
|
if ($row->chars[$i] === RawKeyCode::TAB)
|
||||||
{
|
{
|
||||||
$rx += (KILO_TAB_STOP - 1) - ($rx % KILO_TAB_STOP);
|
$rx += (KILO_TAB_STOP - 1) - ($rx % KILO_TAB_STOP);
|
||||||
}
|
}
|
||||||
|
@ -252,7 +185,7 @@ class Editor {
|
||||||
$cur_rx = 0;
|
$cur_rx = 0;
|
||||||
for ($cx = 0; $cx < $row->size; $cx++)
|
for ($cx = 0; $cx < $row->size; $cx++)
|
||||||
{
|
{
|
||||||
if ($row->chars[$cx] === KeyCode::TAB)
|
if ($row->chars[$cx] === RawKeyCode::TAB)
|
||||||
{
|
{
|
||||||
$cur_rx += (KILO_TAB_STOP - 1) - ($cur_rx % KILO_TAB_STOP);
|
$cur_rx += (KILO_TAB_STOP - 1) - ($cur_rx % KILO_TAB_STOP);
|
||||||
}
|
}
|
||||||
|
@ -267,180 +200,13 @@ class Editor {
|
||||||
return $cx;
|
return $cx;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function insertRow(int $at, string $s, bool $updateSyntax = TRUE): void
|
|
||||||
{
|
|
||||||
if ($at > $this->numRows)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = Row::new($this, $s, $at);
|
|
||||||
|
|
||||||
if ($at === $this->numRows)
|
|
||||||
{
|
|
||||||
$this->rows[] = $row;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->rows = [
|
|
||||||
...array_slice($this->rows, 0, $at),
|
|
||||||
$row,
|
|
||||||
...array_slice($this->rows, $at),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Update indexes of each row so that correct highlighting is done
|
|
||||||
for ($idx = $at; $idx < $this->numRows; $idx++)
|
|
||||||
{
|
|
||||||
$this->rows[$idx]->idx = $idx;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort($this->rows);
|
|
||||||
|
|
||||||
$this->rows[$at]->update();
|
|
||||||
|
|
||||||
$this->dirty = true;
|
|
||||||
|
|
||||||
// Re-tokenize the file
|
|
||||||
if ($updateSyntax)
|
|
||||||
{
|
|
||||||
$this->refreshPHPSyntax();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function deleteRow(int $at): void
|
|
||||||
{
|
|
||||||
if ($at < 0 || $at >= $this->numRows)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the row
|
|
||||||
unset($this->rows[$at]);
|
|
||||||
|
|
||||||
// Re-index the array of rows
|
|
||||||
$this->rows = array_values($this->rows);
|
|
||||||
for ($i = $at; $i < $this->numRows; $i++)
|
|
||||||
{
|
|
||||||
$this->rows[$i]->idx = $i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-tokenize the file
|
|
||||||
$this->refreshPHPSyntax();
|
|
||||||
|
|
||||||
$this->dirty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// ! Editor Operations
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
protected function insertChar(string $c): void
|
|
||||||
{
|
|
||||||
if ($this->cursor->y === $this->numRows)
|
|
||||||
{
|
|
||||||
$this->insertRow($this->numRows, '');
|
|
||||||
}
|
|
||||||
$this->rows[$this->cursor->y]->insertChar($this->cursor->x, $c);
|
|
||||||
|
|
||||||
// Re-tokenize the file
|
|
||||||
$this->refreshPHPSyntax();
|
|
||||||
|
|
||||||
$this->cursor->x++;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function insertNewline(): void
|
|
||||||
{
|
|
||||||
// @TODO attempt smart indentation on newline?
|
|
||||||
|
|
||||||
if ($this->cursor->x === 0)
|
|
||||||
{
|
|
||||||
$this->insertRow($this->cursor->y, '');
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$row = $this->rows[$this->cursor->y];
|
|
||||||
$chars = $row->chars;
|
|
||||||
$newChars = substr($chars, 0, $this->cursor->x);
|
|
||||||
|
|
||||||
// Truncate the previous row
|
|
||||||
$row->chars = $newChars;
|
|
||||||
|
|
||||||
// Add a new row, with the contents from the cursor to the end of the line
|
|
||||||
$this->insertRow($this->cursor->y + 1, substr($chars, $this->cursor->x));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cursor->y++;
|
|
||||||
$this->cursor->x = 0;
|
|
||||||
|
|
||||||
// Re-tokenize the file
|
|
||||||
$this->refreshPHPSyntax();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function deleteChar(): void
|
|
||||||
{
|
|
||||||
if ($this->cursor->y === $this->numRows || ($this->cursor->x === 0 && $this->cursor->y === 0))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$row = $this->rows[$this->cursor->y];
|
|
||||||
if ($this->cursor->x > 0)
|
|
||||||
{
|
|
||||||
$row->deleteChar($this->cursor->x - 1);
|
|
||||||
$this->cursor->x--;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
$this->cursor->x = $this->rows[$this->cursor->y - 1]->size;
|
|
||||||
$this->rows[$this->cursor->y -1]->appendString($row->chars);
|
|
||||||
$this->deleteRow($this->cursor->y);
|
|
||||||
$this->cursor->y--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Re-tokenize the file
|
|
||||||
$this->refreshPHPSyntax();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// ! File I/O
|
// ! File I/O
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
protected function rowsToString(): string
|
|
||||||
{
|
|
||||||
$lines = array_map(fn (Row $row) => (string)$row, $this->rows);
|
|
||||||
|
|
||||||
return implode('', $lines);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function open(string $filename): void
|
|
||||||
{
|
|
||||||
// Copy filename for display
|
|
||||||
$this->filename = $filename;
|
|
||||||
|
|
||||||
$this->selectSyntaxHighlight();
|
|
||||||
|
|
||||||
$handle = fopen($filename, 'rb');
|
|
||||||
if ($handle === FALSE)
|
|
||||||
{
|
|
||||||
$this->setStatusMessage('Failed to open file: %s', $filename);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (($line = fgets($handle)) !== FALSE)
|
|
||||||
{
|
|
||||||
// Remove line endings when reading the file
|
|
||||||
$this->insertRow($this->numRows, rtrim($line), FALSE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose($handle);
|
|
||||||
|
|
||||||
$this->dirty = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function save(): void
|
protected function save(): void
|
||||||
{
|
{
|
||||||
if ($this->filename === '')
|
if ($this->document->filename === '')
|
||||||
{
|
{
|
||||||
$newFilename = $this->prompt('Save as: %s');
|
$newFilename = $this->prompt('Save as: %s');
|
||||||
if ($newFilename === '')
|
if ($newFilename === '')
|
||||||
|
@ -449,17 +215,14 @@ class Editor {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->filename = $newFilename;
|
$this->document->filename = $newFilename;
|
||||||
$this->selectSyntaxHighlight();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$contents = $this->rowsToString();
|
$res = $this->document->save();
|
||||||
|
|
||||||
$res = file_put_contents($this->filename, $contents);
|
if ($res !== FALSE)
|
||||||
if ($res === strlen($contents))
|
|
||||||
{
|
{
|
||||||
$this->setStatusMessage('%d bytes written to disk', strlen($contents));
|
$this->setStatusMessage('%d bytes written to disk', $res);
|
||||||
$this->dirty = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -472,66 +235,72 @@ class Editor {
|
||||||
|
|
||||||
protected function findCallback(string $query, string $key): void
|
protected function findCallback(string $query, string $key): void
|
||||||
{
|
{
|
||||||
static $lastMatch = -1;
|
static $lastMatch = NO_MATCH;
|
||||||
static $direction = 1;
|
static $direction = SearchDirection::FORWARD;
|
||||||
|
|
||||||
static $savedHlLine = 0;
|
static $savedHlLine = 0;
|
||||||
static $savedHl = [];
|
static $savedHl = [];
|
||||||
|
|
||||||
if ( ! empty($savedHl))
|
if ( ! empty($savedHl))
|
||||||
{
|
{
|
||||||
$this->rows[$savedHlLine]->hl = $savedHl;
|
$row = $this->document->row($savedHlLine);
|
||||||
|
|
||||||
|
if ($row->isValid())
|
||||||
|
{
|
||||||
|
$row->hl = $savedHl;
|
||||||
|
}
|
||||||
|
|
||||||
$savedHl = [];
|
$savedHl = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($key)
|
$direction = match ($key) {
|
||||||
|
KeyType::ARROW_UP, KeyType::ARROW_LEFT => SearchDirection::BACKWARD,
|
||||||
|
default => SearchDirection::FORWARD
|
||||||
|
};
|
||||||
|
|
||||||
|
$arrowKeys = [KeyType::ARROW_UP, KeyType::ARROW_DOWN, KeyType::ARROW_LEFT, KeyType::ARROW_RIGHT];
|
||||||
|
|
||||||
|
// Reset search state with non arrow-key input
|
||||||
|
if ( ! in_array($key, $arrowKeys, true))
|
||||||
|
{
|
||||||
|
$lastMatch = NO_MATCH;
|
||||||
|
$direction = SearchDirection::FORWARD;
|
||||||
|
|
||||||
|
if ($key === RawKeyCode::ENTER || $key === RawKeyCode::ESCAPE)
|
||||||
{
|
{
|
||||||
case KeyCode::ENTER:
|
|
||||||
case KeyCode::ESCAPE:
|
|
||||||
$lastMatch = -1;
|
|
||||||
$direction = 1;
|
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
case KeyType::ARROW_DOWN:
|
|
||||||
case KeyType::ARROW_RIGHT:
|
|
||||||
$direction = 1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::ARROW_UP:
|
|
||||||
case KeyType::ARROW_LEFT:
|
|
||||||
$direction = -1;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
$lastMatch = -1;
|
|
||||||
$direction = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($lastMatch === -1)
|
if ($lastMatch === NO_MATCH)
|
||||||
{
|
{
|
||||||
$direction = 1;
|
$direction = SearchDirection::FORWARD;
|
||||||
}
|
}
|
||||||
|
|
||||||
$current = $lastMatch;
|
$current = (int)$lastMatch;
|
||||||
|
|
||||||
if (empty($query))
|
if (empty($query))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for ($i = 0; $i < $this->numRows; $i++)
|
for ($i = 0; $i < $this->document->numRows; $i++)
|
||||||
{
|
{
|
||||||
$current += $direction;
|
$current += $direction;
|
||||||
if ($current === -1)
|
if ($current === -1)
|
||||||
{
|
{
|
||||||
$current = $this->numRows - 1;
|
$current = $this->document->numRows - 1;
|
||||||
}
|
}
|
||||||
else if ($current === $this->numRows)
|
else if ($current === $this->document->numRows)
|
||||||
{
|
{
|
||||||
$current = 0;
|
$current = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
$row =& $this->rows[$current];
|
$row = $this->document->row($current);
|
||||||
|
if ( ! $row->isValid())
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
$match = strpos($row->render, $query);
|
$match = strpos($row->render, $query);
|
||||||
if ($match !== FALSE)
|
if ($match !== FALSE)
|
||||||
|
@ -539,7 +308,7 @@ class Editor {
|
||||||
$lastMatch = $current;
|
$lastMatch = $current;
|
||||||
$this->cursor->y = (int)$current;
|
$this->cursor->y = (int)$current;
|
||||||
$this->cursor->x = $this->rowRxToCx($row, $match);
|
$this->cursor->x = $this->rowRxToCx($row, $match);
|
||||||
$this->offset->y = $this->numRows;
|
$this->offset->y = $this->document->numRows;
|
||||||
|
|
||||||
$savedHlLine = $current;
|
$savedHlLine = $current;
|
||||||
$savedHl = $row->hl;
|
$savedHl = $row->hl;
|
||||||
|
@ -574,9 +343,15 @@ class Editor {
|
||||||
protected function scroll(): void
|
protected function scroll(): void
|
||||||
{
|
{
|
||||||
$this->renderX = 0;
|
$this->renderX = 0;
|
||||||
if ($this->cursor->y < $this->numRows)
|
if ($this->cursor->y < $this->document->numRows)
|
||||||
{
|
{
|
||||||
$this->renderX = $this->rowCxToRx($this->rows[$this->cursor->y], $this->cursor->x);
|
$row = $this->document->row($this->cursor->y);
|
||||||
|
|
||||||
|
if ($row->isValid())
|
||||||
|
{
|
||||||
|
$this->renderX = $this->rowCxToRx($row, $this->cursor->x);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertical Scrolling
|
// Vertical Scrolling
|
||||||
|
@ -604,13 +379,13 @@ class Editor {
|
||||||
{
|
{
|
||||||
for ($y = 0; $y < $this->terminalSize->rows; $y++)
|
for ($y = 0; $y < $this->terminalSize->rows; $y++)
|
||||||
{
|
{
|
||||||
$filerow = $y + $this->offset->y;
|
$fileRow = $y + $this->offset->y;
|
||||||
|
|
||||||
$this->outputBuffer .= ANSI::CLEAR_LINE;
|
$this->outputBuffer .= ANSI::CLEAR_LINE;
|
||||||
|
|
||||||
($filerow >= $this->numRows)
|
($fileRow >= $this->document->numRows)
|
||||||
? $this->drawPlaceholderRow($y)
|
? $this->drawPlaceholderRow($y)
|
||||||
: $this->drawRow($filerow);
|
: $this->drawRow($fileRow);
|
||||||
|
|
||||||
$this->outputBuffer .= "\r\n";
|
$this->outputBuffer .= "\r\n";
|
||||||
}
|
}
|
||||||
|
@ -618,7 +393,13 @@ class Editor {
|
||||||
|
|
||||||
protected function drawRow(int $rowIdx): void
|
protected function drawRow(int $rowIdx): void
|
||||||
{
|
{
|
||||||
$len = $this->rows[$rowIdx]->rsize - $this->offset->x;
|
$row = $this->document->row($rowIdx);
|
||||||
|
if ( ! $row->isValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$len = $row->rsize - $this->offset->x;
|
||||||
if ($len < 0)
|
if ($len < 0)
|
||||||
{
|
{
|
||||||
$len = 0;
|
$len = 0;
|
||||||
|
@ -628,8 +409,8 @@ class Editor {
|
||||||
$len = $this->terminalSize->cols;
|
$len = $this->terminalSize->cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
$chars = substr($this->rows[$rowIdx]->render, $this->offset->x, (int)$len);
|
$chars = substr($row->render, $this->offset->x, (int)$len);
|
||||||
$hl = array_slice($this->rows[$rowIdx]->hl, $this->offset->x, (int)$len);
|
$hl = array_slice($row->hl, $this->offset->x, (int)$len);
|
||||||
|
|
||||||
$currentColor = -1;
|
$currentColor = -1;
|
||||||
|
|
||||||
|
@ -680,7 +461,7 @@ class Editor {
|
||||||
|
|
||||||
protected function drawPlaceholderRow(int $y): void
|
protected function drawPlaceholderRow(int $y): void
|
||||||
{
|
{
|
||||||
if ($this->numRows === 0 && $y === (int)($this->terminalSize->rows / 2))
|
if ($this->document->numRows === 0 && $y === (int)($this->terminalSize->rows / 2))
|
||||||
{
|
{
|
||||||
$welcome = sprintf('PHP Kilo editor -- version %s', KILO_VERSION);
|
$welcome = sprintf('PHP Kilo editor -- version %s', KILO_VERSION);
|
||||||
$welcomelen = strlen($welcome);
|
$welcomelen = strlen($welcome);
|
||||||
|
@ -712,11 +493,11 @@ class Editor {
|
||||||
{
|
{
|
||||||
$this->outputBuffer .= ANSI::color(Color::INVERT);
|
$this->outputBuffer .= ANSI::color(Color::INVERT);
|
||||||
|
|
||||||
$statusFilename = $this->filename !== '' ? $this->filename : '[No Name]';
|
$statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]';
|
||||||
$syntaxType = ($this->syntax !== NULL) ? $this->syntax->filetype : 'no ft';
|
$syntaxType = $this->document->fileType->name;
|
||||||
$isDirty = $this->dirty ? '(modified)' : '';
|
$isDirty = $this->document->isDirty() ? '(modified)' : '';
|
||||||
$status = sprintf('%.20s - %d lines %s', $statusFilename, $this->numRows, $isDirty);
|
$status = sprintf('%.20s - %d lines %s', $statusFilename, $this->document->numRows, $isDirty);
|
||||||
$rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursor->y + 1, $this->numRows);
|
$rstatus = sprintf('%s | %d/%d', $syntaxType, $this->cursor->y + 1, $this->document->numRows);
|
||||||
$len = strlen($status);
|
$len = strlen($status);
|
||||||
$rlen = strlen($rstatus);
|
$rlen = strlen($rstatus);
|
||||||
if ($len > $this->terminalSize->cols)
|
if ($len > $this->terminalSize->cols)
|
||||||
|
@ -742,12 +523,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);
|
||||||
|
@ -758,10 +541,7 @@ class Editor {
|
||||||
{
|
{
|
||||||
$this->scroll();
|
$this->scroll();
|
||||||
|
|
||||||
$this->outputBuffer = '';
|
$this->outputBuffer = ANSI::HIDE_CURSOR . ANSI::RESET_CURSOR;
|
||||||
|
|
||||||
$this->outputBuffer .= ANSI::HIDE_CURSOR;
|
|
||||||
$this->outputBuffer .= ANSI::RESET_CURSOR;
|
|
||||||
|
|
||||||
$this->drawRows();
|
$this->drawRows();
|
||||||
$this->drawStatusBar();
|
$this->drawStatusBar();
|
||||||
|
@ -778,11 +558,6 @@ class Editor {
|
||||||
Terminal::write($this->outputBuffer, strlen($this->outputBuffer));
|
Terminal::write($this->outputBuffer, strlen($this->outputBuffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setStatusMessage(string $fmt, mixed ...$args): void
|
|
||||||
{
|
|
||||||
$this->statusMessage = StatusMessage::from($fmt, ...$args);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// ! Input
|
// ! Input
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
@ -796,20 +571,20 @@ 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 === RawKeyCode::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 === RawKeyCode::ENTER) ? $buffer : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE)
|
if ($c === KeyType::DELETE || $c === KeyType::BACKSPACE)
|
||||||
{
|
{
|
||||||
$buffer = substr($buffer, 0, -1);
|
$buffer = substr($buffer, 0, -1);
|
||||||
}
|
}
|
||||||
|
@ -825,127 +600,35 @@ class Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function moveCursor(string $key): void
|
/**
|
||||||
{
|
* Input processing
|
||||||
$x = $this->cursor->x;
|
*/
|
||||||
$y = $this->cursor->y;
|
|
||||||
$row = $this->rows[$y];
|
|
||||||
|
|
||||||
switch ($key)
|
|
||||||
{
|
|
||||||
case KeyType::ARROW_LEFT:
|
|
||||||
if ($x !== 0)
|
|
||||||
{
|
|
||||||
$x--;
|
|
||||||
}
|
|
||||||
else if ($y > 0)
|
|
||||||
{
|
|
||||||
// Beginning of a line, go to end of previous line
|
|
||||||
$y--;
|
|
||||||
$x = $this->rows[$y]->size - 1;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::ARROW_RIGHT:
|
|
||||||
if ($row && $x < $row->size)
|
|
||||||
{
|
|
||||||
$x++;
|
|
||||||
}
|
|
||||||
else if ($row && $x === $row->size)
|
|
||||||
{
|
|
||||||
$y++;
|
|
||||||
$x = 0;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::ARROW_UP:
|
|
||||||
if ($y !== 0)
|
|
||||||
{
|
|
||||||
$y--;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::ARROW_DOWN:
|
|
||||||
if ($y < $this->numRows)
|
|
||||||
{
|
|
||||||
$y++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::PAGE_UP:
|
|
||||||
$y = ($y > $this->terminalSize->rows)
|
|
||||||
? $y - $this->terminalSize->rows
|
|
||||||
: 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::PAGE_DOWN:
|
|
||||||
$y = ($y + $this->terminalSize->rows < $this->numRows)
|
|
||||||
? $y + $this->terminalSize->rows
|
|
||||||
: $this->numRows;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::HOME_KEY:
|
|
||||||
$x = 0;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::END_KEY:
|
|
||||||
if ($y < $this->numRows)
|
|
||||||
{
|
|
||||||
$x = $this->rows[$y]->size;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snap cursor to the end of a row when moving
|
|
||||||
// from a longer row to a shorter one
|
|
||||||
$row = $this->rows[$y];
|
|
||||||
$rowLen = ($row !== NULL) ? $row->size : 0;
|
|
||||||
if ($x > $rowLen)
|
|
||||||
{
|
|
||||||
$x = $rowLen;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->cursor->x = $x;
|
|
||||||
$this->cursor->y = $y;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function processKeypress(): void
|
protected function processKeypress(): void
|
||||||
{
|
{
|
||||||
$c = $this->readKey();
|
$c = Terminal::readKey();
|
||||||
|
|
||||||
if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
|
if ($c === RawKeyCode::NULL || $c === RawKeyCode::EMPTY)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($c)
|
switch ($c)
|
||||||
{
|
{
|
||||||
case KeyCode::CTRL('q'):
|
case RawKeyCode::CTRL('q'):
|
||||||
$this->quitAttempt();
|
$this->quitAttempt();
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case KeyCode::CTRL('s'):
|
case RawKeyCode::CTRL('s'):
|
||||||
$this->save();
|
$this->save();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode::CTRL('f'):
|
case RawKeyCode::CTRL('f'):
|
||||||
$this->find();
|
$this->find();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::ENTER:
|
case KeyType::DELETE:
|
||||||
$this->insertNewline();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case KeyType::BACKSPACE:
|
case KeyType::BACKSPACE:
|
||||||
case KeyType::DEL_KEY:
|
$this->removeChar($c);
|
||||||
if ($c === KeyType::DEL_KEY)
|
|
||||||
{
|
|
||||||
$this->moveCursor(KeyType::ARROW_RIGHT);
|
|
||||||
}
|
|
||||||
$this->deleteChar();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::ARROW_UP:
|
case KeyType::ARROW_UP:
|
||||||
|
@ -954,12 +637,12 @@ class Editor {
|
||||||
case KeyType::ARROW_RIGHT:
|
case KeyType::ARROW_RIGHT:
|
||||||
case KeyType::PAGE_UP:
|
case KeyType::PAGE_UP:
|
||||||
case KeyType::PAGE_DOWN:
|
case KeyType::PAGE_DOWN:
|
||||||
case KeyType::HOME_KEY:
|
case KeyType::HOME:
|
||||||
case KeyType::END_KEY:
|
case KeyType::END:
|
||||||
$this->moveCursor($c);
|
$this->moveCursor($c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode::CTRL('l'):
|
case RawKeyCode::CTRL('l'):
|
||||||
case KeyType::ESCAPE:
|
case KeyType::ESCAPE:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
break;
|
break;
|
||||||
|
@ -977,14 +660,133 @@ class Editor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// ! Editor operation helpers
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
protected function moveCursor(string $key): void
|
||||||
|
{
|
||||||
|
$x = $this->cursor->x;
|
||||||
|
$y = $this->cursor->y;
|
||||||
|
$row = $this->document->row($y);
|
||||||
|
if ( ! $row->isValid())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($key)
|
||||||
|
{
|
||||||
|
case KeyType::ARROW_LEFT:
|
||||||
|
if ($x !== 0)
|
||||||
|
{
|
||||||
|
$x--;
|
||||||
|
}
|
||||||
|
else if ($y > 0)
|
||||||
|
{
|
||||||
|
// Beginning of a line, go to end of previous line
|
||||||
|
$y--;
|
||||||
|
$x = $row->size - 1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::ARROW_RIGHT:
|
||||||
|
if ($x < $row->size)
|
||||||
|
{
|
||||||
|
$x++;
|
||||||
|
}
|
||||||
|
else if ($x === $row->size)
|
||||||
|
{
|
||||||
|
$y++;
|
||||||
|
$x = 0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::ARROW_UP:
|
||||||
|
if ($y !== 0)
|
||||||
|
{
|
||||||
|
$y--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::ARROW_DOWN:
|
||||||
|
if ($y < $this->document->numRows)
|
||||||
|
{
|
||||||
|
$y++;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::PAGE_UP:
|
||||||
|
$y = saturating_sub($y, $this->terminalSize->rows);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::PAGE_DOWN:
|
||||||
|
$y = saturating_add($y, $this->terminalSize->rows, $this->document->numRows);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::HOME:
|
||||||
|
$x = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case KeyType::END:
|
||||||
|
if ($y < $this->document->numRows)
|
||||||
|
{
|
||||||
|
$x = $row->size;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap cursor to the end of a row when moving
|
||||||
|
// from a longer row to a shorter one
|
||||||
|
$row = $this->document->row($y);
|
||||||
|
if ($row->isValid())
|
||||||
|
{
|
||||||
|
if ($x > $row->size)
|
||||||
|
{
|
||||||
|
$x = $row->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->cursor->x = $x;
|
||||||
|
$this->cursor->y = $y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function insertChar(string $c): void
|
||||||
|
{
|
||||||
|
$this->document->insert($this->cursor, $c);
|
||||||
|
$this->moveCursor(KeyType::ARROW_RIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function removeChar(string $ch): void
|
||||||
|
{
|
||||||
|
if ($ch === KeyType::DELETE)
|
||||||
|
{
|
||||||
|
$this->document->delete($this->cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ch === KeyType::BACKSPACE && ($this->cursor->x > 0 || $this->cursor->y > 0))
|
||||||
|
{
|
||||||
|
$this->moveCursor(KeyType::ARROW_LEFT);
|
||||||
|
$this->document->delete($this->cursor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function quitAttempt(): void
|
protected function quitAttempt(): void
|
||||||
{
|
{
|
||||||
if ($this->dirty && $this->quitTimes > 0)
|
if ($this->document->isDirty() && $this->quitTimes > 0)
|
||||||
{
|
{
|
||||||
|
if ($this->quitTimes === KILO_QUIT_TIMES)
|
||||||
|
{
|
||||||
|
Terminal::ding();
|
||||||
|
}
|
||||||
|
|
||||||
$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.',
|
||||||
$this->quitTimes
|
$this->quitTimes
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->quitTimes--;
|
$this->quitTimes--;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -993,21 +795,4 @@ class Editor {
|
||||||
|
|
||||||
$this->shouldQuit = true;
|
$this->shouldQuit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function refreshSyntax(): void
|
|
||||||
{
|
|
||||||
// Update the syntax highlighting for all the rows of the file
|
|
||||||
array_walk($this->rows, static fn (Row $row) => $row->update());
|
|
||||||
}
|
|
||||||
|
|
||||||
private function refreshPHPSyntax(): void
|
|
||||||
{
|
|
||||||
if ($this->syntax?->filetype !== 'PHP')
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->tokens = PHP8::getTokens($this->rowsToString());
|
|
||||||
$this->refreshSyntax();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ class Highlight {
|
||||||
public const INVALID = 10;
|
public const INVALID = 10;
|
||||||
public const MATCH = 11;
|
public const MATCH = 11;
|
||||||
public const IDENTIFIER = 12;
|
public const IDENTIFIER = 12;
|
||||||
|
public const CHARACTER = 13;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map a PHP syntax token to its associated highlighting type
|
* Map a PHP syntax token to its associated highlighting type
|
||||||
|
|
|
@ -11,16 +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 DELETE = 'KEY_DELETE';
|
||||||
public const END_KEY = 'END';
|
public const END = 'KEY_END';
|
||||||
public const ENTER = 'ENTER';
|
public const ENTER = 'KEY_ENTER';
|
||||||
public const ESCAPE = 'ESCAPE';
|
public const ESCAPE = 'KEY_ESCAPE';
|
||||||
public const HOME_KEY = 'HOME';
|
public const HOME = 'KEY_HOME';
|
||||||
public const PAGE_DOWN = 'PAGE_DOWN';
|
public const PAGE_DOWN = 'KEY_PAGE_DOWN';
|
||||||
public const PAGE_UP = 'PAGE_UP';
|
public const PAGE_UP = 'KEY_PAGE_UP';
|
||||||
}
|
}
|
|
@ -9,7 +9,7 @@ use function Aviat\Kilo\ctrl_key;
|
||||||
* 'Raw' input from stdin
|
* 'Raw' input from stdin
|
||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
class KeyCode {
|
class RawKeyCode {
|
||||||
use Traits\ConstList;
|
use Traits\ConstList;
|
||||||
|
|
||||||
public const ARROW_DOWN = "\e[B";
|
public const ARROW_DOWN = "\e[B";
|
||||||
|
@ -17,8 +17,9 @@ 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 DELETE = "\e[3~";
|
||||||
public const EMPTY = '';
|
public const EMPTY = '';
|
||||||
public const ENTER = "\r";
|
public const ENTER = "\r";
|
||||||
public const ESCAPE = "\e";
|
public const ESCAPE = "\e";
|
15
src/Enum/SearchDirection.php
Normal file
15
src/Enum/SearchDirection.php
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Enum;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Traits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
class SearchDirection {
|
||||||
|
use Traits\ConstList;
|
||||||
|
|
||||||
|
public const FORWARD = 1;
|
||||||
|
public const BACKWARD = -1;
|
||||||
|
}
|
108
src/FileType.php
Normal file
108
src/FileType.php
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
class FileType {
|
||||||
|
|
||||||
|
public static function from(?string $filename): self
|
||||||
|
{
|
||||||
|
$syntax = self::getSyntaxFromFilename((string)$filename);
|
||||||
|
|
||||||
|
return new self($syntax->filetype, $syntax);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function getSyntaxFromFilename(string $filename): Syntax
|
||||||
|
{
|
||||||
|
$ext = strstr(basename($filename), '.');
|
||||||
|
$ext = ($ext !== FALSE) ? $ext : '';
|
||||||
|
return match ($ext) {
|
||||||
|
'.sh', '.bash' => Syntax::new(
|
||||||
|
'Shell',
|
||||||
|
slcs: '#',
|
||||||
|
mcs: '',
|
||||||
|
mce: '',
|
||||||
|
),
|
||||||
|
'.php', 'kilo' => Syntax::new(
|
||||||
|
'PHP',
|
||||||
|
),
|
||||||
|
'.c', '.h', '.cpp', '.cxx', '.cc', '.hpp' => Syntax::new(
|
||||||
|
'C',
|
||||||
|
[
|
||||||
|
'auto', 'break', 'case', 'const', 'continue', 'default', 'do', 'typedef', 'switch', 'return',
|
||||||
|
'static', 'while', 'break', 'struct', 'extern', 'union', 'class', 'else', 'enum', 'for', 'case',
|
||||||
|
'if', 'inline', 'register', 'restrict', 'return', 'sizeof', 'switch', 'typedef', 'union', 'volatile'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'#include', 'unsigned', '#define', '#ifndef', 'double', 'signed', '#endif',
|
||||||
|
'#ifdef', 'float', '#error', '#undef', '#elif', 'long', 'char', 'int', 'void', '#if',
|
||||||
|
'uint32_t', 'wchar_t', 'int32_t', 'int64_t', 'uint64_t', 'int16_t', 'uint16_t',
|
||||||
|
'uint8_t', 'int8_t',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<=>', '<<=', '>>=',
|
||||||
|
'++', '--', '==', '!=', '>=', '<=', '&&', '||', '<<', '>>',
|
||||||
|
'+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '->', '::',
|
||||||
|
],
|
||||||
|
hasCharType: true,
|
||||||
|
highlightCharacters: true,
|
||||||
|
),
|
||||||
|
'.css', '.less', '.sass', '.scss' => Syntax::new(
|
||||||
|
'CSS',
|
||||||
|
slcs: '',
|
||||||
|
),
|
||||||
|
'.go' => Syntax::new(
|
||||||
|
'Go',
|
||||||
|
[
|
||||||
|
'break', 'case', 'chan', 'const', 'continue', 'default', 'defer', 'else',
|
||||||
|
'fallthrough', 'for', 'func', 'go', 'goto', 'if', 'import', 'interface',
|
||||||
|
'map', 'package', 'range', 'return', 'select', 'struct', 'switch', 'type', 'var',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'uint8', 'uint16', 'uint32', 'uint64', 'int8', 'int16', 'int32', 'int64', 'float32', 'float64',
|
||||||
|
'uint', 'int', 'uintptr', 'complex64', 'complex128', 'byte', 'rune', '[]',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'&^=', '...', '>>=', '<<=', '--', '%=', '>>', ':=', '++', '/=', '<<', '>=',
|
||||||
|
'<-', '^=', '*=', '<=', '||', '|=', '-=', '!=', '==', '&&', '&=', '+=',
|
||||||
|
],
|
||||||
|
hasCharType: true,
|
||||||
|
highlightCharacters: true,
|
||||||
|
),
|
||||||
|
'.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es' => Syntax::new(
|
||||||
|
'JavaScript',
|
||||||
|
[
|
||||||
|
'instanceof', 'continue', 'debugger', 'function', 'default', 'extends',
|
||||||
|
'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof',
|
||||||
|
'break', 'catch', 'class', 'const', 'super', 'throw', 'while', 'yield',
|
||||||
|
'case', 'else', 'this', 'void', 'with', 'from', 'for', 'new', 'try',
|
||||||
|
'var', 'do', 'if', 'in', 'as',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'=>', 'Number', 'String', 'Object', 'Math', 'JSON', 'Boolean',
|
||||||
|
],
|
||||||
|
),
|
||||||
|
'.rs' => Syntax::new(
|
||||||
|
'Rust',
|
||||||
|
[
|
||||||
|
'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate',
|
||||||
|
'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl',
|
||||||
|
'loop', 'move', 'self', 'type', 'while', 'for', 'let', 'mod', 'pub', 'ref', 'true',
|
||||||
|
'use', 'mut', 'as', 'fn', 'if', 'in',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'DoubleEndedIterator', 'ExactSizeIterator', 'IntoIterator', 'PartialOrd', 'PartialEq',
|
||||||
|
'Iterator', 'ToString', 'Default', 'ToOwned', 'Extend', 'FnOnce', 'Option', 'String',
|
||||||
|
'AsMut', 'AsRef', 'Clone', 'Debug', 'FnMut', 'Sized', 'Unpin', 'array', 'isize',
|
||||||
|
'usize', '&str', 'Copy', 'Drop', 'From', 'Into', 'None', 'Self', 'Send', 'Some',
|
||||||
|
'Sync', 'bool', 'char', 'i128', 'u128', 'Box', 'Err', 'Ord', 'Vec', 'dyn', 'f32',
|
||||||
|
'f64', 'i16', 'i32', 'i64', 'str', 'u16', 'u32', 'u64', 'Eq', 'Fn', 'Ok', 'i8', 'u8',
|
||||||
|
],
|
||||||
|
hasCharType: true,
|
||||||
|
highlightCharacters: true,
|
||||||
|
),
|
||||||
|
default => Syntax::default(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private function __construct(public string $name, public Syntax $syntax) {}
|
||||||
|
}
|
497
src/Row.php
497
src/Row.php
|
@ -3,7 +3,7 @@
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\Highlight;
|
use Aviat\Kilo\Enum\Highlight;
|
||||||
use Aviat\Kilo\Enum\KeyCode;
|
use Aviat\Kilo\Enum\RawKeyCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property-read int $size
|
* @property-read int $size
|
||||||
|
@ -11,35 +11,71 @@ use Aviat\Kilo\Enum\KeyCode;
|
||||||
* @property-read string $chars
|
* @property-read string $chars
|
||||||
*/
|
*/
|
||||||
class Row {
|
class Row {
|
||||||
use Traits\MagicProperties;
|
// use Traits\MagicProperties;
|
||||||
|
|
||||||
private string $chars = '';
|
/**
|
||||||
|
* The version of the row to be displayed (where tabs are converted to display spaces)
|
||||||
|
*/
|
||||||
public string $render = '';
|
public string $render = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The mapping of characters to their highlighting type
|
||||||
|
*/
|
||||||
public array $hl = [];
|
public array $hl = [];
|
||||||
|
|
||||||
public int $idx;
|
/**
|
||||||
|
* Are we in the middle of highlighting a multi-line comment?
|
||||||
// This feels dirty...
|
*/
|
||||||
private Editor $parent;
|
|
||||||
private bool $hlOpenComment = FALSE;
|
private bool $hlOpenComment = FALSE;
|
||||||
|
|
||||||
private const T_RAW = -1;
|
/**
|
||||||
|
* Create a row in the current document
|
||||||
public static function new(Editor $parent, string $chars, int $idx): self
|
*
|
||||||
|
* @param Document $parent
|
||||||
|
* @param string $chars
|
||||||
|
* @param int $idx
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function new(Document $parent, string $chars, int $idx): self
|
||||||
{
|
{
|
||||||
$self = new self();
|
return new self(
|
||||||
$self->chars = $chars;
|
$parent,
|
||||||
$self->parent = $parent;
|
$chars,
|
||||||
$self->idx = $idx;
|
$idx,
|
||||||
|
);
|
||||||
return $self;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function __construct() {
|
/**
|
||||||
// Private in favor of ::new static function
|
* Create an empty Row
|
||||||
|
*
|
||||||
|
* @return self
|
||||||
|
*/
|
||||||
|
public static function default(): self
|
||||||
|
{
|
||||||
|
return new self(
|
||||||
|
Document::new(),
|
||||||
|
'',
|
||||||
|
0,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function __construct(
|
||||||
|
/**
|
||||||
|
* The document that this row belongs to
|
||||||
|
*/
|
||||||
|
private Document $parent,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string The raw characters in the row
|
||||||
|
*/
|
||||||
|
private string $chars,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int The line number of the current row
|
||||||
|
*/
|
||||||
|
public int $idx,
|
||||||
|
) {}
|
||||||
|
|
||||||
public function __get(string $name): mixed
|
public function __get(string $name): mixed
|
||||||
{
|
{
|
||||||
return match ($name)
|
return match ($name)
|
||||||
|
@ -51,20 +87,21 @@ class Row {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public function __set(string $name, mixed $value): void
|
/**
|
||||||
{
|
* Convert the row contents to a string for saving
|
||||||
if ($name === 'chars')
|
*
|
||||||
{
|
* @return string
|
||||||
$this->chars = $value;
|
*/
|
||||||
$this->update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->chars . "\n";
|
return $this->chars . "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the properties to display for var_dump
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
public function __debugInfo(): array
|
public function __debugInfo(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
|
@ -77,30 +114,52 @@ class Row {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insertChar(int $at, string $c): void
|
/**
|
||||||
|
* Is this row a valid part of a document?
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function isValid(): bool
|
||||||
|
{
|
||||||
|
return ! $this->parent->isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert the string or character $c at index $at
|
||||||
|
*
|
||||||
|
* @param int $at
|
||||||
|
* @param string $c
|
||||||
|
*/
|
||||||
|
public function insert(int $at, string $c): void
|
||||||
{
|
{
|
||||||
if ($at < 0 || $at > $this->size)
|
if ($at < 0 || $at > $this->size)
|
||||||
{
|
{
|
||||||
$this->appendString($c);
|
$this->append($c);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safely insert into arbitrary position in the existing string
|
// Safely insert into arbitrary position in the existing string
|
||||||
$this->chars = substr($this->chars, 0, $at) . $c . substr($this->chars, $at);
|
$this->chars = substr($this->chars, 0, $at) . $c . substr($this->chars, $at);
|
||||||
$this->update();
|
$this->update();
|
||||||
|
|
||||||
$this->parent->dirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function appendString(string $s): void
|
/**
|
||||||
|
* Append $s to the current row
|
||||||
|
*
|
||||||
|
* @param string $s
|
||||||
|
*/
|
||||||
|
public function append(string $s): void
|
||||||
{
|
{
|
||||||
$this->chars .= $s;
|
$this->chars .= $s;
|
||||||
$this->update();
|
$this->update();
|
||||||
|
|
||||||
$this->parent->dirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteChar(int $at): void
|
/**
|
||||||
|
* Delete the character at the specified index
|
||||||
|
*
|
||||||
|
* @param int $at
|
||||||
|
*/
|
||||||
|
public function delete(int $at): void
|
||||||
{
|
{
|
||||||
if ($at < 0 || $at >= $this->size)
|
if ($at < 0 || $at >= $this->size)
|
||||||
{
|
{
|
||||||
|
@ -109,48 +168,53 @@ class Row {
|
||||||
|
|
||||||
$this->chars = substr_replace($this->chars, '', $at, 1);
|
$this->chars = substr_replace($this->chars, '', $at, 1);
|
||||||
$this->update();
|
$this->update();
|
||||||
|
|
||||||
$this->parent->dirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setChars(string $chars): void
|
||||||
|
{
|
||||||
|
$this->chars = $chars;
|
||||||
|
$this->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert tabs to spaces for display, and update syntax highlighting
|
||||||
|
*/
|
||||||
public function update(): void
|
public function update(): void
|
||||||
{
|
{
|
||||||
$this->render = tabs_to_spaces($this->chars);
|
$this->render = tabs_to_spaces($this->chars);
|
||||||
|
$this->highlight();
|
||||||
$this->updateSyntax();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// ! Syntax Highlighting
|
// ! Syntax Highlighting
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
public function updateSyntax(): void
|
/**
|
||||||
|
* Parse the current file to apply syntax highlighting
|
||||||
|
*/
|
||||||
|
public function highlight(): void
|
||||||
{
|
{
|
||||||
$this->hl = array_fill(0, $this->rsize, Highlight::NORMAL);
|
$this->hl = array_fill(0, $this->rsize, Highlight::NORMAL);
|
||||||
|
|
||||||
if ($this->parent->syntax === NULL)
|
if ($this->parent->fileType->name === 'PHP')
|
||||||
|
{
|
||||||
|
$this->highlightPHP();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->parent->fileType->name === 'No filetype')
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->parent->syntax->filetype === 'PHP')
|
$syntax = $this->parent->fileType->syntax;
|
||||||
{
|
|
||||||
$this->updateSyntaxPHP();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
$keywords1 = $this->parent->syntax->keywords1;
|
$mcs = $syntax->multiLineCommentStart;
|
||||||
$keywords2 = $this->parent->syntax->keywords2;
|
$mce = $syntax->multiLineCommentEnd;
|
||||||
|
|
||||||
$scs = $this->parent->syntax->singleLineCommentStart;
|
|
||||||
$mcs = $this->parent->syntax->multiLineCommentStart;
|
|
||||||
$mce = $this->parent->syntax->multiLineCommentEnd;
|
|
||||||
|
|
||||||
$scsLen = strlen($scs);
|
|
||||||
$mcsLen = strlen($mcs);
|
$mcsLen = strlen($mcs);
|
||||||
$mceLen = strlen($mce);
|
$mceLen = strlen($mce);
|
||||||
|
|
||||||
$prevSep = TRUE;
|
|
||||||
$inString = '';
|
$inString = '';
|
||||||
$inComment = ($this->idx > 0 && $this->parent->rows[$this->idx - 1]->hlOpenComment);
|
$inComment = ($this->idx > 0 && $this->parent->rows[$this->idx - 1]->hlOpenComment);
|
||||||
|
|
||||||
|
@ -158,19 +222,8 @@ class Row {
|
||||||
|
|
||||||
while ($i < $this->rsize)
|
while ($i < $this->rsize)
|
||||||
{
|
{
|
||||||
$char = $this->render[$i];
|
|
||||||
$prevHl = ($i > 0) ? $this->hl[$i - 1] : Highlight::NORMAL;
|
|
||||||
|
|
||||||
// Single-line comments
|
|
||||||
if ($scsLen > 0 && $inString === '' && $inComment === FALSE
|
|
||||||
&& substr($this->render, $i, $scsLen) === $scs)
|
|
||||||
{
|
|
||||||
array_replace_range($this->hl, $i, $this->rsize - $i, Highlight::COMMENT);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-line comments
|
// Multi-line comments
|
||||||
if ($mcsLen > 0 && $mceLen > 0 && $inString === '')
|
if ($syntax->mlComments() && $inString === '')
|
||||||
{
|
{
|
||||||
if ($inComment)
|
if ($inComment)
|
||||||
{
|
{
|
||||||
|
@ -180,7 +233,6 @@ class Row {
|
||||||
array_replace_range($this->hl, $i, $mceLen, Highlight::ML_COMMENT);
|
array_replace_range($this->hl, $i, $mceLen, Highlight::ML_COMMENT);
|
||||||
$i += $mceLen;
|
$i += $mceLen;
|
||||||
$inComment = FALSE;
|
$inComment = FALSE;
|
||||||
$prevSep = TRUE;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,11 +249,223 @@ class Row {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// String/Char literals
|
if (
|
||||||
if ($this->parent->syntax->flags & Syntax::HIGHLIGHT_STRINGS)
|
$this->highlightCharacter($i, $syntax)
|
||||||
|
|| $this->highlightComment($i, $syntax)
|
||||||
|
|| $this->highlightPrimaryKeywords($i, $syntax)
|
||||||
|
|| $this->highlightSecondaryKeywords($i, $syntax)
|
||||||
|
|| $this->highlightString($i, $syntax)
|
||||||
|
|| $this->highlightOperators($i, $syntax)
|
||||||
|
|| $this->highlightCommonDelimeters($i)
|
||||||
|
|| $this->highlightCommonOperators($i)
|
||||||
|
|| $this->highlightNumber($i, $syntax)
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
$changed = $this->hlOpenComment !== $inComment;
|
||||||
|
$this->hlOpenComment = $inComment;
|
||||||
|
if ($changed && $this->idx + 1 < $this->parent->numRows)
|
||||||
{
|
{
|
||||||
if ($inString !== '')
|
$this->parent->rows[$this->idx + 1]->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightNumber(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
|
$char = $this->render[$i];
|
||||||
|
if ($opts->numbers() && is_digit($char))
|
||||||
|
{
|
||||||
|
if ($i > 0)
|
||||||
|
{
|
||||||
|
$prevChar = $this->render[$i - 1];
|
||||||
|
if ( ! is_separator($prevChar))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
$this->hl[$i] = Highlight::NUMBER;
|
||||||
|
$i++;
|
||||||
|
|
||||||
|
if ($i < strlen($this->render))
|
||||||
|
{
|
||||||
|
$nextChar = $this->render[$i];
|
||||||
|
if ($nextChar !== '.' && ! is_digit($nextChar))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightWord(int &$i, array $keywords, int $syntaxType): bool
|
||||||
|
{
|
||||||
|
if ($i > 0)
|
||||||
|
{
|
||||||
|
$prevChar = $this->render[$i - 1];
|
||||||
|
if ( ! is_separator($prevChar))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($keywords as $k)
|
||||||
|
{
|
||||||
|
$klen = strlen($k);
|
||||||
|
$nextCharOffset = $i + $klen;
|
||||||
|
$isEndOfLine = $nextCharOffset >= $this->rsize;
|
||||||
|
$nextChar = ($isEndOfLine) ? RawKeyCode::NULL : $this->render[$nextCharOffset];
|
||||||
|
|
||||||
|
if (substr($this->render, $i, $klen) === $k && is_separator($nextChar))
|
||||||
|
{
|
||||||
|
array_replace_range($this->hl, $i, $klen, $syntaxType);
|
||||||
|
$i += $klen;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightChar(int &$i, array $chars, int $syntaxType): bool
|
||||||
|
{
|
||||||
|
if ($this->hl[$i] !== Highlight::NORMAL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$char = $this->render[$i];
|
||||||
|
|
||||||
|
if (in_array($char, $chars, TRUE))
|
||||||
|
{
|
||||||
|
$this->hl[$i] = $syntaxType;
|
||||||
|
$i++;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightPrimaryKeywords(int &$i, Syntax $opts): bool
|
||||||
|
{
|
||||||
|
return $this->highlightWord($i, $opts->keywords1, Highlight::KEYWORD1);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightSecondaryKeywords(int &$i, Syntax $opts): bool
|
||||||
|
{
|
||||||
|
return $this->highlightWord($i, $opts->keywords2, Highlight::KEYWORD2);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightOperators(int &$i, Syntax $opts): bool
|
||||||
|
{
|
||||||
|
return $this->highlightWord($i, $opts->operators, Highlight::OPERATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightCommonOperators(int &$i): bool
|
||||||
|
{
|
||||||
|
return $this->highlightChar(
|
||||||
|
$i,
|
||||||
|
['+', '-', '*', '/', '<', '^', '>', '%', '=', ':', ',', ';', '&', '~'],
|
||||||
|
Highlight::OPERATOR
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightCommonDelimeters(int &$i): bool
|
||||||
|
{
|
||||||
|
return $this->highlightChar(
|
||||||
|
$i,
|
||||||
|
['{', '}', '[', ']', '(', ')'],
|
||||||
|
Highlight::DELIMITER
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightCharacter(int &$i, Syntax $opts): bool
|
||||||
|
{
|
||||||
|
if (($i + 1) >= $this->rsize)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$char = $this->render[$i];
|
||||||
|
$nextChar = $this->render[$i + 1];
|
||||||
|
|
||||||
|
if ($opts->characters() && $char === "'")
|
||||||
|
{
|
||||||
|
$offset = ($nextChar === '\\') ? $i + 2 : $i + 1;
|
||||||
|
$closingIndex = strpos($this->render, "'", $offset);
|
||||||
|
if ($closingIndex === false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$closingChar = $this->render[$closingIndex];
|
||||||
|
if ($closingChar === "'")
|
||||||
|
{
|
||||||
|
array_replace_range($this->hl, $i, $closingIndex - $i + 1, Highlight::CHARACTER);
|
||||||
|
$i = $closingIndex + 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightComment(int &$i, Syntax $opts): bool
|
||||||
|
{
|
||||||
|
if ( ! $opts->comments())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scs = $opts->singleLineCommentStart;
|
||||||
|
$scsLen = strlen($scs);
|
||||||
|
|
||||||
|
if ($scsLen > 0 && substr($this->render, $i, $scsLen) === $scs)
|
||||||
|
{
|
||||||
|
array_replace_range($this->hl, $i, $this->rsize - $i, Highlight::COMMENT);
|
||||||
|
$i = $this->rsize;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function highlightString(int &$i, Syntax $opts): bool
|
||||||
|
{
|
||||||
|
$char = $this->render[$i];
|
||||||
|
|
||||||
|
// If there's a separate character type, highlight that separately
|
||||||
|
if ($opts->hasChar() && $char === "'")
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($opts->strings() && ($char === '"' || $char === '\''))
|
||||||
|
{
|
||||||
|
$quote = $char;
|
||||||
|
$this->hl[$i] = Highlight::STRING;
|
||||||
|
$i++;
|
||||||
|
|
||||||
|
while ($i < $this->rsize)
|
||||||
|
{
|
||||||
|
$char = $this->render[$i];
|
||||||
$this->hl[$i] = Highlight::STRING;
|
$this->hl[$i] = Highlight::STRING;
|
||||||
|
|
||||||
// Check for escaped character
|
// Check for escaped character
|
||||||
|
@ -212,79 +476,21 @@ class Row {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($char === $inString)
|
// End of the string!
|
||||||
{
|
|
||||||
$inString = '';
|
|
||||||
}
|
|
||||||
$i++;
|
$i++;
|
||||||
$prevSep = 1;
|
if ($char === $quote)
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( $char === '"' || $char === '\'')
|
|
||||||
{
|
{
|
||||||
$inString = $char;
|
|
||||||
$this->hl[$i] = Highlight::STRING;
|
|
||||||
$i++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Numbers, including decimal points
|
|
||||||
if ($this->parent->syntax->flags & Syntax::HIGHLIGHT_NUMBERS)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
($char === '.' && $prevHl === Highlight::NUMBER) ||
|
|
||||||
(($prevSep || $prevHl === Highlight::NUMBER) && is_digit($char))
|
|
||||||
)
|
|
||||||
{
|
|
||||||
$this->hl[$i] = Highlight::NUMBER;
|
|
||||||
$i++;
|
|
||||||
$prevSep = FALSE;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keywords
|
|
||||||
if ($prevSep)
|
|
||||||
{
|
|
||||||
$findKeywords = function (array $keywords, int $syntaxType) use (&$i): void
|
|
||||||
{
|
|
||||||
foreach ($keywords as $k)
|
|
||||||
{
|
|
||||||
$klen = strlen($k);
|
|
||||||
$nextCharOffset = $i + $klen;
|
|
||||||
$isEndOfLine = $nextCharOffset >= $this->rsize;
|
|
||||||
$nextChar = ($isEndOfLine) ? KeyCode::NULL : $this->render[$nextCharOffset];
|
|
||||||
|
|
||||||
if (substr($this->render, $i, $klen) === $k && is_separator($nextChar))
|
|
||||||
{
|
|
||||||
array_replace_range($this->hl, $i, $klen, $syntaxType);
|
|
||||||
$i += $klen - 1;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
$findKeywords($keywords1, Highlight::KEYWORD1);
|
return true;
|
||||||
$findKeywords($keywords2, Highlight::KEYWORD2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$prevSep = is_separator($char);
|
return false;
|
||||||
$i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$changed = $this->hlOpenComment !== $inComment;
|
protected function highlightPHP(): void
|
||||||
$this->hlOpenComment = $inComment;
|
|
||||||
if ($changed && $this->idx + 1 < $this->parent->numRows)
|
|
||||||
{
|
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
$this->parent->rows[$this->idx + 1]->updateSyntax();
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function updateSyntaxPHP():void
|
|
||||||
{
|
{
|
||||||
$rowNum = $this->idx + 1;
|
$rowNum = $this->idx + 1;
|
||||||
|
|
||||||
|
@ -295,9 +501,7 @@ class Row {
|
||||||
$this->idx < $this->parent->numRows
|
$this->idx < $this->parent->numRows
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
return;
|
return;
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$tokens = $this->parent->tokens[$rowNum];
|
$tokens = $this->parent->tokens[$rowNum];
|
||||||
|
@ -312,9 +516,7 @@ class Row {
|
||||||
{
|
{
|
||||||
if ($offset >= $this->rsize)
|
if ($offset >= $this->rsize)
|
||||||
{
|
{
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
break;
|
break;
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A multi-line comment can end in the middle of a line...
|
// A multi-line comment can end in the middle of a line...
|
||||||
|
@ -336,13 +538,11 @@ class Row {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$char = $token['char']; // ?? '';
|
$char = $token['char'];
|
||||||
$charLen = strlen($char);
|
$charLen = strlen($char);
|
||||||
if ($charLen === 0 || $offset >= $this->rsize)
|
if ($charLen === 0 || $offset >= $this->rsize)
|
||||||
{
|
{
|
||||||
// @codeCoverageIgnoreStart
|
|
||||||
continue;
|
continue;
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
}
|
}
|
||||||
$charStart = strpos($this->render, $char, $offset);
|
$charStart = strpos($this->render, $char, $offset);
|
||||||
if ($charStart === FALSE)
|
if ($charStart === FALSE)
|
||||||
|
@ -392,25 +592,20 @@ class Row {
|
||||||
$tokenHighlight = Highlight::fromPHPToken($token['type']);
|
$tokenHighlight = Highlight::fromPHPToken($token['type']);
|
||||||
$charHighlight = Highlight::fromPHPChar(trim($token['char']));
|
$charHighlight = Highlight::fromPHPChar(trim($token['char']));
|
||||||
|
|
||||||
$hl = match(true) {
|
$highlight = match(true) {
|
||||||
// Matches a predefined PHP token
|
// Matches a predefined PHP token
|
||||||
$token['type'] !== self::T_RAW && $tokenHighlight !== Highlight::NORMAL
|
$token['type'] !== T_RAW && $tokenHighlight !== Highlight::NORMAL
|
||||||
=> $tokenHighlight,
|
=> $tokenHighlight,
|
||||||
|
|
||||||
// Matches a specific syntax character
|
// Matches a specific syntax character
|
||||||
$charHighlight !== Highlight::NORMAL => $charHighlight,
|
$charHighlight !== Highlight::NORMAL => $charHighlight,
|
||||||
|
|
||||||
// Types/identifiers/keywords that don't have their own token, but are
|
|
||||||
// defined as keywords
|
|
||||||
in_array($token['char'], $this->parent->syntax->keywords2 ?? [], TRUE)
|
|
||||||
=> Highlight::KEYWORD2,
|
|
||||||
|
|
||||||
default => Highlight::NORMAL,
|
default => Highlight::NORMAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($hl !== Highlight::NORMAL)
|
if ($highlight !== Highlight::NORMAL)
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $charStart, $charLen, $hl);
|
array_replace_range($this->hl, $charStart, $charLen, $highlight);
|
||||||
$offset = $charEnd;
|
$offset = $charEnd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -419,9 +614,7 @@ class Row {
|
||||||
$this->hlOpenComment = $inComment;
|
$this->hlOpenComment = $inComment;
|
||||||
if ($changed && ($this->idx + 1) < $this->parent->numRows)
|
if ($changed && ($this->idx + 1) < $this->parent->numRows)
|
||||||
{
|
{
|
||||||
// @codeCoverageIgnoreStart
|
$this->parent->rows[$this->idx + 1]->highlight();
|
||||||
$this->parent->rows[$this->idx + 1]->updateSyntax();
|
|
||||||
// @codeCoverageIgnoreEnd
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,42 +3,101 @@
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
class Syntax {
|
class Syntax {
|
||||||
public const HIGHLIGHT_NUMBERS = (1 << 0);
|
|
||||||
public const HIGHLIGHT_STRINGS = (1 << 1);
|
|
||||||
|
|
||||||
// Tokens for PHP files
|
// Tokens for PHP files
|
||||||
public array $tokens = [];
|
public array $tokens = [];
|
||||||
|
|
||||||
public static function new(
|
public static function new(
|
||||||
string $name,
|
string $name,
|
||||||
array $extList = [],
|
|
||||||
array $keywords1 = [],
|
array $keywords1 = [],
|
||||||
array $keywords2 = [],
|
array $keywords2 = [],
|
||||||
|
array $operators = [],
|
||||||
string $slcs = '//',
|
string $slcs = '//',
|
||||||
string $mcs = '/*',
|
string $mcs = '/*',
|
||||||
string $mce = '*/',
|
string $mce = '*/',
|
||||||
int $flags = 0,
|
bool $highlightNumbers = true,
|
||||||
|
bool $highlightStrings = true,
|
||||||
|
bool $highlightComments = true,
|
||||||
|
bool $hasCharType = false,
|
||||||
|
bool $highlightCharacters = false,
|
||||||
): self
|
): self
|
||||||
{
|
{
|
||||||
return new self($name, $extList, $keywords1, $keywords2, $slcs, $mcs, $mce, $flags);
|
return new self(
|
||||||
|
$name,
|
||||||
|
$keywords1,
|
||||||
|
$keywords2,
|
||||||
|
$operators,
|
||||||
|
$slcs,
|
||||||
|
$mcs,
|
||||||
|
$mce,
|
||||||
|
$highlightNumbers,
|
||||||
|
$hasCharType,
|
||||||
|
$highlightCharacters,
|
||||||
|
$highlightStrings,
|
||||||
|
$highlightComments,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function default(): self
|
||||||
|
{
|
||||||
|
return self::new('No filetype', slcs: '', mcs: '', mce: '', highlightNumbers: false, highlightStrings: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
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 */
|
||||||
public array $keywords2,
|
public array $keywords2,
|
||||||
|
/** Operators for the current language */
|
||||||
|
public array $operators,
|
||||||
/** Syntax to start a single line comment */
|
/** Syntax to start a single line comment */
|
||||||
public string $singleLineCommentStart,
|
public string $singleLineCommentStart,
|
||||||
/** Syntax to start a multi-line comment */
|
/** Syntax to start a multi-line comment */
|
||||||
public string $multiLineCommentStart,
|
public string $multiLineCommentStart,
|
||||||
/** Syntax to end a multi-line commment */
|
/** Syntax to end a multi-line commment */
|
||||||
public string $multiLineCommentEnd,
|
public string $multiLineCommentEnd,
|
||||||
/** Bitflags configuring the specified language syntax */
|
/** Should we highlight numbers? */
|
||||||
public int $flags,
|
private bool $highlightNumbers,
|
||||||
|
/** Does this language have a character type, separate from strings? */
|
||||||
|
private bool $hasCharType,
|
||||||
|
/** Should we highlight chars? */
|
||||||
|
private bool $highlightCharacters,
|
||||||
|
/** should we highlight Strings? */
|
||||||
|
private bool $highlightStrings,
|
||||||
|
/** should we highlight comments? */
|
||||||
|
private bool $highlightComments,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function numbers(): bool
|
||||||
|
{
|
||||||
|
return $this->highlightNumbers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function strings(): bool
|
||||||
|
{
|
||||||
|
return $this->highlightStrings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasChar(): bool
|
||||||
|
{
|
||||||
|
return $this->hasCharType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function characters(): bool
|
||||||
|
{
|
||||||
|
return $this->hasCharType && $this->highlightCharacters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mlComments(): bool
|
||||||
|
{
|
||||||
|
return $this->highlightComments
|
||||||
|
&& strlen($this->multiLineCommentStart) !== 0
|
||||||
|
&& strlen($this->multiLineCommentStart) !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function comments(): bool
|
||||||
|
{
|
||||||
|
return $this->highlightComments && strlen($this->singleLineCommentStart) !== 0;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Enum\RawKeyCode;
|
||||||
|
use Aviat\Kilo\Enum\KeyType;
|
||||||
use Aviat\Kilo\Type\TerminalSize;
|
use Aviat\Kilo\Type\TerminalSize;
|
||||||
|
|
||||||
class Terminal {
|
class Terminal {
|
||||||
|
@ -75,6 +77,52 @@ class Terminal {
|
||||||
return (is_string($input)) ? $input : '';
|
return (is_string($input)) ? $input : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the last key input from the terminal and convert to a
|
||||||
|
* more useful format
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function readKey(): string
|
||||||
|
{
|
||||||
|
$c = Terminal::read();
|
||||||
|
|
||||||
|
return match($c)
|
||||||
|
{
|
||||||
|
// Unambiguous mappings
|
||||||
|
RawKeyCode::ARROW_DOWN => KeyType::ARROW_DOWN,
|
||||||
|
RawKeyCode::ARROW_LEFT => KeyType::ARROW_LEFT,
|
||||||
|
RawKeyCode::ARROW_RIGHT => KeyType::ARROW_RIGHT,
|
||||||
|
RawKeyCode::ARROW_UP => KeyType::ARROW_UP,
|
||||||
|
RawKeyCode::DELETE => KeyType::DELETE,
|
||||||
|
RawKeyCode::ENTER => KeyType::ENTER,
|
||||||
|
RawKeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
|
||||||
|
RawKeyCode::PAGE_UP => KeyType::PAGE_UP,
|
||||||
|
|
||||||
|
// Backspace
|
||||||
|
RawKeyCode::CTRL('h'), RawKeyCode::BACKSPACE => KeyType::BACKSPACE,
|
||||||
|
|
||||||
|
// Escape
|
||||||
|
RawKeyCode::CTRL('l'), RawKeyCode::ESCAPE => KeyType::ESCAPE,
|
||||||
|
|
||||||
|
// Home Key
|
||||||
|
"\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::HOME,
|
||||||
|
|
||||||
|
// End Key
|
||||||
|
"\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::END,
|
||||||
|
|
||||||
|
default => $c,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ring the terminal bell
|
||||||
|
*/
|
||||||
|
public static function ding(): void
|
||||||
|
{
|
||||||
|
self::write(RawKeyCode::BELL);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Write to the stdout stream
|
* Write to the stdout stream
|
||||||
*
|
*
|
||||||
|
|
|
@ -4,8 +4,8 @@ namespace Aviat\Kilo\Tokens;
|
||||||
|
|
||||||
use PhpToken;
|
use PhpToken;
|
||||||
|
|
||||||
use function Aviat\Kilo\str_contains;
|
|
||||||
use function Aviat\Kilo\tabs_to_spaces;
|
use function Aviat\Kilo\tabs_to_spaces;
|
||||||
|
use const Aviat\Kilo\T_RAW;
|
||||||
|
|
||||||
class PHP8 extends PhpToken {
|
class PHP8 extends PhpToken {
|
||||||
private array $rawLines = [];
|
private array $rawLines = [];
|
||||||
|
@ -119,7 +119,7 @@ class PHP8 extends PhpToken {
|
||||||
|
|
||||||
// Simple characters, usually delimiters or single character operators
|
// Simple characters, usually delimiters or single character operators
|
||||||
$this->tokens[$lineNumber][] = [
|
$this->tokens[$lineNumber][] = [
|
||||||
'type' => -1,
|
'type' => T_RAW,
|
||||||
'typeName' => 'RAW',
|
'typeName' => 'RAW',
|
||||||
'char' => tabs_to_spaces($token),
|
'char' => tabs_to_spaces($token),
|
||||||
'line' => $lineNumber,
|
'line' => $lineNumber,
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -8,3 +8,6 @@ namespace Aviat\Kilo;
|
||||||
const KILO_VERSION = '0.3.0';
|
const KILO_VERSION = '0.3.0';
|
||||||
const KILO_TAB_STOP = 4;
|
const KILO_TAB_STOP = 4;
|
||||||
const KILO_QUIT_TIMES = 3;
|
const KILO_QUIT_TIMES = 3;
|
||||||
|
|
||||||
|
const NO_MATCH = -1;
|
||||||
|
const T_RAW = -1;
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\{Color, Highlight, KeyCode};
|
use Aviat\Kilo\Enum\{Color, Highlight, RawKeyCode};
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// ! C function/macro equivalents
|
// ! C function/macro equivalents
|
||||||
|
@ -77,12 +77,12 @@ function is_digit(string $char): bool
|
||||||
function is_space(string $char): bool
|
function is_space(string $char): bool
|
||||||
{
|
{
|
||||||
return match($char) {
|
return match($char) {
|
||||||
KeyCode::CARRIAGE_RETURN,
|
RawKeyCode::CARRIAGE_RETURN,
|
||||||
KeyCode::FORM_FEED,
|
RawKeyCode::FORM_FEED,
|
||||||
KeyCode::NEWLINE,
|
RawKeyCode::NEWLINE,
|
||||||
KeyCode::SPACE,
|
RawKeyCode::SPACE,
|
||||||
KeyCode::TAB,
|
RawKeyCode::TAB,
|
||||||
KeyCode::VERTICAL_TAB => true,
|
RawKeyCode::VERTICAL_TAB => true,
|
||||||
|
|
||||||
default => false,
|
default => false,
|
||||||
};
|
};
|
||||||
|
@ -107,7 +107,7 @@ function is_separator(string $char): bool
|
||||||
|
|
||||||
$isSep = str_contains(',.()+-/*=~%<>[];', $char);
|
$isSep = str_contains(',.()+-/*=~%<>[];', $char);
|
||||||
|
|
||||||
return is_space($char) || $char === KeyCode::NULL || $isSep;
|
return is_space($char) || $char === RawKeyCode::NULL || $isSep;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -138,7 +138,7 @@ function array_replace_range(array &$array, int $offset, int $length, mixed $val
|
||||||
* @param int|null $offset
|
* @param int|null $offset
|
||||||
* @return bool
|
* @return bool
|
||||||
*/
|
*/
|
||||||
function str_contains(string $haystack, string $str, ?int $offset = NULL): bool
|
function str_has(string $haystack, string $str, ?int $offset = NULL): bool
|
||||||
{
|
{
|
||||||
if (empty($str))
|
if (empty($str))
|
||||||
{
|
{
|
||||||
|
@ -165,6 +165,7 @@ function syntax_to_color(int $hl): int
|
||||||
Highlight::KEYWORD1 => Color::FG_YELLOW,
|
Highlight::KEYWORD1 => Color::FG_YELLOW,
|
||||||
Highlight::KEYWORD2 => Color::FG_GREEN,
|
Highlight::KEYWORD2 => Color::FG_GREEN,
|
||||||
Highlight::STRING => Color::FG_MAGENTA,
|
Highlight::STRING => Color::FG_MAGENTA,
|
||||||
|
Highlight::CHARACTER => Color::FG_BRIGHT_MAGENTA,
|
||||||
Highlight::NUMBER => Color::FG_BRIGHT_RED,
|
Highlight::NUMBER => Color::FG_BRIGHT_RED,
|
||||||
Highlight::OPERATOR => Color::FG_BRIGHT_GREEN,
|
Highlight::OPERATOR => Color::FG_BRIGHT_GREEN,
|
||||||
Highlight::VARIABLE => Color::FG_BRIGHT_CYAN,
|
Highlight::VARIABLE => Color::FG_BRIGHT_CYAN,
|
||||||
|
@ -185,99 +186,41 @@ function syntax_to_color(int $hl): int
|
||||||
*/
|
*/
|
||||||
function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string
|
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(RawKeyCode::TAB, str_repeat(RawKeyCode::SPACE, $number), $str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function error_code_name(int $code): string
|
||||||
* Generate/Get the syntax highlighting objects
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
function get_file_syntax_map(): array
|
|
||||||
{
|
{
|
||||||
static $db = [];
|
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',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (count($db) === 0)
|
function saturating_add(int $a, int $b, int $max): int
|
||||||
|
{
|
||||||
|
return ($a + $b > $max) ? $max : $a + $b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saturating_sub(int $a, int $b): int
|
||||||
|
{
|
||||||
|
if ($b > $a)
|
||||||
{
|
{
|
||||||
$db = [
|
return 0;
|
||||||
Syntax::new(
|
|
||||||
'C',
|
|
||||||
['.c', '.h', '.cpp'],
|
|
||||||
[
|
|
||||||
'continue', 'typedef', 'switch', 'return', 'static', 'while', 'break', 'struct',
|
|
||||||
'union', 'class', 'else', 'enum', 'for', 'case', 'if',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'#include', 'unsigned', '#define', '#ifndef', 'double', 'signed', '#endif',
|
|
||||||
'#ifdef', 'float', '#error', '#undef', 'long', 'char', 'int', 'void', '#if',
|
|
||||||
],
|
|
||||||
'//',
|
|
||||||
'/*',
|
|
||||||
'*/',
|
|
||||||
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
|
|
||||||
),
|
|
||||||
Syntax::new(
|
|
||||||
'CSS',
|
|
||||||
['.css', '.less', '.sass', 'scss'],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
'',
|
|
||||||
'/*',
|
|
||||||
'*/',
|
|
||||||
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
|
|
||||||
),
|
|
||||||
Syntax::new(
|
|
||||||
'JavaScript',
|
|
||||||
['.js', '.jsx', '.ts', '.tsx', '.jsm', '.mjs', '.es'],
|
|
||||||
[
|
|
||||||
'instanceof', 'continue', 'debugger', 'function', 'default', 'extends',
|
|
||||||
'finally', 'delete', 'export', 'import', 'return', 'switch', 'typeof',
|
|
||||||
'break', 'catch', 'class', 'const', 'super', 'throw', 'while', 'yield',
|
|
||||||
'case', 'else', 'this', 'void', 'with', 'from', 'for', 'new', 'try',
|
|
||||||
'var', 'do', 'if', 'in', 'as',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'=>', 'Number', 'String', 'Object', 'Math', 'JSON', 'Boolean',
|
|
||||||
],
|
|
||||||
'//',
|
|
||||||
'/*',
|
|
||||||
'*/',
|
|
||||||
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
|
|
||||||
),
|
|
||||||
Syntax::new(
|
|
||||||
'PHP',
|
|
||||||
['.php', 'kilo'],
|
|
||||||
[],
|
|
||||||
[],
|
|
||||||
'//',
|
|
||||||
'/*',
|
|
||||||
'*/',
|
|
||||||
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
|
|
||||||
),
|
|
||||||
Syntax::new(
|
|
||||||
'Rust',
|
|
||||||
['.rs'],
|
|
||||||
[
|
|
||||||
'continue', 'return', 'static', 'struct', 'unsafe', 'break', 'const', 'crate',
|
|
||||||
'extern', 'match', 'super', 'trait', 'where', 'else', 'enum', 'false', 'impl',
|
|
||||||
'loop', 'move', 'self', 'type', 'while', 'for', 'let', 'mod', 'pub', 'ref', 'true',
|
|
||||||
'use', 'mut', 'as', 'fn', 'if', 'in',
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'DoubleEndedIterator', 'ExactSizeIterator', 'IntoIterator', 'PartialOrd', 'PartialEq',
|
|
||||||
'Iterator', 'ToString', 'Default', 'ToOwned', 'Extend', 'FnOnce', 'Option', 'String',
|
|
||||||
'AsMut', 'AsRef', 'Clone', 'Debug', 'FnMut', 'Sized', 'Unpin', 'array', 'isize',
|
|
||||||
'usize', '&str', 'Copy', 'Drop', 'From', 'Into', 'None', 'Self', 'Send', 'Some',
|
|
||||||
'Sync', 'bool', 'char', 'i128', 'u128', 'Box', 'Err', 'Ord', 'Vec', 'dyn', 'f32',
|
|
||||||
'f64', 'i16', 'i32', 'i64', 'str', 'u16', 'u32', 'u64', 'Eq', 'Fn', 'Ok', 'i8', 'u8',
|
|
||||||
],
|
|
||||||
'//',
|
|
||||||
'/*',
|
|
||||||
'*/',
|
|
||||||
Syntax::HIGHLIGHT_NUMBERS | Syntax::HIGHLIGHT_STRINGS,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $db;
|
return $a - $b;
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo\Tests\Traits;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
use Aviat\Kilo\ANSI;
|
use Aviat\Kilo\ANSI;
|
||||||
use Aviat\Kilo\Enum\Color;
|
use Aviat\Kilo\Enum\Color;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo\Tests\Traits;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
use Aviat\Kilo\Editor;
|
use Aviat\Kilo\Editor;
|
||||||
use Aviat\Kilo\Type\TerminalSize;
|
use Aviat\Kilo\Type\TerminalSize;
|
||||||
|
@ -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();
|
||||||
|
@ -49,7 +41,7 @@ class EditorTest extends TestCase {
|
||||||
{
|
{
|
||||||
$editor = MockEditor::mock('test.php');
|
$editor = MockEditor::mock('test.php');
|
||||||
|
|
||||||
$state = json_encode($editor, JSON_THROW_ON_ERROR);
|
$state = json_encode($editor->__debugInfo(), JSON_THROW_ON_ERROR);
|
||||||
$this->assertMatchesJsonSnapshot($state);
|
$this->assertMatchesJsonSnapshot($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +49,7 @@ class EditorTest extends TestCase {
|
||||||
{
|
{
|
||||||
$editor = MockEditor::mock('src/ffi.h');
|
$editor = MockEditor::mock('src/ffi.h');
|
||||||
|
|
||||||
$state = json_encode($editor, JSON_THROW_ON_ERROR);
|
$state = json_encode($editor->__debugInfo(), JSON_THROW_ON_ERROR);
|
||||||
$this->assertMatchesJsonSnapshot($state);
|
$this->assertMatchesJsonSnapshot($state);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ namespace Aviat\Kilo\Tests\Enum;
|
||||||
|
|
||||||
use function Aviat\Kilo\ctrl_key;
|
use function Aviat\Kilo\ctrl_key;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\KeyCode;
|
use Aviat\Kilo\Enum\RawKeyCode;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class KeyCodeTest extends TestCase {
|
class KeyCodeTest extends TestCase {
|
||||||
|
@ -16,7 +16,7 @@ class KeyCodeTest extends TestCase {
|
||||||
$ord = $i;
|
$ord = $i;
|
||||||
$expected = chr($ord);
|
$expected = chr($ord);
|
||||||
|
|
||||||
$actual = KeyCode::CTRL($char);
|
$actual = RawKeyCode::CTRL($char);
|
||||||
|
|
||||||
$this->assertEquals(ctrl_key($char), $ord, 'chr(ctrl_key) !== CTRL');
|
$this->assertEquals(ctrl_key($char), $ord, 'chr(ctrl_key) !== CTRL');
|
||||||
$this->assertEquals($expected, $actual, "CTRL+'{$char}' should return chr($ord)");
|
$this->assertEquals($expected, $actual, "CTRL+'{$char}' should return chr($ord)");
|
||||||
|
@ -25,13 +25,13 @@ class KeyCodeTest extends TestCase {
|
||||||
|
|
||||||
public function testNullOnInvalidChar(): void
|
public function testNullOnInvalidChar(): void
|
||||||
{
|
{
|
||||||
$this->assertNull(KeyCode::CTRL("\t"));
|
$this->assertNull(RawKeyCode::CTRL("\t"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSameOutputOnUpperOrLower(): void
|
public function testSameOutputOnUpperOrLower(): void
|
||||||
{
|
{
|
||||||
$lower = KeyCode::CTRL('v');
|
$lower = RawKeyCode::CTRL('v');
|
||||||
$upper = KeyCode::CTRL('V');
|
$upper = RawKeyCode::CTRL('V');
|
||||||
|
|
||||||
$this->assertEquals($lower, $upper);
|
$this->assertEquals($lower, $upper);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,13 +8,12 @@ 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;
|
||||||
use function Aviat\Kilo\is_separator;
|
use function Aviat\Kilo\is_separator;
|
||||||
use function Aviat\Kilo\is_space;
|
use function Aviat\Kilo\is_space;
|
||||||
use function Aviat\Kilo\str_contains;
|
use function Aviat\Kilo\str_has;
|
||||||
use function Aviat\Kilo\syntax_to_color;
|
use function Aviat\Kilo\syntax_to_color;
|
||||||
use function Aviat\Kilo\tabs_to_spaces;
|
use function Aviat\Kilo\tabs_to_spaces;
|
||||||
|
|
||||||
|
@ -97,28 +96,23 @@ 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
|
||||||
$this->assertTrue(str_contains(' vcd', 'vcd', 2));
|
$this->assertTrue(str_has(' vcd', 'vcd', 2));
|
||||||
|
|
||||||
$this->assertFalse(str_contains('', "\0"));
|
$this->assertFalse(str_has('', "\0"));
|
||||||
|
|
||||||
// An empty search string returns false
|
// An empty search string returns false
|
||||||
$this->assertFalse(str_contains('', ''));
|
$this->assertFalse(str_has('', ''));
|
||||||
|
|
||||||
$this->assertTrue(str_contains('alphabet', 'phab'));
|
$this->assertTrue(str_has('alphabet', 'phab'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_tabs_to_spaces(): void
|
public function test_tabs_to_spaces(): void
|
||||||
{
|
{
|
||||||
$original = "\t\t\t{";
|
$original = "\t\t\t{";
|
||||||
$this->assertFalse(str_contains(tabs_to_spaces($original), "\t"));
|
$this->assertFalse(str_has(tabs_to_spaces($original), "\t"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_array_replace_range_length_1(): void
|
public function test_array_replace_range_length_1(): void
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo\Tests\Traits;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
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->document->insertRow(0, '');
|
||||||
|
|
||||||
|
$this->row = $this->document->rows[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSanity(): void
|
public function testSanity(): void
|
||||||
|
@ -28,13 +33,14 @@ class RowTest extends TestCase {
|
||||||
|
|
||||||
public function testSetRunsUpdate(): void
|
public function testSetRunsUpdate(): void
|
||||||
{
|
{
|
||||||
$this->row->chars = 'abcde';
|
$this->row->setChars('abcde');
|
||||||
|
$this->assertNotEmpty($this->row->chars);
|
||||||
$this->assertEquals('abcde', $this->row->render);
|
$this->assertEquals('abcde', $this->row->render);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test__toString(): void
|
public function test__toString(): void
|
||||||
{
|
{
|
||||||
$this->row->chars = 'abcde';
|
$this->row->setChars('abcde');
|
||||||
$this->assertEquals("abcde\n", (string)$this->row);
|
$this->assertEquals("abcde\n", (string)$this->row);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,42 +59,41 @@ 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->setChars('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->setChars('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->setChars('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->setChars('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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo\Tests\Traits;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
use Aviat\Kilo\{Termios, TermiosException};
|
use Aviat\Kilo\{Termios, TermiosException};
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,4 +1,107 @@
|
||||||
{
|
{
|
||||||
|
"cursor": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"document": {
|
||||||
|
"fileType": {
|
||||||
|
"name": "C",
|
||||||
|
"syntax": {
|
||||||
|
"tokens": [],
|
||||||
|
"filetype": "C",
|
||||||
|
"keywords1": [
|
||||||
|
"auto",
|
||||||
|
"break",
|
||||||
|
"case",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"default",
|
||||||
|
"do",
|
||||||
|
"typedef",
|
||||||
|
"switch",
|
||||||
|
"return",
|
||||||
|
"static",
|
||||||
|
"while",
|
||||||
|
"break",
|
||||||
|
"struct",
|
||||||
|
"extern",
|
||||||
|
"union",
|
||||||
|
"class",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"for",
|
||||||
|
"case",
|
||||||
|
"if",
|
||||||
|
"inline",
|
||||||
|
"register",
|
||||||
|
"restrict",
|
||||||
|
"return",
|
||||||
|
"sizeof",
|
||||||
|
"switch",
|
||||||
|
"typedef",
|
||||||
|
"union",
|
||||||
|
"volatile"
|
||||||
|
],
|
||||||
|
"keywords2": [
|
||||||
|
"#include",
|
||||||
|
"unsigned",
|
||||||
|
"#define",
|
||||||
|
"#ifndef",
|
||||||
|
"double",
|
||||||
|
"signed",
|
||||||
|
"#endif",
|
||||||
|
"#ifdef",
|
||||||
|
"float",
|
||||||
|
"#error",
|
||||||
|
"#undef",
|
||||||
|
"#elif",
|
||||||
|
"long",
|
||||||
|
"char",
|
||||||
|
"int",
|
||||||
|
"void",
|
||||||
|
"#if",
|
||||||
|
"uint32_t",
|
||||||
|
"wchar_t",
|
||||||
|
"int32_t",
|
||||||
|
"int64_t",
|
||||||
|
"uint64_t",
|
||||||
|
"int16_t",
|
||||||
|
"uint16_t",
|
||||||
|
"uint8_t",
|
||||||
|
"int8_t"
|
||||||
|
],
|
||||||
|
"operators": [
|
||||||
|
"<=>",
|
||||||
|
"<<=",
|
||||||
|
">>=",
|
||||||
|
"++",
|
||||||
|
"--",
|
||||||
|
"==",
|
||||||
|
"!=",
|
||||||
|
">=",
|
||||||
|
"<=",
|
||||||
|
"&&",
|
||||||
|
"||",
|
||||||
|
"<<",
|
||||||
|
">>",
|
||||||
|
"+=",
|
||||||
|
"-=",
|
||||||
|
"*=",
|
||||||
|
"\/=",
|
||||||
|
"%=",
|
||||||
|
"&=",
|
||||||
|
"|=",
|
||||||
|
"^=",
|
||||||
|
"->",
|
||||||
|
"::"
|
||||||
|
],
|
||||||
|
"singleLineCommentStart": "\/\/",
|
||||||
|
"multiLineCommentStart": "\/*",
|
||||||
|
"multiLineCommentEnd": "*\/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tokens": [],
|
||||||
|
"filename": "src\/ffi.h",
|
||||||
"rows": [
|
"rows": [
|
||||||
{
|
{
|
||||||
"render": "\/**",
|
"render": "\/**",
|
||||||
|
@ -517,11 +620,11 @@
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
7,
|
||||||
0,
|
|
||||||
0,
|
0,
|
||||||
5,
|
5,
|
||||||
5,
|
5,
|
||||||
|
@ -551,7 +654,7 @@
|
||||||
5,
|
5,
|
||||||
5,
|
5,
|
||||||
5,
|
5,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 16
|
"idx": 16
|
||||||
},
|
},
|
||||||
|
@ -570,12 +673,12 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
0,
|
13,
|
||||||
5,
|
13,
|
||||||
5,
|
13,
|
||||||
5,
|
7
|
||||||
0
|
|
||||||
],
|
],
|
||||||
"idx": 17
|
"idx": 17
|
||||||
},
|
},
|
||||||
|
@ -880,7 +983,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 25
|
"idx": 25
|
||||||
},
|
},
|
||||||
|
@ -958,7 +1061,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 28
|
"idx": 28
|
||||||
},
|
},
|
||||||
|
@ -1036,7 +1139,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 31
|
"idx": 31
|
||||||
},
|
},
|
||||||
|
@ -1108,7 +1211,7 @@
|
||||||
{
|
{
|
||||||
"render": "{",
|
"render": "{",
|
||||||
"hl": [
|
"hl": [
|
||||||
0
|
9
|
||||||
],
|
],
|
||||||
"idx": 35
|
"idx": 35
|
||||||
},
|
},
|
||||||
|
@ -1160,7 +1263,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 37
|
"idx": 37
|
||||||
},
|
},
|
||||||
|
@ -1218,7 +1321,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 40
|
"idx": 40
|
||||||
},
|
},
|
||||||
|
@ -1277,7 +1380,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 43
|
"idx": 43
|
||||||
},
|
},
|
||||||
|
@ -1334,7 +1437,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 46
|
"idx": 46
|
||||||
},
|
},
|
||||||
|
@ -1391,11 +1494,11 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
9,
|
||||||
6,
|
6,
|
||||||
6,
|
6,
|
||||||
0,
|
9,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 49
|
"idx": 49
|
||||||
},
|
},
|
||||||
|
@ -1468,6 +1571,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -1477,16 +1581,15 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
7
|
||||||
0
|
|
||||||
],
|
],
|
||||||
"idx": 52
|
"idx": 52
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"render": "};",
|
"render": "};",
|
||||||
"hl": [
|
"hl": [
|
||||||
0,
|
9,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 53
|
"idx": 53
|
||||||
},
|
},
|
||||||
|
@ -1512,14 +1615,14 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
9,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
7,
|
||||||
0,
|
0,
|
||||||
3,
|
3,
|
||||||
3,
|
3,
|
||||||
|
@ -1536,6 +1639,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -1545,9 +1649,8 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
9,
|
||||||
0,
|
7
|
||||||
0
|
|
||||||
],
|
],
|
||||||
"idx": 55
|
"idx": 55
|
||||||
},
|
},
|
||||||
|
@ -1568,14 +1671,14 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
9,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
7,
|
||||||
0,
|
0,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
|
@ -1597,13 +1700,13 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
0,
|
3,
|
||||||
0,
|
3,
|
||||||
0,
|
3,
|
||||||
0,
|
3,
|
||||||
0,
|
3,
|
||||||
0,
|
|
||||||
0,
|
0,
|
||||||
3,
|
3,
|
||||||
3,
|
3,
|
||||||
|
@ -1620,6 +1723,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
@ -1629,9 +1733,8 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
9,
|
||||||
0,
|
7
|
||||||
0
|
|
||||||
],
|
],
|
||||||
"idx": 56
|
"idx": 56
|
||||||
},
|
},
|
||||||
|
@ -1853,7 +1956,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
9
|
||||||
],
|
],
|
||||||
"idx": 61
|
"idx": 61
|
||||||
},
|
},
|
||||||
|
@ -1885,7 +1988,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 62
|
"idx": 62
|
||||||
},
|
},
|
||||||
|
@ -1917,7 +2020,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 63
|
"idx": 63
|
||||||
},
|
},
|
||||||
|
@ -1952,7 +2055,7 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 64
|
"idx": 64
|
||||||
},
|
},
|
||||||
|
@ -1987,15 +2090,15 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 65
|
"idx": 65
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"render": "};",
|
"render": "};",
|
||||||
"hl": [
|
"hl": [
|
||||||
0,
|
9,
|
||||||
0
|
7
|
||||||
],
|
],
|
||||||
"idx": 66
|
"idx": 66
|
||||||
},
|
},
|
||||||
|
@ -2012,75 +2115,40 @@
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
9,
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
4,
|
||||||
|
7,
|
||||||
0,
|
0,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
4,
|
4,
|
||||||
0,
|
7,
|
||||||
0,
|
|
||||||
4,
|
|
||||||
4,
|
|
||||||
4,
|
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
0,
|
9,
|
||||||
0,
|
7
|
||||||
0
|
|
||||||
],
|
],
|
||||||
"idx": 67
|
"idx": 67
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dirty": false,
|
"dirty": false
|
||||||
"filename": "src\/ffi.h",
|
|
||||||
"syntax": {
|
|
||||||
"tokens": [],
|
|
||||||
"filetype": "C",
|
|
||||||
"filematch": [
|
|
||||||
".c",
|
|
||||||
".h",
|
|
||||||
".cpp"
|
|
||||||
],
|
|
||||||
"keywords1": [
|
|
||||||
"continue",
|
|
||||||
"typedef",
|
|
||||||
"switch",
|
|
||||||
"return",
|
|
||||||
"static",
|
|
||||||
"while",
|
|
||||||
"break",
|
|
||||||
"struct",
|
|
||||||
"union",
|
|
||||||
"class",
|
|
||||||
"else",
|
|
||||||
"enum",
|
|
||||||
"for",
|
|
||||||
"case",
|
|
||||||
"if"
|
|
||||||
],
|
|
||||||
"keywords2": [
|
|
||||||
"#include",
|
|
||||||
"unsigned",
|
|
||||||
"#define",
|
|
||||||
"#ifndef",
|
|
||||||
"double",
|
|
||||||
"signed",
|
|
||||||
"#endif",
|
|
||||||
"#ifdef",
|
|
||||||
"float",
|
|
||||||
"#error",
|
|
||||||
"#undef",
|
|
||||||
"long",
|
|
||||||
"char",
|
|
||||||
"int",
|
|
||||||
"void",
|
|
||||||
"#if"
|
|
||||||
],
|
|
||||||
"singleLineCommentStart": "\/\/",
|
|
||||||
"multiLineCommentStart": "\/*",
|
|
||||||
"multiLineCommentEnd": "*\/",
|
|
||||||
"flags": 3
|
|
||||||
},
|
},
|
||||||
"tokens": []
|
"offset": {
|
||||||
|
"x": 0,
|
||||||
|
"y": 0
|
||||||
|
},
|
||||||
|
"renderX": 0,
|
||||||
|
"terminalSize": {
|
||||||
|
"rows": 21,
|
||||||
|
"cols": 80
|
||||||
|
},
|
||||||
|
"statusMessage": {
|
||||||
|
"text": "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find",
|
||||||
|
"len": 51,
|
||||||
|
"time": 1234567890
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,27 +4,36 @@
|
||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
"document": {
|
"document": {
|
||||||
"syntax": null,
|
"fileType": {
|
||||||
|
"name": "No filetype",
|
||||||
|
"syntax": {
|
||||||
"tokens": [],
|
"tokens": [],
|
||||||
|
"filetype": "No filetype",
|
||||||
|
"keywords1": [],
|
||||||
|
"keywords2": [],
|
||||||
|
"operators": [],
|
||||||
|
"singleLineCommentStart": "",
|
||||||
|
"multiLineCommentStart": "",
|
||||||
|
"multiLineCommentEnd": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tokens": [],
|
||||||
|
"filename": "",
|
||||||
"rows": [],
|
"rows": [],
|
||||||
"filename": null
|
"dirty": false
|
||||||
},
|
},
|
||||||
"offset": {
|
"offset": {
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0
|
"y": 0
|
||||||
},
|
},
|
||||||
"dirty": false,
|
|
||||||
"filename": "",
|
|
||||||
"renderX": 0,
|
"renderX": 0,
|
||||||
"rows": [],
|
|
||||||
"terminalSize": {
|
"terminalSize": {
|
||||||
"rows": 21,
|
"rows": 21,
|
||||||
"cols": 80
|
"cols": 80
|
||||||
},
|
},
|
||||||
"statusMessage": {
|
"statusMessage": {
|
||||||
"text": "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find",
|
"text": "HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find",
|
||||||
|
"len": 51,
|
||||||
"time": 1234567890
|
"time": 1234567890
|
||||||
},
|
}
|
||||||
"syntax": null,
|
|
||||||
"tokens": []
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user