Compare commits
28 Commits
Author | SHA1 | Date | |
---|---|---|---|
5828895eec | |||
045bf89f45 | |||
223371fb4a | |||
|
8cb783ed2f | ||
|
573f8c0cde | ||
|
7554c69d63 | ||
|
4a5f074f28 | ||
|
b8cb08c8a8 | ||
|
998102816e | ||
|
fd478b697f | ||
dd4b707d12 | |||
|
26ea12883a | ||
|
c8be79d2d8 | ||
|
048f2c5f38 | ||
|
3b4ed0f245 | ||
|
63b93b1dd9 | ||
|
5598636df1 | ||
|
f2177e0b40 | ||
|
2db285d54f | ||
|
7e19021af2 | ||
|
5887faf7d4 | ||
|
07fded57e8 | ||
|
6a9994c811 | ||
|
36da56c519 | ||
|
9214c16595 | ||
|
eb3f7f848d | ||
|
131e6177e1 | ||
|
4d91919fb5 |
@ -1,4 +1,4 @@
|
|||||||
FROM php:8-alpine3.13
|
FROM php:8.1-alpine
|
||||||
|
|
||||||
RUN apk add --no-cache --virtual .persistent-deps libffi-dev \
|
RUN apk add --no-cache --virtual .persistent-deps libffi-dev \
|
||||||
&& docker-php-ext-configure ffi --with-ffi \
|
&& docker-php-ext-configure ffi --with-ffi \
|
||||||
|
4
Jenkinsfile
vendored
4
Jenkinsfile
vendored
@ -5,9 +5,9 @@ pipeline {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
stages {
|
stages {
|
||||||
stage('PHP 7.4') {
|
stage('PHP 8.1') {
|
||||||
steps {
|
steps {
|
||||||
sh 'apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing php8-phpdbg'
|
sh 'apk add --no-cache -X http://dl-cdn.alpinelinux.org/alpine/edge/testing php-phpdbg'
|
||||||
sh 'curl -sS https://getcomposer.org/installer | php'
|
sh 'curl -sS https://getcomposer.org/installer | php'
|
||||||
sh 'php composer.phar install'
|
sh 'php composer.phar install'
|
||||||
sh 'phpdbg -dffi.enable=1 -qrr -- ./vendor/bin/phpunit --coverage-text --coverage-clover clover.xml --colors=never -c phpunit.xml tests'
|
sh 'phpdbg -dffi.enable=1 -qrr -- ./vendor/bin/phpunit --coverage-text --coverage-clover clover.xml --colors=never -c phpunit.xml tests'
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
A reimplementation of the [Kilo](https://viewsourcecode.org/snaptoken/kilo/index.html) tutorial in PHP. Also has some inspiration from the [Hecto](https://www.philippflenker.com/hecto/) text editor tutorial. Requires PHP 8 and FFI.
|
A reimplementation of the [Kilo](https://viewsourcecode.org/snaptoken/kilo/index.html) tutorial in PHP. Also has some inspiration from the [Hecto](https://www.philippflenker.com/hecto/) text editor tutorial. Requires PHP 8 and FFI.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* PHP 8
|
* PHP 8.1
|
||||||
* FFI enabled
|
* FFI enabled
|
||||||
|
|
||||||
## Implementation notes:
|
## Implementation notes:
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
{
|
{
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"files": [
|
"files": [
|
||||||
"src/constants.php",
|
"src/Kilo.php"
|
||||||
"src/functions.php"
|
|
||||||
],
|
],
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Aviat\\Kilo\\": "src/"
|
"Aviat\\Kilo\\": "src/"
|
||||||
@ -16,9 +15,8 @@
|
|||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"phpunit/phpunit": "^9.5.0",
|
"phpunit/phpunit": "^9.5.0",
|
||||||
"phpstan/phpstan": "^0.12.19",
|
"phpstan/phpstan": "^1.8.2",
|
||||||
"rector/rector": "^0.10.9",
|
"spatie/phpunit-snapshot-assertions": "^4.2.15"
|
||||||
"spatie/phpunit-snapshot-assertions": "^4.2.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c phpunit.xml tests",
|
"coverage": "phpdbg -qrr -- vendor/bin/phpunit -c phpunit.xml tests",
|
||||||
@ -28,6 +26,6 @@
|
|||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-ffi": "*",
|
"ext-ffi": "*",
|
||||||
"php": ">= 7.4.0"
|
"php": ">= 8.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4551
composer.lock
generated
4551
composer.lock
generated
File diff suppressed because it is too large
Load Diff
30
kilo
30
kilo
@ -3,14 +3,34 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
use Aviat\Kilo\Terminal\Termios;
|
||||||
|
|
||||||
|
require_once __DIR__ . '/src/Kilo.php';
|
||||||
|
|
||||||
|
// Remove the composer install requirement by
|
||||||
|
// manually handling autoloading
|
||||||
|
spl_autoload_register(static function (string $class): void {
|
||||||
|
$nsParts = explode('\\', $class);
|
||||||
|
array_shift($nsParts);
|
||||||
|
array_shift($nsParts);
|
||||||
|
|
||||||
|
array_unshift($nsParts, __DIR__, 'src');
|
||||||
|
$file = implode(DIRECTORY_SEPARATOR, $nsParts) . '.php';
|
||||||
|
|
||||||
|
if (file_exists($file))
|
||||||
|
{
|
||||||
|
require_once($file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Log notices/errors/warnings to file
|
// Log notices/errors/warnings to file
|
||||||
set_error_handler(static function (
|
set_error_handler(static function (
|
||||||
$errno,
|
int $errno,
|
||||||
$errstr,
|
string $errstr,
|
||||||
$errfile,
|
string $errfile,
|
||||||
$errline
|
int $errline,
|
||||||
) {
|
) {
|
||||||
$msg = print_r([
|
$msg = print_r([
|
||||||
'code' => error_code_name($errno),
|
'code' => error_code_name($errno),
|
||||||
|
54
rector.php
54
rector.php
@ -1,54 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
use Rector\Core\Configuration\Option;
|
|
||||||
use Rector\DowngradePhp80\Rector\Catch_\DowngradeNonCapturingCatchesRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\Class_\DowngradePropertyPromotionRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\ClassConstFetch\DowngradeClassOnObjectToGetClassRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\ClassMethod\DowngradeStaticTypeDeclarationRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\ClassMethod\DowngradeTrailingCommasInParamUseRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\Expression\DowngradeMatchToSwitchRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\FunctionLike\DowngradeMixedTypeDeclarationRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\FunctionLike\DowngradeUnionTypeDeclarationRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\NullsafeMethodCall\DowngradeNullsafeToTernaryOperatorRector;
|
|
||||||
use Rector\DowngradePhp80\Rector\Property\DowngradeUnionTypeTypedPropertyRector;
|
|
||||||
use Rector\Generics\Rector\Class_\GenericsPHPStormMethodAnnotationRector;
|
|
||||||
use Rector\Set\ValueObject\SetList;
|
|
||||||
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
|
|
||||||
|
|
||||||
return static function (ContainerConfigurator $containerConfigurator): void {
|
|
||||||
// get parameters
|
|
||||||
// $parameters = $containerConfigurator->parameters();
|
|
||||||
//
|
|
||||||
// // Define what rule sets will be applied
|
|
||||||
// $parameters->set(Option::SETS, [
|
|
||||||
// SetList::DEAD_CODE,
|
|
||||||
// SetList::PHP_80,
|
|
||||||
// ]);
|
|
||||||
|
|
||||||
// get services (needed for register a single rule)
|
|
||||||
$services = $containerConfigurator->services();
|
|
||||||
|
|
||||||
|
|
||||||
$rules = [
|
|
||||||
// PHP8 downgrade
|
|
||||||
DowngradeClassOnObjectToGetClassRector::class,
|
|
||||||
DowngradeMatchToSwitchRector::class,
|
|
||||||
DowngradeMixedTypeDeclarationRector::class,
|
|
||||||
DowngradeNonCapturingCatchesRector::class,
|
|
||||||
DowngradeNullsafeToTernaryOperatorRector::class,
|
|
||||||
DowngradePropertyPromotionRector::class,
|
|
||||||
DowngradeStaticTypeDeclarationRector::class,
|
|
||||||
DowngradeTrailingCommasInParamUseRector::class,
|
|
||||||
DowngradeUnionTypeDeclarationRector::class,
|
|
||||||
DowngradeUnionTypeTypedPropertyRector::class,
|
|
||||||
|
|
||||||
GenericsPHPStormMethodAnnotationRector::class,
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($rules as $rule)
|
|
||||||
{
|
|
||||||
$services->set($rule);
|
|
||||||
}
|
|
||||||
};
|
|
@ -78,6 +78,12 @@ class Document {
|
|||||||
$this->dirty = false;
|
$this->dirty = false;
|
||||||
$this->selectSyntaxHighlight();
|
$this->selectSyntaxHighlight();
|
||||||
|
|
||||||
|
// Add a row to empty files so it can be properly edited
|
||||||
|
if ($this->isEmpty())
|
||||||
|
{
|
||||||
|
$this->rows[] = Row::new($this, "", 0);
|
||||||
|
}
|
||||||
|
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,14 +101,14 @@ class Document {
|
|||||||
return $res;
|
return $res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function insert(Point $at, string $c): void
|
public function insert(Point $at, string|KeyType $c): void
|
||||||
{
|
{
|
||||||
if ($at->y > $this->numRows)
|
if ($at->y > $this->numRows)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($c === KeyType::ENTER || $c === RawKeyCode::CARRIAGE_RETURN)
|
if ($c === KeyType::Enter || $c === RawKeyCode::CARRIAGE_RETURN)
|
||||||
{
|
{
|
||||||
$this->insertNewline($at);
|
$this->insertNewline($at);
|
||||||
$this->dirty = true;
|
$this->dirty = true;
|
||||||
@ -113,7 +119,7 @@ class Document {
|
|||||||
$this->dirty = true;
|
$this->dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Point $at): void
|
public function delete(Point $at, int $gutter = 0): void
|
||||||
{
|
{
|
||||||
if ($at->y > $this->numRows)
|
if ($at->y > $this->numRows)
|
||||||
{
|
{
|
||||||
@ -122,7 +128,7 @@ class Document {
|
|||||||
|
|
||||||
$row =& $this->rows[$at->y];
|
$row =& $this->rows[$at->y];
|
||||||
|
|
||||||
if ($at->x === $this->rows[$at->y]->size && $at->y + 1 < $this->numRows)
|
if (($at->x - $gutter) === $this->rows[$at->y]->size && $at->y + 1 < $this->numRows)
|
||||||
{
|
{
|
||||||
$this->rows[$at->y]->append($this->rows[$at->y + 1]->chars);
|
$this->rows[$at->y]->append($this->rows[$at->y + 1]->chars);
|
||||||
$this->deleteRow($at->y + 1);
|
$this->deleteRow($at->y + 1);
|
||||||
|
172
src/Editor.php
172
src/Editor.php
@ -2,20 +2,25 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
use Aviat\Kilo\Type\TerminalSize;
|
use Aviat\Kilo\Enum\{Highlight, KeyType, RawKeyCode, SearchDirection};
|
||||||
use Aviat\Kilo\Enum\{
|
use Aviat\Kilo\Terminal\ANSI;
|
||||||
Color,
|
use Aviat\Kilo\Terminal\Enum\Color;
|
||||||
RawKeyCode,
|
use Aviat\Kilo\Terminal\Terminal;
|
||||||
KeyType,
|
|
||||||
Highlight,
|
|
||||||
SearchDirection
|
|
||||||
};
|
|
||||||
use Aviat\Kilo\Type\{Point, StatusMessage};
|
use Aviat\Kilo\Type\{Point, StatusMessage};
|
||||||
|
use Aviat\Kilo\Type\TerminalSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // Don't highlight this!
|
* // Don't highlight this!
|
||||||
*/
|
*/
|
||||||
class Editor {
|
class Editor {
|
||||||
|
/**
|
||||||
|
* @var bool Whether to render line numbers
|
||||||
|
*/
|
||||||
|
public bool $showLineNumbers = true;
|
||||||
|
/**
|
||||||
|
* @var int The size of the line number Gutter in characters
|
||||||
|
*/
|
||||||
|
public int $numberGutter = 5;
|
||||||
/**
|
/**
|
||||||
* @var string The screen buffer
|
* @var string The screen buffer
|
||||||
*/
|
*/
|
||||||
@ -63,10 +68,6 @@ class Editor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the Editor instance with CLI arguments
|
* Create the Editor instance with CLI arguments
|
||||||
*
|
|
||||||
* @param int $argc
|
|
||||||
* @param array $argv
|
|
||||||
* @return Editor
|
|
||||||
*/
|
*/
|
||||||
public static function new(int $argc = 0, array $argv = []): Editor
|
public static function new(int $argc = 0, array $argv = []): Editor
|
||||||
{
|
{
|
||||||
@ -80,8 +81,6 @@ class Editor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The real constructor, ladies and gentlemen
|
* The real constructor, ladies and gentlemen
|
||||||
*
|
|
||||||
* @param string|null $filename
|
|
||||||
*/
|
*/
|
||||||
private function __construct(?string $filename = NULL)
|
private function __construct(?string $filename = NULL)
|
||||||
{
|
{
|
||||||
@ -126,17 +125,20 @@ class Editor {
|
|||||||
*/
|
*/
|
||||||
public function run(): void
|
public function run(): void
|
||||||
{
|
{
|
||||||
|
$this->refreshScreen();
|
||||||
|
|
||||||
while ( ! $this->shouldQuit)
|
while ( ! $this->shouldQuit)
|
||||||
{
|
{
|
||||||
$this->refreshScreen();
|
// Don't redraw unless the screen actually needs to update!
|
||||||
$this->processKeypress();
|
if ($this->processKeypress() !== false)
|
||||||
|
{
|
||||||
|
$this->refreshScreen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a status message to be displayed, using printf formatting
|
* Set a status message to be displayed, using printf formatting
|
||||||
* @param string $fmt
|
|
||||||
* @param mixed ...$args
|
|
||||||
*/
|
*/
|
||||||
public function setStatusMessage(string $fmt, mixed ...$args): void
|
public function setStatusMessage(string $fmt, mixed ...$args): void
|
||||||
{
|
{
|
||||||
@ -153,10 +155,6 @@ class Editor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Cursor X to Render X
|
* Cursor X to Render X
|
||||||
*
|
|
||||||
* @param Row $row
|
|
||||||
* @param int $cx
|
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
protected function rowCxToRx(Row $row, int $cx): int
|
protected function rowCxToRx(Row $row, int $cx): int
|
||||||
{
|
{
|
||||||
@ -175,10 +173,6 @@ class Editor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Render X to Cursor X
|
* Render X to Cursor X
|
||||||
*
|
|
||||||
* @param Row $row
|
|
||||||
* @param int $rx
|
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
protected function rowRxToCx(Row $row, int $rx): int
|
protected function rowRxToCx(Row $row, int $rx): int
|
||||||
{
|
{
|
||||||
@ -233,7 +227,7 @@ class Editor {
|
|||||||
// ! Find
|
// ! Find
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
protected function findCallback(string $query, string $key): void
|
protected function findCallback(string $query, string|KeyType $key): void
|
||||||
{
|
{
|
||||||
static $lastMatch = NO_MATCH;
|
static $lastMatch = NO_MATCH;
|
||||||
static $direction = SearchDirection::FORWARD;
|
static $direction = SearchDirection::FORWARD;
|
||||||
@ -254,11 +248,11 @@ class Editor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$direction = match ($key) {
|
$direction = match ($key) {
|
||||||
KeyType::ARROW_UP, KeyType::ARROW_LEFT => SearchDirection::BACKWARD,
|
KeyType::ArrowUp, KeyType::ArrowLeft => SearchDirection::BACKWARD,
|
||||||
default => SearchDirection::FORWARD
|
default => SearchDirection::FORWARD
|
||||||
};
|
};
|
||||||
|
|
||||||
$arrowKeys = [KeyType::ARROW_UP, KeyType::ARROW_DOWN, KeyType::ARROW_LEFT, KeyType::ARROW_RIGHT];
|
$arrowKeys = [KeyType::ArrowUp, KeyType::ArrowDown, KeyType::ArrowLeft, KeyType::ArrowRight];
|
||||||
|
|
||||||
// Reset search state with non arrow-key input
|
// Reset search state with non arrow-key input
|
||||||
if ( ! in_array($key, $arrowKeys, true))
|
if ( ! in_array($key, $arrowKeys, true))
|
||||||
@ -286,7 +280,7 @@ class Editor {
|
|||||||
|
|
||||||
for ($i = 0; $i < $this->document->numRows; $i++)
|
for ($i = 0; $i < $this->document->numRows; $i++)
|
||||||
{
|
{
|
||||||
$current += $direction;
|
$current += $direction->value;
|
||||||
if ($current === -1)
|
if ($current === -1)
|
||||||
{
|
{
|
||||||
$current = $this->document->numRows - 1;
|
$current = $this->document->numRows - 1;
|
||||||
@ -313,7 +307,7 @@ class Editor {
|
|||||||
$savedHlLine = $current;
|
$savedHlLine = $current;
|
||||||
$savedHl = $row->hl;
|
$savedHl = $row->hl;
|
||||||
// Update the highlight array of the relevant row with the 'MATCH' type
|
// Update the highlight array of the relevant row with the 'MATCH' type
|
||||||
array_replace_range($row->hl, $match, strlen($query), Highlight::MATCH);
|
array_replace_range($row->hl, $match, strlen($query), Highlight::SearchMatch);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -409,6 +403,11 @@ class Editor {
|
|||||||
$len = $this->terminalSize->cols;
|
$len = $this->terminalSize->cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($this->showLineNumbers)
|
||||||
|
{
|
||||||
|
$this->outputBuffer .= sprintf("%-{$this->numberGutter}s", ($rowIdx+1));
|
||||||
|
}
|
||||||
|
|
||||||
$chars = substr($row->render, $this->offset->x, (int)$len);
|
$chars = substr($row->render, $this->offset->x, (int)$len);
|
||||||
$hl = array_slice($row->hl, $this->offset->x, (int)$len);
|
$hl = array_slice($row->hl, $this->offset->x, (int)$len);
|
||||||
|
|
||||||
@ -424,15 +423,13 @@ class Editor {
|
|||||||
$sym = (ord($ch) <= 26)
|
$sym = (ord($ch) <= 26)
|
||||||
? chr(ord('@') + ord($ch))
|
? chr(ord('@') + ord($ch))
|
||||||
: '?';
|
: '?';
|
||||||
$this->outputBuffer .= ANSI::color(Color::INVERT);
|
$this->outputBuffer .= ANSI::invert($sym);
|
||||||
$this->outputBuffer .= $sym;
|
|
||||||
$this->outputBuffer .= ANSI::RESET_TEXT;
|
|
||||||
if ($currentColor !== -1)
|
if ($currentColor !== -1)
|
||||||
{
|
{
|
||||||
$this->outputBuffer .= ANSI::color($currentColor);
|
$this->outputBuffer .= ANSI::color($currentColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ($hl[$i] === Highlight::NORMAL)
|
else if ($hl[$i] === Highlight::Normal)
|
||||||
{
|
{
|
||||||
if ($currentColor !== -1)
|
if ($currentColor !== -1)
|
||||||
{
|
{
|
||||||
@ -444,7 +441,7 @@ class Editor {
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$color = syntax_to_color($hl[$i]);
|
$color = get_syntax_color($hl[$i]);
|
||||||
if ($color !== $currentColor)
|
if ($color !== $currentColor)
|
||||||
{
|
{
|
||||||
$currentColor = $color;
|
$currentColor = $color;
|
||||||
@ -470,17 +467,14 @@ class Editor {
|
|||||||
$welcomelen = $this->terminalSize->cols;
|
$welcomelen = $this->terminalSize->cols;
|
||||||
}
|
}
|
||||||
|
|
||||||
$padding = ($this->terminalSize->cols - $welcomelen) / 2;
|
$padding = (int)floor(($this->terminalSize->cols - $welcomelen) / 2);
|
||||||
if ($padding > 0)
|
if ($padding > 0)
|
||||||
{
|
{
|
||||||
$this->outputBuffer .= '~';
|
$this->outputBuffer .= '~';
|
||||||
$padding--;
|
$padding--;
|
||||||
}
|
}
|
||||||
for ($i = 0; $i < $padding; $i++)
|
|
||||||
{
|
|
||||||
$this->outputBuffer .= ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
|
$this->outputBuffer .= str_repeat(' ', $padding);
|
||||||
$this->outputBuffer .= substr($welcome, 0, $welcomelen);
|
$this->outputBuffer .= substr($welcome, 0, $welcomelen);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -491,7 +485,7 @@ class Editor {
|
|||||||
|
|
||||||
protected function drawStatusBar(): void
|
protected function drawStatusBar(): void
|
||||||
{
|
{
|
||||||
$this->outputBuffer .= ANSI::color(Color::INVERT);
|
$this->outputBuffer .= ANSI::INVERSE_TEXT;
|
||||||
|
|
||||||
$statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]';
|
$statusFilename = $this->document->filename !== '' ? $this->document->filename : '[No Name]';
|
||||||
$syntaxType = $this->document->fileType->name;
|
$syntaxType = $this->document->fileType->name;
|
||||||
@ -547,10 +541,12 @@ class Editor {
|
|||||||
$this->drawStatusBar();
|
$this->drawStatusBar();
|
||||||
$this->drawMessageBar();
|
$this->drawMessageBar();
|
||||||
|
|
||||||
|
$gutter = ($this->showLineNumbers) ? $this->numberGutter : 0;
|
||||||
|
|
||||||
// Specify the current cursor position
|
// Specify the current cursor position
|
||||||
$this->outputBuffer .= ANSI::moveCursor(
|
$this->outputBuffer .= ANSI::moveCursor(
|
||||||
$this->cursor->y - $this->offset->y,
|
$this->cursor->y - $this->offset->y,
|
||||||
$this->renderX - $this->offset->x
|
($this->renderX - $this->offset->x) + $gutter,
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->outputBuffer .= ANSI::SHOW_CURSOR;
|
$this->outputBuffer .= ANSI::SHOW_CURSOR;
|
||||||
@ -574,21 +570,21 @@ class Editor {
|
|||||||
$c = Terminal::readKey();
|
$c = Terminal::readKey();
|
||||||
$isModifier = in_array($c, $modifiers, TRUE);
|
$isModifier = in_array($c, $modifiers, TRUE);
|
||||||
|
|
||||||
if ($c === KeyType::ESCAPE || ($c === RawKeyCode::ENTER && $buffer !== ''))
|
if ($c === KeyType::Escape || ($c === KeyType::Enter && $buffer !== ''))
|
||||||
{
|
{
|
||||||
$this->setStatusMessage('');
|
$this->setStatusMessage('');
|
||||||
if ($callback !== NULL)
|
if ($callback !== NULL)
|
||||||
{
|
{
|
||||||
$callback($buffer, $c);
|
$callback($buffer, $c);
|
||||||
}
|
}
|
||||||
return ($c === RawKeyCode::ENTER) ? $buffer : '';
|
return ($c === KeyType::Enter) ? $buffer : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($c === KeyType::DELETE || $c === KeyType::BACKSPACE)
|
if ($c === KeyType::Delete || $c === KeyType::Backspace)
|
||||||
{
|
{
|
||||||
$buffer = substr($buffer, 0, -1);
|
$buffer = substr($buffer, 0, -1);
|
||||||
}
|
}
|
||||||
else if (is_ascii($c) && ( ! (is_ctrl($c) || $isModifier)))
|
else if (is_string($c) && is_ascii($c) && ( ! (is_ctrl($c) || $isModifier)))
|
||||||
{
|
{
|
||||||
$buffer .= $c;
|
$buffer .= $c;
|
||||||
}
|
}
|
||||||
@ -602,21 +598,23 @@ class Editor {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Input processing
|
* Input processing
|
||||||
|
*
|
||||||
|
* Returns `false` on no keypress
|
||||||
*/
|
*/
|
||||||
protected function processKeypress(): void
|
protected function processKeypress(): bool|null
|
||||||
{
|
{
|
||||||
$c = Terminal::readKey();
|
$c = Terminal::readKey();
|
||||||
|
|
||||||
if ($c === RawKeyCode::NULL || $c === RawKeyCode::EMPTY)
|
if ($c === RawKeyCode::NULL || $c === RawKeyCode::EMPTY)
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch ($c)
|
switch ($c)
|
||||||
{
|
{
|
||||||
case RawKeyCode::CTRL('q'):
|
case RawKeyCode::CTRL('q'):
|
||||||
$this->quitAttempt();
|
$this->quitAttempt();
|
||||||
return;
|
return true;
|
||||||
|
|
||||||
case RawKeyCode::CTRL('s'):
|
case RawKeyCode::CTRL('s'):
|
||||||
$this->save();
|
$this->save();
|
||||||
@ -626,24 +624,24 @@ class Editor {
|
|||||||
$this->find();
|
$this->find();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::DELETE:
|
case KeyType::Delete:
|
||||||
case KeyType::BACKSPACE:
|
case KeyType::Backspace:
|
||||||
$this->removeChar($c);
|
$this->removeChar($c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::ARROW_UP:
|
case KeyType::ArrowUp:
|
||||||
case KeyType::ARROW_DOWN:
|
case KeyType::ArrowDown:
|
||||||
case KeyType::ARROW_LEFT:
|
case KeyType::ArrowLeft:
|
||||||
case KeyType::ARROW_RIGHT:
|
case KeyType::ArrowRight:
|
||||||
case KeyType::PAGE_UP:
|
case KeyType::PageUp:
|
||||||
case KeyType::PAGE_DOWN:
|
case KeyType::PageDown:
|
||||||
case KeyType::HOME:
|
case KeyType::Home:
|
||||||
case KeyType::END:
|
case KeyType::End:
|
||||||
$this->moveCursor($c);
|
$this->moveCursor($c);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case RawKeyCode::CTRL('l'):
|
case RawKeyCode::CTRL('l'):
|
||||||
case KeyType::ESCAPE:
|
case KeyType::Escape:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -658,13 +656,15 @@ class Editor {
|
|||||||
$this->quitTimes = KILO_QUIT_TIMES;
|
$this->quitTimes = KILO_QUIT_TIMES;
|
||||||
$this->setStatusMessage('');
|
$this->setStatusMessage('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// ! Editor operation helpers
|
// ! Editor operation helpers
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
protected function moveCursor(string $key): void
|
protected function moveCursor(KeyType $key): void
|
||||||
{
|
{
|
||||||
$x = $this->cursor->x;
|
$x = $this->cursor->x;
|
||||||
$y = $this->cursor->y;
|
$y = $this->cursor->y;
|
||||||
@ -676,7 +676,7 @@ class Editor {
|
|||||||
|
|
||||||
switch ($key)
|
switch ($key)
|
||||||
{
|
{
|
||||||
case KeyType::ARROW_LEFT:
|
case KeyType::ArrowLeft:
|
||||||
if ($x !== 0)
|
if ($x !== 0)
|
||||||
{
|
{
|
||||||
$x--;
|
$x--;
|
||||||
@ -687,9 +687,9 @@ class Editor {
|
|||||||
$y--;
|
$y--;
|
||||||
$x = $row->size - 1;
|
$x = $row->size - 1;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::ARROW_RIGHT:
|
case KeyType::ArrowRight:
|
||||||
if ($x < $row->size)
|
if ($x < $row->size)
|
||||||
{
|
{
|
||||||
$x++;
|
$x++;
|
||||||
@ -699,40 +699,40 @@ class Editor {
|
|||||||
$y++;
|
$y++;
|
||||||
$x = 0;
|
$x = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::ARROW_UP:
|
case KeyType::ArrowUp:
|
||||||
if ($y !== 0)
|
if ($y !== 0)
|
||||||
{
|
{
|
||||||
$y--;
|
$y--;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::ARROW_DOWN:
|
case KeyType::ArrowDown:
|
||||||
if ($y < $this->document->numRows)
|
if ($y < $this->document->numRows)
|
||||||
{
|
{
|
||||||
$y++;
|
$y++;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::PAGE_UP:
|
case KeyType::PageUp:
|
||||||
$y = saturating_sub($y, $this->terminalSize->rows);
|
$y = saturating_sub($y, $this->terminalSize->rows);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::PAGE_DOWN:
|
case KeyType::PageDown:
|
||||||
$y = saturating_add($y, $this->terminalSize->rows, $this->document->numRows);
|
$y = saturating_add($y, $this->terminalSize->rows, $this->document->numRows);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::HOME:
|
case KeyType::Home:
|
||||||
$x = 0;
|
$x = 0;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyType::END:
|
case KeyType::End:
|
||||||
if ($y < $this->document->numRows)
|
if ($y < $this->document->numRows)
|
||||||
{
|
{
|
||||||
$x = $row->size;
|
$x = $row->size;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Do nothing
|
// Do nothing
|
||||||
@ -753,23 +753,23 @@ class Editor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function insertChar(string $c): void
|
protected function insertChar(string|KeyType $c): void
|
||||||
{
|
{
|
||||||
$this->document->insert($this->cursor, $c);
|
$this->document->insert($this->cursor, $c);
|
||||||
$this->moveCursor(KeyType::ARROW_RIGHT);
|
$this->moveCursor(KeyType::ArrowRight);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function removeChar(string $ch): void
|
protected function removeChar(string|KeyType $ch): void
|
||||||
{
|
{
|
||||||
if ($ch === KeyType::DELETE)
|
if ($ch === KeyType::Delete)
|
||||||
{
|
{
|
||||||
$this->document->delete($this->cursor);
|
$this->document->delete($this->cursor, $this->numberGutter);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($ch === KeyType::BACKSPACE && ($this->cursor->x > 0 || $this->cursor->y > 0))
|
if ($ch === KeyType::Backspace && (($this->cursor->x >= $this->numberGutter) || $this->cursor->y > 0))
|
||||||
{
|
{
|
||||||
$this->moveCursor(KeyType::ARROW_LEFT);
|
$this->moveCursor(KeyType::ArrowLeft);
|
||||||
$this->document->delete($this->cursor);
|
$this->document->delete($this->cursor, $this->numberGutter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
150
src/Enum/C.php
150
src/Enum/C.php
@ -1,150 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Aviat\Kilo\Enum;
|
|
||||||
|
|
||||||
use Aviat\Kilo\Traits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Just a namespace for C language constants
|
|
||||||
*/
|
|
||||||
class C {
|
|
||||||
use Traits\ConstList;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// ! Misc I/O constants
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
public const STDIN_FILENO = 0;
|
|
||||||
public const STDOUT_FILENO = 1;
|
|
||||||
public const STDERR_FILENO = 2;
|
|
||||||
public const TCSAFLUSH = 2;
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// ! Termios flags and constants
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
|
|
||||||
/* Input modes */
|
|
||||||
public const IGNBRK = (1 << 0); /* Ignore break condition. */
|
|
||||||
public const BRKINT = (1 << 1); /* Signal interrupt on break. */
|
|
||||||
public const IGNPAR = (1 << 2); /* Ignore characters with parity errors. */
|
|
||||||
public const PARMRK = (1 << 3); /* Mark parity and framing errors. */
|
|
||||||
public const INPCK = (1 << 4); /* Enable input parity check. */
|
|
||||||
public const ISTRIP = (1 << 5); /* Strip 8th bit off characters. */
|
|
||||||
public const INLCR = (1 << 6); /* Map NL to CR on input. */
|
|
||||||
public const IGNCR = (1 << 7); /* Ignore CR. */
|
|
||||||
public const ICRNL = (1 << 8); /* Map CR to NL on input. */
|
|
||||||
public const IXON = (1 << 9); /* Enable start/stop output control. */
|
|
||||||
public const IXOFF = (1 << 10); /* Enable start/stop input control. */
|
|
||||||
public const IXANY = (1 << 11); /* Any character will restart after stop. */
|
|
||||||
public const IMAXBEL = (1 << 13); /* Ring bell when input queue is full. */
|
|
||||||
public const IUCLC = (1 << 14); /* Translate upper case input to lower case. */
|
|
||||||
|
|
||||||
/* Output modes */
|
|
||||||
public const OPOST = (1 << 0); /* Perform output processing. */
|
|
||||||
public const ONLCR = (1 << 1); /* Map NL to CR-NL on output. */
|
|
||||||
public const OXTABS = (1 << 2); /* Expand tabs to spaces. */
|
|
||||||
public const ONOEOT = (1 << 3); /* Discard EOT (^D) on output. */
|
|
||||||
public const OCRNL = (1 << 4); /* Map CR to NL. */
|
|
||||||
public const ONOCR = (1 << 5); /* Discard CR's when on column 0. */
|
|
||||||
public const ONLRET = (1 << 6); /* Move to column 0 on NL. */
|
|
||||||
public const NLDLY = (3 << 8); /* NL delay. */
|
|
||||||
public const NL0 = (0 << 8); /* NL type 0. */
|
|
||||||
public const NL1 = (1 << 8); /* NL type 1. */
|
|
||||||
public const TABDLY = (3 << 10 | 1 << 2); /* TAB delay. */
|
|
||||||
public const TAB0 = (0 << 10); /* TAB delay type 0. */
|
|
||||||
public const TAB1 = (1 << 10); /* TAB delay type 1. */
|
|
||||||
public const TAB2 = (2 << 10); /* TAB delay type 2. */
|
|
||||||
public const TAB3 = (1 << 2); /* Expand tabs to spaces. */
|
|
||||||
public const CRDLY = (3 << 12); /* CR delay. */
|
|
||||||
public const CR0 = (0 << 12); /* CR delay type 0. */
|
|
||||||
public const CR1 = (1 << 12); /* CR delay type 1. */
|
|
||||||
public const CR2 = (2 << 12); /* CR delay type 2. */
|
|
||||||
public const CR3 = (3 << 12); /* CR delay type 3. */
|
|
||||||
public const FFDLY = (1 << 14); /* FF delay. */
|
|
||||||
public const FF0 = (0 << 14); /* FF delay type 0. */
|
|
||||||
public const FF1 = (1 << 14); /* FF delay type 1. */
|
|
||||||
public const BSDLY = (1 << 15); /* BS delay. */
|
|
||||||
public const BS0 = (0 << 15); /* BS delay type 0. */
|
|
||||||
public const BS1 = (1 << 15); /* BS delay type 1. */
|
|
||||||
public const VTDLY = (1 << 16); /* VT delay. */
|
|
||||||
public const VT0 = (0 << 16); /* VT delay type 0. */
|
|
||||||
public const VT1 = (1 << 16); /* VT delay type 1. */
|
|
||||||
public const OLCUC = (1 << 17); /* Translate lower case output to upper case */
|
|
||||||
public const OFILL = (1 << 18); /* Send fill characters for delays. */
|
|
||||||
public const OFDEL = (1 << 19); /* Fill is DEL. */
|
|
||||||
|
|
||||||
/* Control modes */
|
|
||||||
public const CIGNORE = (1 << 0); /* Ignore these control flags. */
|
|
||||||
public const CS5 = 0; /* 5 bits per byte. */
|
|
||||||
public const CS6 = (1 << 8); /* 6 bits per byte. */
|
|
||||||
public const CS7 = (1 << 9); /* 7 bits per byte. */
|
|
||||||
public const CS8 = (C::CS6|C::CS7); /* 8 bits per byte. */
|
|
||||||
public const CSIZE = (C::CS5|C::CS6|C::CS7|C::CS8); /* Number of bits per byte (mask). */
|
|
||||||
public const CSTOPB = (1 << 10); /* Two stop bits instead of one. */
|
|
||||||
public const CREAD = (1 << 11); /* Enable receiver. */
|
|
||||||
public const PARENB = (1 << 12); /* Parity enable. */
|
|
||||||
public const PARODD = (1 << 13); /* Odd parity instead of even. */
|
|
||||||
public const HUPCL = (1 << 14); /* Hang up on last close. */
|
|
||||||
public const CLOCAL = (1 << 15); /* Ignore modem status lines. */
|
|
||||||
public const CRTSCTS = (1 << 16); /* RTS/CTS flow control. */
|
|
||||||
public const CRTS_IFLOW = C::CRTSCTS; /* Compatibility. */
|
|
||||||
public const CCTS_OFLOW = C::CRTSCTS; /* Compatibility. */
|
|
||||||
public const CDTRCTS = (1 << 17); /* DTR/CTS flow control. */
|
|
||||||
public const MDMBUF = (1 << 20); /* DTR/DCD flow control. */
|
|
||||||
public const CHWFLOW = (C::MDMBUF|C::CRTSCTS|C::CDTRCTS); /* All types of flow control. */
|
|
||||||
|
|
||||||
/* Local modes */
|
|
||||||
public const ECHOKE = (1 << 0); /* Visual erase for KILL. */
|
|
||||||
public const _ECHOE = (1 << 1); /* Visual erase for ERASE. */
|
|
||||||
public const ECHOE = C::_ECHOE;
|
|
||||||
public const _ECHOK = (1 << 2); /* Echo NL after KILL. */
|
|
||||||
public const ECHOK = C::_ECHOK;
|
|
||||||
public const _ECHO = (1 << 3); /* Enable echo. */
|
|
||||||
public const ECHO = C::_ECHO;
|
|
||||||
public const _ECHONL = (1 << 4); /* Echo NL even if ECHO is off. */
|
|
||||||
public const ECHONL = C::_ECHONL;
|
|
||||||
public const ECHOPRT = (1 << 5); /* Hardcopy visual erase. */
|
|
||||||
public const ECHOCTL = (1 << 6); /* Echo control characters as ^X. */
|
|
||||||
public const _ISIG = (1 << 7); /* Enable signals. */
|
|
||||||
public const ISIG = C::_ISIG;
|
|
||||||
public const _ICANON = (1 << 8); /* Do erase and kill processing. */
|
|
||||||
public const ICANON = C::_ICANON;
|
|
||||||
public const ALTWERASE = (1 << 9); /* Alternate WERASE algorithm. */
|
|
||||||
public const _IEXTEN = (1 << 10); /* Enable DISCARD and LNEXT. */
|
|
||||||
public const IEXTEN = C::_IEXTEN;
|
|
||||||
public const EXTPROC = (1 << 11); /* External processing. */
|
|
||||||
public const _TOSTOP = (1 << 22); /* Send SIGTTOU for background output. */
|
|
||||||
public const TOSTOP = C::_TOSTOP;
|
|
||||||
public const FLUSHO = (1 << 23); /* Output being flushed (state). */
|
|
||||||
public const XCASE = (1 << 24); /* Canonical upper/lower case. */
|
|
||||||
public const NOKERNINFO = (1 << 25); /* Disable VSTATUS. */
|
|
||||||
public const PENDIN = (1 << 29); /* Retype pending input (state). */
|
|
||||||
public const _NOFLSH = (1 << 31); /* Disable flush after interrupt. */
|
|
||||||
public const NOFLSH = C::_NOFLSH;
|
|
||||||
|
|
||||||
/* Control characters */
|
|
||||||
public const VEOF = 0; /* End-of-file character [ICANON]. */
|
|
||||||
public const VEOL = 1; /* End-of-line character [ICANON]. */
|
|
||||||
public const VEOL2 = 2; /* Second EOL character [ICANON]. */
|
|
||||||
public const VERASE = 3; /* Erase character [ICANON]. */
|
|
||||||
public const VWERASE = 4; /* Word-erase character [ICANON]. */
|
|
||||||
public const VKILL = 5; /* Kill-line character [ICANON]. */
|
|
||||||
public const VREPRINT = 6; /* Reprint-line character [ICANON]. */
|
|
||||||
public const VINTR = 8; /* Interrupt character [ISIG]. */
|
|
||||||
public const VQUIT = 9; /* Quit character [ISIG]. */
|
|
||||||
public const VSUSP = 10; /* Suspend character [ISIG]. */
|
|
||||||
public const VDSUSP = 11; /* Delayed suspend character [ISIG]. */
|
|
||||||
public const VSTART = 12; /* Start (X-ON) character [IXON, IXOFF]. */
|
|
||||||
public const VSTOP = 13; /* Stop (X-OFF) character [IXON, IXOFF]. */
|
|
||||||
public const VLNEXT = 14; /* Literal-next character [IEXTEN]. */
|
|
||||||
public const VDISCARD = 15; /* Discard character [IEXTEN]. */
|
|
||||||
public const VMIN = 16; /* Minimum number of bytes read at once [!ICANON]. */
|
|
||||||
public const VTIME = 17; /* Time-out value (tenths of a second) [!ICANON]. */
|
|
||||||
public const VSTATUS = 18; /* Status character [ICANON]. */
|
|
||||||
public const NCCS = 20; /* Value duplicated in <hurd/tioctl.defs>. */
|
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
// ! IOCTL constants
|
|
||||||
// ------------------------------------------------------------------------
|
|
||||||
public const TIOCGWINSZ = 0x5413;
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Aviat\Kilo\Enum;
|
|
||||||
|
|
||||||
use Aviat\Kilo\Traits;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ANSI Color escape sequences
|
|
||||||
* @enum
|
|
||||||
*/
|
|
||||||
class Color {
|
|
||||||
use Traits\ConstList;
|
|
||||||
|
|
||||||
// Foreground colors
|
|
||||||
public const FG_BLACK = 30;
|
|
||||||
public const FG_RED = 31;
|
|
||||||
public const FG_GREEN = 32;
|
|
||||||
public const FG_YELLOW = 33;
|
|
||||||
public const FG_BLUE = 34;
|
|
||||||
public const FG_MAGENTA = 35;
|
|
||||||
public const FG_CYAN = 36;
|
|
||||||
public const FG_WHITE = 37;
|
|
||||||
public const FG_BRIGHT_BLACK = 90;
|
|
||||||
public const FG_BRIGHT_RED = 91;
|
|
||||||
public const FG_BRIGHT_GREEN = 92;
|
|
||||||
public const FG_BRIGHT_YELLOW = 93;
|
|
||||||
public const FG_BRIGHT_BLUE = 94;
|
|
||||||
public const FG_BRIGHT_MAGENTA = 95;
|
|
||||||
public const FG_BRIGHT_CYAN = 96;
|
|
||||||
public const FG_BRIGHT_WHITE = 97;
|
|
||||||
|
|
||||||
// Background colors
|
|
||||||
public const BG_BLACK = 40;
|
|
||||||
public const BG_RED = 41;
|
|
||||||
public const BG_GREEN = 42;
|
|
||||||
public const BG_YELLOW = 43;
|
|
||||||
public const BG_BLUE = 44;
|
|
||||||
public const BG_MAGENTA = 45;
|
|
||||||
public const BG_CYAN = 46;
|
|
||||||
public const BG_WHITE = 47;
|
|
||||||
public const BG_BRIGHT_BLACK = 100;
|
|
||||||
public const BG_BRIGHT_RED = 101;
|
|
||||||
public const BG_BRIGHT_GREEN = 102;
|
|
||||||
public const BG_BRIGHT_YELLOW = 103;
|
|
||||||
public const BG_BRIGHT_BLUE = 104;
|
|
||||||
public const BG_BRIGHT_MAGENTA = 105;
|
|
||||||
public const BG_BRIGHT_CYAN = 106;
|
|
||||||
public const BG_BRIGHT_WHITE = 107;
|
|
||||||
|
|
||||||
public const INVERT = 7;
|
|
||||||
}
|
|
@ -3,46 +3,49 @@
|
|||||||
namespace Aviat\Kilo\Enum;
|
namespace Aviat\Kilo\Enum;
|
||||||
|
|
||||||
use Aviat\Kilo\Traits;
|
use Aviat\Kilo\Traits;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
class Highlight {
|
enum Highlight implements JsonSerializable {
|
||||||
use Traits\ConstList;
|
use Traits\EnumTrait;
|
||||||
|
|
||||||
public const NORMAL = 0;
|
case Normal;
|
||||||
public const COMMENT = 1;
|
case Comment;
|
||||||
public const ML_COMMENT = 2;
|
case MultiLineComment;
|
||||||
public const KEYWORD1 = 3;
|
case Keyword1;
|
||||||
public const KEYWORD2 = 4;
|
case Keyword2;
|
||||||
public const STRING = 5;
|
case String;
|
||||||
public const NUMBER = 6;
|
case Number;
|
||||||
public const OPERATOR = 7;
|
case Operator;
|
||||||
public const VARIABLE = 8;
|
case Variable;
|
||||||
public const DELIMITER = 9;
|
case Delimiter;
|
||||||
public const INVALID = 10;
|
case Invalid;
|
||||||
public const MATCH = 11;
|
case SearchMatch;
|
||||||
public const IDENTIFIER = 12;
|
case Identifier;
|
||||||
public const CHARACTER = 13;
|
case Character;
|
||||||
|
case Embed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map a PHP syntax token to its associated highlighting type
|
* Map a PHP syntax token to its associated highlighting type
|
||||||
*
|
*
|
||||||
* @param int $token
|
* @param int $token
|
||||||
* @return int
|
* @return Highlight
|
||||||
*/
|
*/
|
||||||
public static function fromPHPToken(int $token): int
|
public static function fromPHPToken(int $token): self
|
||||||
{
|
{
|
||||||
$token = match($token) {
|
return match($token) {
|
||||||
// Delimiters
|
// Delimiters
|
||||||
T_ARRAY,
|
T_ARRAY,
|
||||||
|
T_ATTRIBUTE,
|
||||||
T_CURLY_OPEN,
|
T_CURLY_OPEN,
|
||||||
T_DOLLAR_OPEN_CURLY_BRACES,
|
T_DOLLAR_OPEN_CURLY_BRACES,
|
||||||
T_OPEN_TAG,
|
T_OPEN_TAG,
|
||||||
T_OPEN_TAG_WITH_ECHO,
|
T_OPEN_TAG_WITH_ECHO,
|
||||||
T_CLOSE_TAG,
|
T_CLOSE_TAG,
|
||||||
T_START_HEREDOC,
|
T_START_HEREDOC,
|
||||||
T_END_HEREDOC => Highlight::DELIMITER,
|
T_END_HEREDOC => Highlight::Delimiter,
|
||||||
|
|
||||||
// Number literals and magic constants
|
// Number literals and magic constants
|
||||||
T_CLASS_C,
|
T_CLASS_C,
|
||||||
@ -55,16 +58,18 @@ class Highlight {
|
|||||||
T_METHOD_C,
|
T_METHOD_C,
|
||||||
T_NS_C,
|
T_NS_C,
|
||||||
T_NUM_STRING,
|
T_NUM_STRING,
|
||||||
T_TRAIT_C => Highlight::NUMBER,
|
T_TRAIT_C => Highlight::Number,
|
||||||
|
|
||||||
// String literals
|
// String literals
|
||||||
T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE => Highlight::STRING,
|
T_CONSTANT_ENCAPSED_STRING, T_ENCAPSED_AND_WHITESPACE => Highlight::String,
|
||||||
|
|
||||||
// Simple variables
|
// Simple variables
|
||||||
T_VARIABLE, T_STRING_VARNAME => Highlight::VARIABLE,
|
T_VARIABLE, T_STRING_VARNAME => Highlight::Variable,
|
||||||
|
|
||||||
// Operators
|
// Operators
|
||||||
T_AS,
|
T_AS,
|
||||||
|
T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG,
|
||||||
|
T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
|
||||||
T_AND_EQUAL,
|
T_AND_EQUAL,
|
||||||
T_BOOLEAN_AND,
|
T_BOOLEAN_AND,
|
||||||
T_BOOLEAN_OR,
|
T_BOOLEAN_OR,
|
||||||
@ -103,7 +108,7 @@ class Highlight {
|
|||||||
T_SL_EQUAL,
|
T_SL_EQUAL,
|
||||||
T_SR,
|
T_SR,
|
||||||
T_SR_EQUAL,
|
T_SR_EQUAL,
|
||||||
T_XOR_EQUAL => Highlight::OPERATOR,
|
T_XOR_EQUAL => Highlight::Operator,
|
||||||
|
|
||||||
// Keywords1
|
// Keywords1
|
||||||
T_ABSTRACT,
|
T_ABSTRACT,
|
||||||
@ -166,10 +171,10 @@ class Highlight {
|
|||||||
T_VAR,
|
T_VAR,
|
||||||
T_WHILE,
|
T_WHILE,
|
||||||
T_YIELD,
|
T_YIELD,
|
||||||
T_YIELD_FROM => Highlight::KEYWORD1,
|
T_YIELD_FROM => Highlight::Keyword1,
|
||||||
|
|
||||||
// Not string literals, but identifiers, keywords, etc.
|
// Not string literals, but identifiers, keywords, etc.
|
||||||
T_STRING => Highlight::IDENTIFIER,
|
T_STRING => Highlight::Identifier,
|
||||||
|
|
||||||
// Types and casts
|
// Types and casts
|
||||||
T_ARRAY_CAST,
|
T_ARRAY_CAST,
|
||||||
@ -179,15 +184,15 @@ class Highlight {
|
|||||||
T_INT_CAST,
|
T_INT_CAST,
|
||||||
T_OBJECT_CAST,
|
T_OBJECT_CAST,
|
||||||
T_STRING_CAST,
|
T_STRING_CAST,
|
||||||
T_UNSET_CAST => Highlight::KEYWORD2,
|
T_UNSET_CAST => Highlight::Keyword2,
|
||||||
|
|
||||||
// Invalid syntax
|
// Comments
|
||||||
T_BAD_CHARACTER => Highlight::INVALID,
|
T_DOC_COMMENT => Highlight::MultiLineComment,
|
||||||
|
|
||||||
default => Highlight::NORMAL,
|
T_INLINE_HTML => Highlight::Embed,
|
||||||
|
|
||||||
|
default => Highlight::Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
return $token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -195,21 +200,19 @@ class Highlight {
|
|||||||
* highlighting type
|
* highlighting type
|
||||||
*
|
*
|
||||||
* @param string $char
|
* @param string $char
|
||||||
* @return int
|
* @return Highlight
|
||||||
*/
|
*/
|
||||||
public static function fromPHPChar(string $char): int
|
public static function fromPHPChar(string $char): self
|
||||||
{
|
{
|
||||||
$hl = match ($char) {
|
return match ($char) {
|
||||||
// Delimiter characters
|
// Delimiter characters
|
||||||
'[', ']', '{', '}', '(', ')', '"', "'" => Highlight::DELIMITER,
|
'[', ']', '{', '}', '(', ')', '"', "'" => Highlight::Delimiter,
|
||||||
|
|
||||||
// Single character operators
|
// Single character operators
|
||||||
'?', ',', ';', ':', '^', '%', '+', '-',
|
'?', ',', ';', ':', '^', '%', '+', '-',
|
||||||
'*', '/', '.', '|', '~', '>', '<', '=', '!' => Highlight::OPERATOR,
|
'*', '/', '.', '|', '~', '>', '<', '=', '!' => Highlight::Operator,
|
||||||
|
|
||||||
default => Highlight::NORMAL,
|
default => Highlight::Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
return $hl;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -3,24 +3,34 @@
|
|||||||
namespace Aviat\Kilo\Enum;
|
namespace Aviat\Kilo\Enum;
|
||||||
|
|
||||||
use Aviat\Kilo\Traits;
|
use Aviat\Kilo\Traits;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constants representing various control keys
|
* Enum representing various control keys
|
||||||
* @enum
|
|
||||||
*/
|
*/
|
||||||
class KeyType {
|
enum KeyType implements JsonSerializable {
|
||||||
|
use Traits\EnumTrait;
|
||||||
use Traits\ConstList;
|
use Traits\ConstList;
|
||||||
|
|
||||||
public const ARROW_DOWN = 'KEY_ARROW_DOWN';
|
// ------------------------------------------------------------------------
|
||||||
public const ARROW_LEFT = 'KEY_ARROW_LEFT';
|
// Movement Keys
|
||||||
public const ARROW_RIGHT = 'KEY_ARROW_RIGHT';
|
// ------------------------------------------------------------------------
|
||||||
public const ARROW_UP = 'KEY_ARROW_UP';
|
case ArrowUp;
|
||||||
public const BACKSPACE = 'KEY_BACKSPACE';
|
case ArrowDown;
|
||||||
public const DELETE = 'KEY_DELETE';
|
case ArrowLeft;
|
||||||
public const END = 'KEY_END';
|
case ArrowRight;
|
||||||
public const ENTER = 'KEY_ENTER';
|
case Home;
|
||||||
public const ESCAPE = 'KEY_ESCAPE';
|
case End;
|
||||||
public const HOME = 'KEY_HOME';
|
case PageUp;
|
||||||
public const PAGE_DOWN = 'KEY_PAGE_DOWN';
|
case PageDown;
|
||||||
public const PAGE_UP = 'KEY_PAGE_UP';
|
// ------------------------------------------------------------------------
|
||||||
|
// Editing Keys
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
case Backspace;
|
||||||
|
case Delete;
|
||||||
|
case Enter;
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Others
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
case Escape;
|
||||||
}
|
}
|
@ -7,9 +7,9 @@ use Aviat\Kilo\Traits;
|
|||||||
/**
|
/**
|
||||||
* @enum
|
* @enum
|
||||||
*/
|
*/
|
||||||
class SearchDirection {
|
enum SearchDirection: int {
|
||||||
use Traits\ConstList;
|
use Traits\ConstList;
|
||||||
|
|
||||||
public const FORWARD = 1;
|
case FORWARD = 1;
|
||||||
public const BACKWARD = -1;
|
case BACKWARD = -1;
|
||||||
}
|
}
|
13
src/Enum/SyntaxFamily.php
Normal file
13
src/Enum/SyntaxFamily.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Enum;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Traits;
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
enum SyntaxFamily implements JsonSerializable {
|
||||||
|
use Traits\EnumTrait;
|
||||||
|
|
||||||
|
case C;
|
||||||
|
case XML;
|
||||||
|
}
|
@ -2,12 +2,11 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Enum\SyntaxFamily;
|
||||||
|
|
||||||
class FileType {
|
class FileType {
|
||||||
/**
|
/**
|
||||||
* Create the FileType object from the filename
|
* Create the FileType object from the filename
|
||||||
*
|
|
||||||
* @param string|null $filename
|
|
||||||
* @return self
|
|
||||||
*/
|
*/
|
||||||
public static function from(?string $filename): self
|
public static function from(?string $filename): self
|
||||||
{
|
{
|
||||||
@ -18,15 +17,12 @@ class FileType {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create the Syntax object from the filename
|
* Create the Syntax object from the filename
|
||||||
*
|
|
||||||
* @param string $filename
|
|
||||||
* @return Syntax
|
|
||||||
*/
|
*/
|
||||||
private static function getSyntaxFromFilename(string $filename): Syntax
|
private static function getSyntaxFromFilename(string $filename): Syntax
|
||||||
{
|
{
|
||||||
$ext = strstr(basename($filename), '.');
|
$ext = strstr(basename($filename), '.');
|
||||||
$ext = ($ext !== FALSE) ? $ext : '';
|
$ext = ($ext !== FALSE) ? $ext : '';
|
||||||
$syntax = match ($ext) {
|
return match ($ext) {
|
||||||
'.sh', '.bash' => Syntax::new(
|
'.sh', '.bash' => Syntax::new(
|
||||||
'Shell',
|
'Shell',
|
||||||
[
|
[
|
||||||
@ -125,10 +121,88 @@ class FileType {
|
|||||||
hasCharType: true,
|
hasCharType: true,
|
||||||
highlightCharacters: true,
|
highlightCharacters: true,
|
||||||
),
|
),
|
||||||
|
'.html', '.htm', '.shtml' => Syntax::new(
|
||||||
|
'Html',
|
||||||
|
[
|
||||||
|
'!doctype', '!DOCTYPE', 'html', 'base', 'head', 'link', 'meta', 'style', 'title', 'body', 'address', 'article',
|
||||||
|
'aside', 'footer', 'header', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'main', 'nav', 'section',
|
||||||
|
'blockquote', 'dd', 'div', 'dl', 'dt', 'figcaption', 'figure', 'hr', 'li', 'ol', 'p', 'pre',
|
||||||
|
'ul', 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd',
|
||||||
|
'mark', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span', 'strong', 'sub', 'sup', 'time',
|
||||||
|
'u', 'var', 'wbr', 'area', 'audio', 'img', 'map', 'track', 'video', 'embed', 'iframe', 'object',
|
||||||
|
'param', 'picture', 'portal', 'source', 'svg', 'math', 'canvas', 'noscript', 'script', 'del',
|
||||||
|
'ins', 'caption', 'col', 'colgroup', 'table', 'td', 'tbody', 'tfoot', 'th', 'thead', 'tr',
|
||||||
|
'button', 'datalist', 'fieldset', 'form', 'input', 'label', 'legend', 'meter', 'optgroup',
|
||||||
|
'option', 'output', 'progress', 'select', 'textarea', 'details', 'dialog', 'menu', 'summary',
|
||||||
|
'slot', 'template', 'frameset', 'frame', 'noframes', 'marquee', 'font', 'center',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'id', 'class', 'for', 'style', 'charset'
|
||||||
|
],
|
||||||
|
operators: ['</', '<', '=', '>'],
|
||||||
|
slcs: '',
|
||||||
|
mcs: '<!--',
|
||||||
|
mce: '-->',
|
||||||
|
highlightNumbers: false,
|
||||||
|
hasCommonOperators: false,
|
||||||
|
syntaxFamily: SyntaxFamily::XML,
|
||||||
|
),
|
||||||
|
'.xml' => Syntax::new(
|
||||||
|
'XML',
|
||||||
|
[
|
||||||
|
'!doctype', '!element', '<?xml', '?>',
|
||||||
|
],
|
||||||
|
operators: ['<', '=', '>'],
|
||||||
|
slcs: '',
|
||||||
|
mcs: '<!--',
|
||||||
|
mce: '-->',
|
||||||
|
highlightNumbers: false,
|
||||||
|
hasCommonOperators: false,
|
||||||
|
syntaxFamily: SyntaxFamily::XML,
|
||||||
|
),
|
||||||
|
'.zig' => Syntax::new(
|
||||||
|
'Zig',
|
||||||
|
[
|
||||||
|
'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',
|
||||||
|
'pub', 'fn', 'orelse', 'catch', 'and', 'or', 'comptime', 'test', 'var', 'opaque',
|
||||||
|
'usingnamespace', 'errdefer', 'callconv', 'unreachable', 'defer', 'inline',
|
||||||
|
'@addrSpaceCast', '@addWithOverflow', '@alignCast', '@alignOf', '@as', '@atomicLoad', '@atomicRmw',
|
||||||
|
'@atomicStore', '@bitCast', '@bitOffsetOf', '@bitSizeOf', '@breakpoint', '@mulAdd', '@byteSwap',
|
||||||
|
'@bitReverse', '@offsetOf', '@call', '@cDefine', '@cImport', '@cInclude', '@clz', '@cmpxchgStrong',
|
||||||
|
'@cmpxchgWeak', '@compileError', '@compileLog', '@constCast', '@ctz', '@cUndef', '@cVaArg', '@cVaCopy',
|
||||||
|
'@cVaEnd', '@cVaStart', '@divExact', '@divFloor', '@divTrunc', '@embedFile', '@enumFromInt', '@errorFromInt',
|
||||||
|
'@errorName', '@errorReturnTrace', '@errorCast', '@export', '@extern', '@fence', '@field', '@fieldParentPtr',
|
||||||
|
'@floatCast', '@floatFromInt', '@frameAddress', '@hasDecl', '@hasField', '@import', '@inComptime',
|
||||||
|
'@intCast', '@intFromBool', '@intFromEnum', '@intFromError', '@intFromFloat', '@intFromPtr', '@max',
|
||||||
|
'@memcpy', '@memset', '@min', "@wasmMemorySize", '@wasmMemoryGrow', '@mod', '@mulWithOverflow', '@panic',
|
||||||
|
'@popCount', '@prefetch', '@ptrCast', '@ptrFromInt', '@rem', '@returnAddress', '@select', '@setAlignStack',
|
||||||
|
'@setCold', '@setEvalBranchQuota', '@setFloatMode', '@setRuntimeSafety', '@shlExact', '@shlWithOverflow',
|
||||||
|
'@shrExact', '@shuffle', '@sizeOf', '@splat', '@reduce', '@src', '@sqrt', '@sin', '@cos', '@tan', '@exp',
|
||||||
|
'@exp2', '@log', '@log2', '@log10', '@abs', '@floor', '@ceil', '@trunc', '@round', '@subWithOverflow',
|
||||||
|
'@tagName', '@This', '@trap', '@truncate', '@Type', '@typeInfo', '@typeName', '@TypeOf', '@unionint',
|
||||||
|
'@Vector', '@volatileCast', '@workGroupId', '@workGroupSize', '@workItemId',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'i8', 'u8', 'i16', 'u16', 'i32', 'u32', 'i64', 'u64', 'i128', 'u128', 'isize', 'usize',
|
||||||
|
'c_char', 'c_short', 'c_ushort', 'c_int', 'c_uint', 'c_long', 'c_ulong', 'c_longlong',
|
||||||
|
'c_ulonglong', 'c_longdouble',
|
||||||
|
'f16', 'f32', 'f64', 'f80', 'f128', 'bool', 'anyopaque', 'void', 'noreturn', 'type',
|
||||||
|
'anyerror', 'comptime_init', 'comptime_float',
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'<=>', '<<=', '>>=', '...',
|
||||||
|
'++', '--', '==', '!=', '>=', '<=', '&&', '||', '<<', '>>',
|
||||||
|
'+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '->', '::',
|
||||||
|
'+%', '+%=', '+|', '+|=', '-%', '-%=', '*%', '*%=', '*|', '*|=',
|
||||||
|
'.?', '++', '**', '.*',
|
||||||
|
],
|
||||||
|
mcs: '',
|
||||||
|
mce: '',
|
||||||
|
),
|
||||||
default => Syntax::default(),
|
default => Syntax::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return $syntax;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function __construct(public string $name, public Syntax $syntax) {}
|
private function __construct(public string $name, public Syntax $syntax) {}
|
||||||
|
@ -2,7 +2,48 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\{Color, Highlight, RawKeyCode};
|
use Aviat\Kilo\Enum\Highlight;
|
||||||
|
use Aviat\Kilo\Enum\RawKeyCode;
|
||||||
|
use Aviat\Kilo\Terminal\Enum\Color;
|
||||||
|
use Aviat\Kilo\Terminal\Enum\Color256;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// ! App Constants
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const KILO_VERSION = '0.3.0';
|
||||||
|
const KILO_TAB_STOP = 4;
|
||||||
|
const KILO_QUIT_TIMES = 3;
|
||||||
|
|
||||||
|
const NO_MATCH = -1;
|
||||||
|
const T_RAW = -1;
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// ! App Config
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure syntax highlighting colors
|
||||||
|
*/
|
||||||
|
function get_syntax_color(Highlight $hl): Color | Color256 | int {
|
||||||
|
return match ($hl)
|
||||||
|
{
|
||||||
|
Highlight::Comment => Color::FG_CYAN,
|
||||||
|
Highlight::MultiLineComment => Color::FG_BRIGHT_BLACK,
|
||||||
|
Highlight::Keyword1 => Color::FG_YELLOW,
|
||||||
|
Highlight::Keyword2 => Color::FG_GREEN,
|
||||||
|
Highlight::String => Color::FG_MAGENTA,
|
||||||
|
Highlight::Character => Color::FG_BRIGHT_MAGENTA,
|
||||||
|
Highlight::Number => Color::FG_BRIGHT_RED,
|
||||||
|
Highlight::Operator => Color::FG_BRIGHT_GREEN,
|
||||||
|
Highlight::Variable => Color::FG_BRIGHT_CYAN,
|
||||||
|
Highlight::Delimiter => Color::FG_BLUE,
|
||||||
|
Highlight::Invalid => Color::BG_BRIGHT_RED,
|
||||||
|
Highlight::SearchMatch => Color::INVERT,
|
||||||
|
Highlight::Identifier => Color::FG_BRIGHT_WHITE,
|
||||||
|
default => Color::FG_WHITE,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// ! C function/macro equivalents
|
// ! C function/macro equivalents
|
||||||
@ -11,9 +52,6 @@ use Aviat\Kilo\Enum\{Color, Highlight, RawKeyCode};
|
|||||||
/**
|
/**
|
||||||
* Do bit twiddling to convert a letter into
|
* Do bit twiddling to convert a letter into
|
||||||
* its Ctrl-letter equivalent ordinal ascii value
|
* its Ctrl-letter equivalent ordinal ascii value
|
||||||
*
|
|
||||||
* @param string $char
|
|
||||||
* @return int
|
|
||||||
*/
|
*/
|
||||||
function ctrl_key(string $char): int
|
function ctrl_key(string $char): int
|
||||||
{
|
{
|
||||||
@ -30,9 +68,6 @@ function ctrl_key(string $char): int
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the one-character string contain an ascii ordinal value?
|
* Does the one-character string contain an ascii ordinal value?
|
||||||
*
|
|
||||||
* @param string $single_char
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
function is_ascii(string $single_char): bool
|
function is_ascii(string $single_char): bool
|
||||||
{
|
{
|
||||||
@ -46,9 +81,6 @@ function is_ascii(string $single_char): bool
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the one-character string contain an ascii control character?
|
* Does the one-character string contain an ascii control character?
|
||||||
*
|
|
||||||
* @param string $char
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
function is_ctrl(string $char): bool
|
function is_ctrl(string $char): bool
|
||||||
{
|
{
|
||||||
@ -58,9 +90,6 @@ function is_ctrl(string $char): bool
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the one-character string contain an ascii number?
|
* Does the one-character string contain an ascii number?
|
||||||
*
|
|
||||||
* @param string $char
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
function is_digit(string $char): bool
|
function is_digit(string $char): bool
|
||||||
{
|
{
|
||||||
@ -70,13 +99,10 @@ function is_digit(string $char): bool
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the one-character string contain ascii whitespace?
|
* Does the one-character string contain ascii whitespace?
|
||||||
*
|
|
||||||
* @param string $char
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
function is_space(string $char): bool
|
function is_space(string $char): bool
|
||||||
{
|
{
|
||||||
$isSpace = match($char) {
|
return match($char) {
|
||||||
RawKeyCode::CARRIAGE_RETURN,
|
RawKeyCode::CARRIAGE_RETURN,
|
||||||
RawKeyCode::FORM_FEED,
|
RawKeyCode::FORM_FEED,
|
||||||
RawKeyCode::NEWLINE,
|
RawKeyCode::NEWLINE,
|
||||||
@ -86,8 +112,6 @@ function is_space(string $char): bool
|
|||||||
|
|
||||||
default => false,
|
default => false,
|
||||||
};
|
};
|
||||||
|
|
||||||
return $isSpace;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
@ -96,9 +120,6 @@ function is_space(string $char): bool
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the one-character string contain a character that separates tokens?
|
* Does the one-character string contain a character that separates tokens?
|
||||||
*
|
|
||||||
* @param string $char
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
function is_separator(string $char): bool
|
function is_separator(string $char): bool
|
||||||
{
|
{
|
||||||
@ -133,12 +154,7 @@ function array_replace_range(array &$array, int $offset, int $length, mixed $val
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the string $haystack contain $str, optionally searching from $offset?
|
* Does the string $haystack contain $str, optionally searching from $offset
|
||||||
*
|
|
||||||
* @param string $haystack
|
|
||||||
* @param string $str
|
|
||||||
* @param int|null $offset
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
function str_has(string $haystack, string $str, ?int $offset = NULL): bool
|
function str_has(string $haystack, string $str, ?int $offset = NULL): bool
|
||||||
{
|
{
|
||||||
@ -152,41 +168,8 @@ function str_has(string $haystack, string $str, ?int $offset = NULL): bool
|
|||||||
: \str_contains($haystack, $str);
|
: \str_contains($haystack, $str);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the ASCII color escape number for the specified syntax type
|
|
||||||
*
|
|
||||||
* @param int $hl
|
|
||||||
* @return int
|
|
||||||
*/
|
|
||||||
function syntax_to_color(int $hl): int
|
|
||||||
{
|
|
||||||
$color = match ($hl)
|
|
||||||
{
|
|
||||||
Highlight::COMMENT => Color::FG_CYAN,
|
|
||||||
Highlight::ML_COMMENT => Color::FG_BRIGHT_BLACK,
|
|
||||||
Highlight::KEYWORD1 => Color::FG_YELLOW,
|
|
||||||
Highlight::KEYWORD2 => Color::FG_GREEN,
|
|
||||||
Highlight::STRING => Color::FG_MAGENTA,
|
|
||||||
Highlight::CHARACTER => Color::FG_BRIGHT_MAGENTA,
|
|
||||||
Highlight::NUMBER => Color::FG_BRIGHT_RED,
|
|
||||||
Highlight::OPERATOR => Color::FG_BRIGHT_GREEN,
|
|
||||||
Highlight::VARIABLE => Color::FG_BRIGHT_CYAN,
|
|
||||||
Highlight::DELIMITER => Color::FG_BLUE,
|
|
||||||
Highlight::INVALID => Color::BG_BRIGHT_RED,
|
|
||||||
Highlight::MATCH => Color::INVERT,
|
|
||||||
Highlight::IDENTIFIER => Color::FG_BRIGHT_WHITE,
|
|
||||||
default => Color::FG_WHITE,
|
|
||||||
};
|
|
||||||
|
|
||||||
return $color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Replace tabs with the specified number of spaces.
|
* Replace tabs with the specified number of spaces.
|
||||||
*
|
|
||||||
* @param string $str
|
|
||||||
* @param int $number
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string
|
function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string
|
||||||
{
|
{
|
||||||
@ -195,7 +178,7 @@ function tabs_to_spaces(string $str, int $number = KILO_TAB_STOP): string
|
|||||||
|
|
||||||
function error_code_name(int $code): string
|
function error_code_name(int $code): string
|
||||||
{
|
{
|
||||||
$errorName = match ($code) {
|
return match ($code) {
|
||||||
E_ERROR => 'Error',
|
E_ERROR => 'Error',
|
||||||
E_WARNING => 'Warning',
|
E_WARNING => 'Warning',
|
||||||
E_PARSE => 'Parse Error',
|
E_PARSE => 'Parse Error',
|
||||||
@ -212,15 +195,19 @@ function error_code_name(int $code): string
|
|||||||
E_USER_DEPRECATED => 'User Deprecated',
|
E_USER_DEPRECATED => 'User Deprecated',
|
||||||
default => 'Unknown',
|
default => 'Unknown',
|
||||||
};
|
};
|
||||||
|
|
||||||
return $errorName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds two numbers to at most the $max value
|
||||||
|
*/
|
||||||
function saturating_add(int $a, int $b, int $max): int
|
function saturating_add(int $a, int $b, int $max): int
|
||||||
{
|
{
|
||||||
return ($a + $b > $max) ? $max : $a + $b;
|
return ($a + $b > $max) ? $max : $a + $b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete one number from another, down to zero at the least
|
||||||
|
*/
|
||||||
function saturating_sub(int $a, int $b): int
|
function saturating_sub(int $a, int $b): int
|
||||||
{
|
{
|
||||||
if ($b > $a)
|
if ($b > $a)
|
146
src/Row.php
146
src/Row.php
@ -28,11 +28,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a row in the current document
|
* Create a row in the current document
|
||||||
*
|
|
||||||
* @param Document $parent
|
|
||||||
* @param string $chars
|
|
||||||
* @param int $idx
|
|
||||||
* @return self
|
|
||||||
*/
|
*/
|
||||||
public static function new(Document $parent, string $chars, int $idx): self
|
public static function new(Document $parent, string $chars, int $idx): self
|
||||||
{
|
{
|
||||||
@ -45,8 +40,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Create an empty Row
|
* Create an empty Row
|
||||||
*
|
|
||||||
* @return self
|
|
||||||
*/
|
*/
|
||||||
public static function default(): self
|
public static function default(): self
|
||||||
{
|
{
|
||||||
@ -61,36 +54,35 @@ class Row {
|
|||||||
/**
|
/**
|
||||||
* The document that this row belongs to
|
* The document that this row belongs to
|
||||||
*/
|
*/
|
||||||
private Document $parent,
|
private readonly Document $parent,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string The raw characters in the row
|
* The raw characters in the row
|
||||||
*/
|
*/
|
||||||
private string $chars,
|
private string $chars,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int The line number of the current row
|
* The line number of the current row
|
||||||
*/
|
*/
|
||||||
public int $idx,
|
public int $idx,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up dynamically generated properties
|
||||||
|
*/
|
||||||
public function __get(string $name): mixed
|
public function __get(string $name): mixed
|
||||||
{
|
{
|
||||||
$prop = match ($name)
|
return match ($name)
|
||||||
{
|
{
|
||||||
'size' => strlen($this->chars),
|
'size' => strlen($this->chars),
|
||||||
'rsize' => strlen($this->render),
|
'rsize' => strlen($this->render),
|
||||||
'chars' => $this->chars,
|
'chars' => $this->chars,
|
||||||
default => NULL,
|
default => NULL,
|
||||||
};
|
};
|
||||||
|
|
||||||
return $prop;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert the row contents to a string for saving
|
* Convert the row contents to a string for saving
|
||||||
*
|
|
||||||
* @return string
|
|
||||||
*/
|
*/
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
@ -99,8 +91,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the properties to display for var_dump
|
* Set the properties to display for var_dump
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
*/
|
||||||
public function __debugInfo(): array
|
public function __debugInfo(): array
|
||||||
{
|
{
|
||||||
@ -116,8 +106,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this row a valid part of a document?
|
* Is this row a valid part of a document?
|
||||||
*
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
public function isValid(): bool
|
public function isValid(): bool
|
||||||
{
|
{
|
||||||
@ -127,8 +115,8 @@ class Row {
|
|||||||
/**
|
/**
|
||||||
* Insert the string or character $c at index $at
|
* Insert the string or character $c at index $at
|
||||||
*
|
*
|
||||||
* @param int $at
|
* @param int $at The point to insert at
|
||||||
* @param string $c
|
* @param string $c The string to insert
|
||||||
*/
|
*/
|
||||||
public function insert(int $at, string $c): void
|
public function insert(int $at, string $c): void
|
||||||
{
|
{
|
||||||
@ -145,8 +133,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Append $s to the current row
|
* Append $s to the current row
|
||||||
*
|
|
||||||
* @param string $s
|
|
||||||
*/
|
*/
|
||||||
public function append(string $s): void
|
public function append(string $s): void
|
||||||
{
|
{
|
||||||
@ -157,7 +143,7 @@ class Row {
|
|||||||
/**
|
/**
|
||||||
* Delete the character at the specified index
|
* Delete the character at the specified index
|
||||||
*
|
*
|
||||||
* @param int $at
|
* @param int $at The index to delete
|
||||||
*/
|
*/
|
||||||
public function delete(int $at): void
|
public function delete(int $at): void
|
||||||
{
|
{
|
||||||
@ -172,8 +158,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the contents of the Row
|
* Set the contents of the Row
|
||||||
*
|
|
||||||
* @param string $chars
|
|
||||||
*/
|
*/
|
||||||
public function setChars(string $chars): void
|
public function setChars(string $chars): void
|
||||||
{
|
{
|
||||||
@ -199,7 +183,7 @@ class Row {
|
|||||||
*/
|
*/
|
||||||
public function highlight(): void
|
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->fileType->name === 'PHP')
|
if ($this->parent->fileType->name === 'PHP')
|
||||||
{
|
{
|
||||||
@ -232,10 +216,10 @@ class Row {
|
|||||||
{
|
{
|
||||||
if ($inComment)
|
if ($inComment)
|
||||||
{
|
{
|
||||||
$this->hl[$i] = Highlight::ML_COMMENT;
|
$this->hl[$i] = Highlight::MultiLineComment;
|
||||||
if (substr($this->render, $i, $mceLen) === $mce)
|
if (substr($this->render, $i, $mceLen) === $mce)
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $i, $mceLen, Highlight::ML_COMMENT);
|
array_replace_range($this->hl, $i, $mceLen, Highlight::MultiLineComment);
|
||||||
$i += $mceLen;
|
$i += $mceLen;
|
||||||
$inComment = FALSE;
|
$inComment = FALSE;
|
||||||
continue;
|
continue;
|
||||||
@ -247,7 +231,7 @@ class Row {
|
|||||||
|
|
||||||
if (substr($this->render, $i, $mcsLen) === $mcs)
|
if (substr($this->render, $i, $mcsLen) === $mcs)
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $i, $mcsLen, Highlight::ML_COMMENT);
|
array_replace_range($this->hl, $i, $mcsLen, Highlight::MultiLineComment);
|
||||||
$i += $mcsLen;
|
$i += $mcsLen;
|
||||||
$inComment = TRUE;
|
$inComment = TRUE;
|
||||||
continue;
|
continue;
|
||||||
@ -261,7 +245,7 @@ class Row {
|
|||||||
|| $this->highlightSecondaryKeywords($i, $syntax)
|
|| $this->highlightSecondaryKeywords($i, $syntax)
|
||||||
|| $this->highlightString($i, $syntax)
|
|| $this->highlightString($i, $syntax)
|
||||||
|| $this->highlightOperators($i, $syntax)
|
|| $this->highlightOperators($i, $syntax)
|
||||||
|| $this->highlightCommonDelimeters($i)
|
|| $this->highlightCommonDelimiters($i)
|
||||||
|| $this->highlightCommonOperators($i, $syntax)
|
|| $this->highlightCommonOperators($i, $syntax)
|
||||||
|| $this->highlightNumber($i, $syntax)
|
|| $this->highlightNumber($i, $syntax)
|
||||||
) {
|
) {
|
||||||
@ -281,15 +265,11 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight number literals
|
* Highlight number literals
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightNumber(int &$i, Syntax $opts): bool
|
protected function highlightNumber(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
$char = $this->render[$i];
|
$char = $this->render[$i];
|
||||||
if ($opts->numbers() && is_digit($char) && $this->hl[$i] === Highlight::NORMAL)
|
if ($opts->numbers() && is_digit($char) && $this->hl[$i] === Highlight::Normal)
|
||||||
{
|
{
|
||||||
if ($i > 0)
|
if ($i > 0)
|
||||||
{
|
{
|
||||||
@ -302,7 +282,7 @@ class Row {
|
|||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
$this->hl[$i] = Highlight::NUMBER;
|
$this->hl[$i] = Highlight::Number;
|
||||||
$i++;
|
$i++;
|
||||||
|
|
||||||
if ($i < strlen($this->render))
|
if ($i < strlen($this->render))
|
||||||
@ -325,14 +305,8 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight keywords and/or operators
|
* Highlight keywords and/or operators
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param array $keywords
|
|
||||||
* @param int $syntaxType
|
|
||||||
* @param bool $requireSeparator
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightWord(int &$i, array $keywords, int $syntaxType, bool $requireSeparator = true): bool
|
protected function highlightWord(int &$i, array $keywords, Highlight $syntaxType, bool $requireSeparator = true): bool
|
||||||
{
|
{
|
||||||
if ($i > 0 && $requireSeparator)
|
if ($i > 0 && $requireSeparator)
|
||||||
{
|
{
|
||||||
@ -364,15 +338,10 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight a single-char character from a list of provided keywords
|
* Highlight a single-char character from a list of provided keywords
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param array $chars
|
|
||||||
* @param int $syntaxType
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightChar(int &$i, array $chars, int $syntaxType): bool
|
protected function highlightChar(int &$i, array $chars, Highlight $syntaxType): bool
|
||||||
{
|
{
|
||||||
if ($this->hl[$i] !== Highlight::NORMAL)
|
if ($this->hl[$i] !== Highlight::Normal)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -392,46 +361,30 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight primary keywords
|
* Highlight primary keywords
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightPrimaryKeywords(int &$i, Syntax $opts): bool
|
protected function highlightPrimaryKeywords(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
return $this->highlightWord($i, $opts->keywords1, Highlight::KEYWORD1);
|
return $this->highlightWord($i, $opts->keywords1, Highlight::Keyword1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight secondary keywords
|
* Highlight secondary keywords
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightSecondaryKeywords(int &$i, Syntax $opts): bool
|
protected function highlightSecondaryKeywords(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
return $this->highlightWord($i, $opts->keywords2, Highlight::KEYWORD2);
|
return $this->highlightWord($i, $opts->keywords2, Highlight::Keyword2);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight language-specific operators
|
* Highlight language-specific operators
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightOperators(int &$i, Syntax $opts): bool
|
protected function highlightOperators(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
return $this->highlightWord($i, $opts->operators, Highlight::OPERATOR, false);
|
return $this->highlightWord($i, $opts->operators, Highlight::Operator, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight common single-character operators
|
* Highlight common single-character operators
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightCommonOperators(int &$i, Syntax $opts): bool
|
protected function highlightCommonOperators(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
@ -443,31 +396,24 @@ class Row {
|
|||||||
return $this->highlightChar(
|
return $this->highlightChar(
|
||||||
$i,
|
$i,
|
||||||
['+', '-', '*', '/', '<', '^', '>', '%', '=', ':', ',', ';', '&', '~', '!', '|', '.'],
|
['+', '-', '*', '/', '<', '^', '>', '%', '=', ':', ',', ';', '&', '~', '!', '|', '.'],
|
||||||
Highlight::OPERATOR
|
Highlight::Operator
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight brackets and braces
|
* Highlight brackets and braces
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightCommonDelimeters(int &$i): bool
|
protected function highlightCommonDelimiters(int &$i): bool
|
||||||
{
|
{
|
||||||
return $this->highlightChar(
|
return $this->highlightChar(
|
||||||
$i,
|
$i,
|
||||||
['{', '}', '[', ']', '(', ')'],
|
['{', '}', '[', ']', '(', ')'],
|
||||||
Highlight::DELIMITER
|
Highlight::Delimiter
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight character literals
|
* Highlight character literals
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightCharacter(int &$i, Syntax $opts): bool
|
protected function highlightCharacter(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
@ -491,7 +437,7 @@ class Row {
|
|||||||
$closingChar = $this->render[$closingIndex];
|
$closingChar = $this->render[$closingIndex];
|
||||||
if ($closingChar === "'")
|
if ($closingChar === "'")
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $i, $closingIndex - $i + 1, Highlight::CHARACTER);
|
array_replace_range($this->hl, $i, $closingIndex - $i + 1, Highlight::Character);
|
||||||
$i = $closingIndex + 1;
|
$i = $closingIndex + 1;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -503,10 +449,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight single-line comments
|
* Highlight single-line comments
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightComment(int &$i, Syntax $opts): bool
|
protected function highlightComment(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
@ -520,7 +462,7 @@ class Row {
|
|||||||
|
|
||||||
if ($scsLen > 0 && substr($this->render, $i, $scsLen) === $scs)
|
if ($scsLen > 0 && substr($this->render, $i, $scsLen) === $scs)
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $i, $this->rsize - $i, Highlight::COMMENT);
|
array_replace_range($this->hl, $i, $this->rsize - $i, Highlight::Comment);
|
||||||
$i = $this->rsize;
|
$i = $this->rsize;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@ -531,10 +473,6 @@ class Row {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Highlight quote-delimited string literals
|
* Highlight quote-delimited string literals
|
||||||
*
|
|
||||||
* @param int $i
|
|
||||||
* @param Syntax $opts
|
|
||||||
* @return bool
|
|
||||||
*/
|
*/
|
||||||
protected function highlightString(int &$i, Syntax $opts): bool
|
protected function highlightString(int &$i, Syntax $opts): bool
|
||||||
{
|
{
|
||||||
@ -549,18 +487,18 @@ class Row {
|
|||||||
if ($opts->strings() && ($char === '"' || $char === '\''))
|
if ($opts->strings() && ($char === '"' || $char === '\''))
|
||||||
{
|
{
|
||||||
$quote = $char;
|
$quote = $char;
|
||||||
$this->hl[$i] = Highlight::STRING;
|
$this->hl[$i] = Highlight::String;
|
||||||
$i++;
|
$i++;
|
||||||
|
|
||||||
while ($i < $this->rsize)
|
while ($i < $this->rsize)
|
||||||
{
|
{
|
||||||
$char = $this->render[$i];
|
$char = $this->render[$i];
|
||||||
$this->hl[$i] = Highlight::STRING;
|
$this->hl[$i] = Highlight::String;
|
||||||
|
|
||||||
// Check for escaped character
|
// Check for escaped character
|
||||||
if ($char === '\\' && $i+1 < $this->rsize)
|
if ($char === '\\' && $i+1 < $this->rsize)
|
||||||
{
|
{
|
||||||
$this->hl[$i + 1] = Highlight::STRING;
|
$this->hl[$i + 1] = Highlight::String;
|
||||||
$i += 2;
|
$i += 2;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -619,20 +557,20 @@ class Row {
|
|||||||
if ($commentEnd !== FALSE)
|
if ($commentEnd !== FALSE)
|
||||||
{
|
{
|
||||||
$inComment = FALSE;
|
$inComment = FALSE;
|
||||||
array_replace_range($this->hl, 0, $commentEnd + 2, Highlight::ML_COMMENT);
|
array_replace_range($this->hl, 0, $commentEnd + 2, Highlight::MultiLineComment);
|
||||||
$offset = $commentEnd;
|
$offset = $commentEnd;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, just set the whole row
|
// Otherwise, just set the whole row
|
||||||
$this->hl = array_fill(0, $this->rsize, Highlight::ML_COMMENT);
|
$this->hl = array_fill(0, $this->rsize, Highlight::MultiLineComment);
|
||||||
$this->hl[$offset] = Highlight::ML_COMMENT;
|
$this->hl[$offset] = Highlight::MultiLineComment;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$char = $token['char'];
|
$char = $token['char'];
|
||||||
$charLen = strlen($char);
|
$charLen = strlen($char);
|
||||||
if ($charLen === 0 || $offset >= $this->rsize)
|
if ($charLen === 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -649,7 +587,7 @@ class Row {
|
|||||||
// Single line comments
|
// Single line comments
|
||||||
if (str_contains($char, '//') || str_contains($char, '#'))
|
if (str_contains($char, '//') || str_contains($char, '#'))
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $charStart, $charLen, Highlight::COMMENT);
|
array_replace_range($this->hl, $charStart, $charLen, Highlight::Comment);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -664,13 +602,13 @@ class Row {
|
|||||||
if ($hasEnd)
|
if ($hasEnd)
|
||||||
{
|
{
|
||||||
$len = $end - $start + 2;
|
$len = $end - $start + 2;
|
||||||
array_replace_range($this->hl, $start, $len, Highlight::ML_COMMENT);
|
array_replace_range($this->hl, $start, $len, Highlight::MultiLineComment);
|
||||||
$inComment = FALSE;
|
$inComment = FALSE;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$inComment = TRUE;
|
$inComment = TRUE;
|
||||||
array_replace_range($this->hl, $start, $charLen - $offset, Highlight::ML_COMMENT);
|
array_replace_range($this->hl, $start, $charLen - $offset, Highlight::MultiLineComment);
|
||||||
$offset = $start + $charLen - $offset;
|
$offset = $start + $charLen - $offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -686,16 +624,16 @@ class Row {
|
|||||||
|
|
||||||
$highlight = match(true) {
|
$highlight = match(true) {
|
||||||
// Matches a predefined PHP token
|
// Matches a predefined PHP token
|
||||||
$token['type'] !== 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,
|
||||||
|
|
||||||
default => Highlight::NORMAL,
|
default => Highlight::Normal,
|
||||||
};
|
};
|
||||||
|
|
||||||
if ($highlight !== Highlight::NORMAL)
|
if ($highlight !== Highlight::Normal)
|
||||||
{
|
{
|
||||||
array_replace_range($this->hl, $charStart, $charLen, $highlight);
|
array_replace_range($this->hl, $charStart, $charLen, $highlight);
|
||||||
$offset = $charEnd;
|
$offset = $charEnd;
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Enum\SyntaxFamily;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of the syntax of a programming language
|
||||||
|
*/
|
||||||
class Syntax {
|
class Syntax {
|
||||||
// Tokens for PHP files
|
// Tokens for PHP files
|
||||||
public array $tokens = [];
|
public array $tokens = [];
|
||||||
@ -20,6 +25,7 @@ class Syntax {
|
|||||||
bool $hasCharType = false,
|
bool $hasCharType = false,
|
||||||
bool $highlightCharacters = false,
|
bool $highlightCharacters = false,
|
||||||
bool $hasCommonOperators = true,
|
bool $hasCommonOperators = true,
|
||||||
|
SyntaxFamily $syntaxFamily = SyntaxFamily::C,
|
||||||
): self
|
): self
|
||||||
{
|
{
|
||||||
return new self(
|
return new self(
|
||||||
@ -36,16 +42,17 @@ class Syntax {
|
|||||||
$highlightStrings,
|
$highlightStrings,
|
||||||
$highlightComments,
|
$highlightComments,
|
||||||
$hasCommonOperators,
|
$hasCommonOperators,
|
||||||
|
$syntaxFamily,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function default(): self
|
public static function default(): self
|
||||||
{
|
{
|
||||||
return self::new(
|
return self::new(
|
||||||
'No filetype', slcs:
|
'No filetype',
|
||||||
'', mcs:
|
slcs: '',
|
||||||
'', mce:
|
mcs: '',
|
||||||
'',
|
mce: '',
|
||||||
highlightNumbers: false,
|
highlightNumbers: false,
|
||||||
highlightStrings: false,
|
highlightStrings: false,
|
||||||
hasCommonOperators: false,
|
hasCommonOperators: false,
|
||||||
@ -79,6 +86,8 @@ class Syntax {
|
|||||||
private bool $highlightComments,
|
private bool $highlightComments,
|
||||||
/** should we highlight common operators? */
|
/** should we highlight common operators? */
|
||||||
private bool $hasCommonOperators,
|
private bool $hasCommonOperators,
|
||||||
|
/** What kind of general syntax family does this file belong to? */
|
||||||
|
private SyntaxFamily $syntaxFamily,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function numbers(): bool
|
public function numbers(): bool
|
||||||
@ -105,7 +114,7 @@ class Syntax {
|
|||||||
{
|
{
|
||||||
return $this->highlightComments
|
return $this->highlightComments
|
||||||
&& strlen($this->multiLineCommentStart) !== 0
|
&& strlen($this->multiLineCommentStart) !== 0
|
||||||
&& strlen($this->multiLineCommentStart) !== 0;
|
&& strlen($this->multiLineCommentEnd) !== 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function comments(): bool
|
public function comments(): bool
|
||||||
@ -117,4 +126,9 @@ class Syntax {
|
|||||||
{
|
{
|
||||||
return $this->hasCommonOperators;
|
return $this->hasCommonOperators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function syntaxFamily(): SyntaxFamily
|
||||||
|
{
|
||||||
|
return $this->syntaxFamily;
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,6 +1,9 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo\Terminal;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Terminal\Enum\Color;
|
||||||
|
use Aviat\Kilo\Terminal\Enum\Color256;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ANSI
|
* ANSI
|
||||||
@ -31,8 +34,18 @@ class ANSI {
|
|||||||
|
|
||||||
public const BOLD_TEXT = "\e[1m";
|
public const BOLD_TEXT = "\e[1m";
|
||||||
|
|
||||||
|
public const DIM_TEXT = "\e[2m";
|
||||||
|
|
||||||
|
public const ITALIC_TEXT = "\d[3m";
|
||||||
|
|
||||||
public const UNDERLINE_TEXT = "\e[4m";
|
public const UNDERLINE_TEXT = "\e[4m";
|
||||||
|
|
||||||
|
public const BLINKING_TEXT = "\e[5m";
|
||||||
|
|
||||||
|
public const INVERSE_TEXT = "\e[7m";
|
||||||
|
|
||||||
|
public const STRIKE_TEXT = "\e[9m";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Move the cursor to position 0,0 which is the top left
|
* Move the cursor to position 0,0 which is the top left
|
||||||
*/
|
*/
|
||||||
@ -46,14 +59,48 @@ class ANSI {
|
|||||||
public const SHOW_CURSOR = "\e[?25h";
|
public const SHOW_CURSOR = "\e[?25h";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the ascii sequence for basic text color
|
* Generate the ascii sequence for basic foreground text color
|
||||||
*
|
*
|
||||||
* @param int $color
|
* @param Color|Color256|int $color
|
||||||
|
* @param Color $ground
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
public static function color(int $color): string
|
public static function color(Color|Color256| int $color, Color $ground = Color::Fg): string
|
||||||
{
|
{
|
||||||
return self::escapeSequence('%dm', $color);
|
if ( ! $color instanceof Color)
|
||||||
|
{
|
||||||
|
return self::color256($color, $ground);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::escapeSequence('%dm', $color->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the ANSI sequence for a 256 mode color
|
||||||
|
*
|
||||||
|
* @param Color256 | int $color
|
||||||
|
* @param Color $ground
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function color256(Color256| int $color, Color $ground = Color::Fg): string
|
||||||
|
{
|
||||||
|
if ($color instanceof Color256)
|
||||||
|
{
|
||||||
|
$color = $color->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::escapeSequence('%d;5;%dm', $ground->value, $color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invert the foreground/background on a text segment
|
||||||
|
*
|
||||||
|
* @param string $text
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function invert(string $text): string
|
||||||
|
{
|
||||||
|
return self::INVERSE_TEXT . $text . self::RESET_TEXT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
27
src/Terminal/C.php
Normal file
27
src/Terminal/C.php
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Terminal;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Traits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Just a namespace for C language constants
|
||||||
|
*/
|
||||||
|
class C {
|
||||||
|
use Traits\ConstList;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// ! Misc I/O constants
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public const STDIN_FILENO = 0;
|
||||||
|
public const STDOUT_FILENO = 1;
|
||||||
|
public const STDERR_FILENO = 2;
|
||||||
|
public const TCSANOW = 0;
|
||||||
|
public const TCSAFLUSH = 2;
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// ! IOCTL constants
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
public const TIOCGWINSZ = 0x5413;
|
||||||
|
}
|
55
src/Terminal/Enum/Color.php
Normal file
55
src/Terminal/Enum/Color.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Terminal\Enum;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Traits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANSI Color escape sequences
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
enum Color: int {
|
||||||
|
use Traits\ConstList;
|
||||||
|
|
||||||
|
// Foreground/Background
|
||||||
|
case Fg = 38;
|
||||||
|
case Bg = 48;
|
||||||
|
|
||||||
|
// Foreground colors
|
||||||
|
case FG_BLACK = 30;
|
||||||
|
case FG_RED = 31;
|
||||||
|
case FG_GREEN = 32;
|
||||||
|
case FG_YELLOW = 33;
|
||||||
|
case FG_BLUE = 34;
|
||||||
|
case FG_MAGENTA = 35;
|
||||||
|
case FG_CYAN = 36;
|
||||||
|
case FG_WHITE = 37;
|
||||||
|
case FG_BRIGHT_BLACK = 90;
|
||||||
|
case FG_BRIGHT_RED = 91;
|
||||||
|
case FG_BRIGHT_GREEN = 92;
|
||||||
|
case FG_BRIGHT_YELLOW = 93;
|
||||||
|
case FG_BRIGHT_BLUE = 94;
|
||||||
|
case FG_BRIGHT_MAGENTA = 95;
|
||||||
|
case FG_BRIGHT_CYAN = 96;
|
||||||
|
case FG_BRIGHT_WHITE = 97;
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
case BG_BLACK = 40;
|
||||||
|
case BG_RED = 41;
|
||||||
|
case BG_GREEN = 42;
|
||||||
|
case BG_YELLOW = 43;
|
||||||
|
case BG_BLUE = 44;
|
||||||
|
case BG_MAGENTA = 45;
|
||||||
|
case BG_CYAN = 46;
|
||||||
|
case BG_WHITE = 47;
|
||||||
|
case BG_BRIGHT_BLACK = 100;
|
||||||
|
case BG_BRIGHT_RED = 101;
|
||||||
|
case BG_BRIGHT_GREEN = 102;
|
||||||
|
case BG_BRIGHT_YELLOW = 103;
|
||||||
|
case BG_BRIGHT_BLUE = 104;
|
||||||
|
case BG_BRIGHT_MAGENTA = 105;
|
||||||
|
case BG_BRIGHT_CYAN = 106;
|
||||||
|
case BG_BRIGHT_WHITE = 107;
|
||||||
|
|
||||||
|
case INVERT = 7;
|
||||||
|
}
|
31
src/Terminal/Enum/Color256.php
Normal file
31
src/Terminal/Enum/Color256.php
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Terminal\Enum;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Traits;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANSI 256 Color escape sequences
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
enum Color256: int {
|
||||||
|
use Traits\ConstList;
|
||||||
|
|
||||||
|
// Base colors for 256-color setup
|
||||||
|
case Black = 0;
|
||||||
|
case Red = 1;
|
||||||
|
case Green = 2;
|
||||||
|
case Yellow = 3;
|
||||||
|
case Blue = 4;
|
||||||
|
case Magenta = 5;
|
||||||
|
case Cyan = 6;
|
||||||
|
case White = 7;
|
||||||
|
case BrightBlack = 8;
|
||||||
|
case BrightRed = 9;
|
||||||
|
case BrightGreen = 10;
|
||||||
|
case BrightYellow = 11;
|
||||||
|
case BrightBlue = 12;
|
||||||
|
case BrightMagenta = 13;
|
||||||
|
case BrightCyan = 14;
|
||||||
|
case BrightWhite = 15;
|
||||||
|
}
|
11
src/Terminal/Enum/LibType.php
Normal file
11
src/Terminal/Enum/LibType.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Terminal\Enum;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Traits;
|
||||||
|
|
||||||
|
enum LibType: string {
|
||||||
|
use Traits\ConstList;
|
||||||
|
case GLIBC = "glibc";
|
||||||
|
case MUSL = "musl";
|
||||||
|
}
|
@ -1,9 +1,9 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo\Terminal;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\RawKeyCode;
|
|
||||||
use Aviat\Kilo\Enum\KeyType;
|
use Aviat\Kilo\Enum\KeyType;
|
||||||
|
use Aviat\Kilo\Enum\RawKeyCode;
|
||||||
use Aviat\Kilo\Type\TerminalSize;
|
use Aviat\Kilo\Type\TerminalSize;
|
||||||
|
|
||||||
class Terminal {
|
class Terminal {
|
||||||
@ -81,40 +81,38 @@ class Terminal {
|
|||||||
* Get the last key input from the terminal and convert to a
|
* Get the last key input from the terminal and convert to a
|
||||||
* more useful format
|
* more useful format
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string|KeyType
|
||||||
*/
|
*/
|
||||||
public static function readKey(): string
|
public static function readKey(): string|KeyType
|
||||||
{
|
{
|
||||||
$c = Terminal::read();
|
$c = Terminal::read();
|
||||||
|
|
||||||
$key = match($c)
|
return match($c)
|
||||||
{
|
{
|
||||||
// Unambiguous mappings
|
// Unambiguous mappings
|
||||||
RawKeyCode::ARROW_DOWN => KeyType::ARROW_DOWN,
|
RawKeyCode::ARROW_DOWN => KeyType::ArrowDown,
|
||||||
RawKeyCode::ARROW_LEFT => KeyType::ARROW_LEFT,
|
RawKeyCode::ARROW_LEFT => KeyType::ArrowLeft,
|
||||||
RawKeyCode::ARROW_RIGHT => KeyType::ARROW_RIGHT,
|
RawKeyCode::ARROW_RIGHT => KeyType::ArrowRight,
|
||||||
RawKeyCode::ARROW_UP => KeyType::ARROW_UP,
|
RawKeyCode::ARROW_UP => KeyType::ArrowUp,
|
||||||
RawKeyCode::DELETE => KeyType::DELETE,
|
RawKeyCode::DELETE => KeyType::Delete,
|
||||||
RawKeyCode::ENTER => KeyType::ENTER,
|
RawKeyCode::ENTER => KeyType::Enter,
|
||||||
RawKeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
|
RawKeyCode::PAGE_DOWN => KeyType::PageDown,
|
||||||
RawKeyCode::PAGE_UP => KeyType::PAGE_UP,
|
RawKeyCode::PAGE_UP => KeyType::PageUp,
|
||||||
|
|
||||||
// Backspace
|
// Backspace
|
||||||
RawKeyCode::CTRL('h'), RawKeyCode::BACKSPACE => KeyType::BACKSPACE,
|
RawKeyCode::CTRL('h'), RawKeyCode::BACKSPACE => KeyType::Backspace,
|
||||||
|
|
||||||
// Escape
|
// Escape
|
||||||
RawKeyCode::CTRL('l'), RawKeyCode::ESCAPE => KeyType::ESCAPE,
|
RawKeyCode::CTRL('l'), RawKeyCode::ESCAPE => KeyType::Escape,
|
||||||
|
|
||||||
// Home Key
|
// Home Key
|
||||||
"\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::HOME,
|
"\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::Home,
|
||||||
|
|
||||||
// End Key
|
// End Key
|
||||||
"\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::END,
|
"\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::End,
|
||||||
|
|
||||||
default => $c,
|
default => $c,
|
||||||
};
|
};
|
||||||
|
|
||||||
return $key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -128,7 +126,6 @@ class Terminal {
|
|||||||
/**
|
/**
|
||||||
* Write to the stdout stream
|
* Write to the stdout stream
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @param string $str
|
* @param string $str
|
||||||
* @param int|NULL $len
|
* @param int|NULL $len
|
||||||
* @return int|false
|
* @return int|false
|
||||||
@ -156,7 +153,6 @@ class Terminal {
|
|||||||
* See if tput exists for fallback terminal size detection
|
* See if tput exists for fallback terminal size detection
|
||||||
*
|
*
|
||||||
* @return bool
|
* @return bool
|
||||||
* @codeCoverageIgnore
|
|
||||||
*/
|
*/
|
||||||
private static function has_tput(): bool
|
private static function has_tput(): bool
|
||||||
{
|
{
|
@ -1,12 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo\Terminal;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Terminal\Enum\LibType;
|
||||||
use FFI;
|
use FFI;
|
||||||
use FFI\CData;
|
use FFI\CData;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\C;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implicit singleton wrapper around terminal settings to simplify enabling/disabling raw mode
|
* An implicit singleton wrapper around terminal settings to simplify enabling/disabling raw mode
|
||||||
*/
|
*/
|
||||||
@ -59,16 +58,13 @@ class Termios {
|
|||||||
register_shutdown_function([static::class, 'disableRawMode']);
|
register_shutdown_function([static::class, 'disableRawMode']);
|
||||||
|
|
||||||
$termios = clone $instance->originalTermios;
|
$termios = clone $instance->originalTermios;
|
||||||
$termios->c_iflag &= ~(C::BRKINT | C::ICRNL | C::INPCK | C::ISTRIP | C::IXON);
|
|
||||||
$termios->c_oflag = 0; // &= ~(C::OPOST);
|
|
||||||
$termios->c_cflag |= (C::CS8);
|
|
||||||
$termios->c_lflag &= ~( C::ECHO | C::ICANON | C::IEXTEN | C::ISIG );
|
|
||||||
$termios->c_cc[C::VMIN] = 0;
|
|
||||||
$termios->c_cc[C::VTIME] = 1;
|
|
||||||
|
|
||||||
// Turn on raw mode
|
// Use the stdlib func to set raw mode parameters
|
||||||
|
self::ffi()->cfmakeraw(FFI::addr($termios));
|
||||||
|
|
||||||
|
// Apply raw mode to the terminal
|
||||||
$res = self::ffi()
|
$res = self::ffi()
|
||||||
->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios));
|
->tcsetattr(C::STDIN_FILENO, C::TCSANOW, FFI::addr($termios));
|
||||||
|
|
||||||
return $res !== -1;
|
return $res !== -1;
|
||||||
}
|
}
|
||||||
@ -97,17 +93,21 @@ class Termios {
|
|||||||
/**
|
/**
|
||||||
* Get the size of the current terminal window
|
* Get the size of the current terminal window
|
||||||
*
|
*
|
||||||
* @codeCoverageIgnore
|
|
||||||
* @return array|null
|
* @return array|null
|
||||||
*/
|
*/
|
||||||
public static function getWindowSize(): ?array
|
public static function getWindowSize(): ?array
|
||||||
{
|
{
|
||||||
|
$res = NULL;
|
||||||
|
|
||||||
// First, try to get the answer from ioctl
|
// First, try to get the answer from ioctl
|
||||||
$ffi = self::ffi();
|
$ffi = self::ffi();
|
||||||
$ws = $ffi->new('struct winsize');
|
$ws = $ffi->new('struct winsize');
|
||||||
if ($ws !== NULL)
|
if ($ws !== NULL)
|
||||||
{
|
{
|
||||||
$res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
|
$res = (self::getLibType() === LibType::MUSL)
|
||||||
|
? $ffi->tcgetwinsize(C::STDOUT_FILENO, FFI::addr($ws))
|
||||||
|
: $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
|
||||||
|
|
||||||
if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0)
|
if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0)
|
||||||
{
|
{
|
||||||
return [$ws->ws_row, $ws->ws_col];
|
return [$ws->ws_row, $ws->ws_col];
|
||||||
@ -117,14 +117,33 @@ class Termios {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function getLibType(): LibType
|
||||||
|
{
|
||||||
|
static $type;
|
||||||
|
|
||||||
|
if ($type === NULL)
|
||||||
|
{
|
||||||
|
$maybeLibc = "/usr/lib/libc.so";
|
||||||
|
if (file_exists($maybeLibc))
|
||||||
|
{
|
||||||
|
$rawLibInfo = (string)shell_exec($maybeLibc);
|
||||||
|
if (str_contains(strtolower($rawLibInfo), "musl"))
|
||||||
|
{
|
||||||
|
$type = LibType::MUSL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$type = LibType::GLIBC;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
|
||||||
private static function getInstance(): self
|
private static function getInstance(): self
|
||||||
{
|
{
|
||||||
static $instance;
|
static $instance;
|
||||||
|
|
||||||
if ($instance === NULL)
|
$instance ??= new self();
|
||||||
{
|
|
||||||
$instance = new self();
|
|
||||||
}
|
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
@ -140,6 +159,12 @@ class Termios {
|
|||||||
|
|
||||||
if ($ffi === NULL)
|
if ($ffi === NULL)
|
||||||
{
|
{
|
||||||
|
if (self::getLibType() === LibType::MUSL)
|
||||||
|
{
|
||||||
|
$ffi = FFI::load(__DIR__ . '/ffi_musl.h');
|
||||||
|
return $ffi;
|
||||||
|
}
|
||||||
|
|
||||||
$ffi = FFI::load(__DIR__ . '/ffi.h');
|
$ffi = FFI::load(__DIR__ . '/ffi.h');
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,14 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo\Terminal;
|
||||||
|
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
class TermiosException extends \UnexpectedValueException {
|
class TermiosException extends \UnexpectedValueException {
|
||||||
public function __construct($message = 'Failed to apply terminal settings', $code = 0, Throwable $previous = NULL)
|
public function __construct(
|
||||||
|
string $message = 'Failed to apply terminal settings',
|
||||||
|
int $code = 0,
|
||||||
|
Throwable $previous = NULL)
|
||||||
{
|
{
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
}
|
}
|
@ -9,7 +9,7 @@
|
|||||||
// PHP 'constants' for FFI integration
|
// PHP 'constants' for FFI integration
|
||||||
// These seem to be the only define statements supported by the FFI integration
|
// These seem to be the only define statements supported by the FFI integration
|
||||||
#define FFI_SCOPE "terminal"
|
#define FFI_SCOPE "terminal"
|
||||||
#define FFI_LIB "libc.so.6"
|
#define FFI_LIB "libc.so"
|
||||||
|
|
||||||
// Nonsense for a test with a single quote
|
// Nonsense for a test with a single quote
|
||||||
// Ignored by PHP due to the octothorpe (#)
|
// Ignored by PHP due to the octothorpe (#)
|
||||||
@ -55,6 +55,7 @@ struct termios
|
|||||||
|
|
||||||
int tcgetattr (int fd, struct termios *termios_p);
|
int tcgetattr (int fd, struct termios *termios_p);
|
||||||
int tcsetattr (int fd, int optional_actions, const struct termios *termios_p);
|
int tcsetattr (int fd, int optional_actions, const struct termios *termios_p);
|
||||||
|
void cfmakeraw(struct termios *);
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
//! <sys/ioctl.h>
|
//! <sys/ioctl.h>
|
70
src/Terminal/ffi_musl.h
Normal file
70
src/Terminal/ffi_musl.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* Interfaces for PHP FFI
|
||||||
|
*
|
||||||
|
* Most of the structure code is cribbed from GLib
|
||||||
|
*
|
||||||
|
* Defines are not (generally) recognized by the FFI integration
|
||||||
|
*/
|
||||||
|
|
||||||
|
// PHP 'constants' for FFI integration
|
||||||
|
// These seem to be the only define statements supported by the FFI integration
|
||||||
|
#define FFI_SCOPE "terminal"
|
||||||
|
#define FFI_LIB "libc.so"
|
||||||
|
|
||||||
|
// Nonsense for a test with a single quote
|
||||||
|
// Ignored by PHP due to the octothorpe (#)
|
||||||
|
#if 0
|
||||||
|
# char* x = "String with \" escape char";
|
||||||
|
# char y = 'q';
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//! <termios.h>
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/* Type of terminal control flag masks. */
|
||||||
|
typedef unsigned long int tcflag_t;
|
||||||
|
|
||||||
|
/* Type of control characters. */
|
||||||
|
typedef unsigned char cc_t;
|
||||||
|
|
||||||
|
/* Type of baud rate specifiers. */
|
||||||
|
typedef long int speed_t;
|
||||||
|
|
||||||
|
/* Terminal control structure. */
|
||||||
|
struct termios
|
||||||
|
{
|
||||||
|
/* Input modes. */
|
||||||
|
tcflag_t c_iflag;
|
||||||
|
|
||||||
|
/* Output modes. */
|
||||||
|
tcflag_t c_oflag;
|
||||||
|
|
||||||
|
/* Control modes. */
|
||||||
|
tcflag_t c_cflag;
|
||||||
|
|
||||||
|
/* Local modes. */
|
||||||
|
tcflag_t c_lflag;
|
||||||
|
|
||||||
|
/* Control characters. */
|
||||||
|
cc_t c_cc[20];
|
||||||
|
|
||||||
|
/* Input and output baud rates. */
|
||||||
|
speed_t __ispeed, __ospeed;
|
||||||
|
};
|
||||||
|
|
||||||
|
int tcgetattr (int fd, struct termios *termios_p);
|
||||||
|
int tcsetattr (int fd, int optional_actions, const struct termios *termios_p);
|
||||||
|
void cfmakeraw(struct termios *);
|
||||||
|
int tcgetwinsize(int fd, struct winsize *);
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
//! <sys/ioctl.h>
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
struct winsize {
|
||||||
|
unsigned short ws_row;
|
||||||
|
unsigned short ws_col;
|
||||||
|
unsigned short ws_xpixel;
|
||||||
|
unsigned short ws_ypixel;
|
||||||
|
};
|
||||||
|
int ioctl (int, int, ...);
|
@ -13,7 +13,7 @@ class PHP8 extends PhpToken {
|
|||||||
private array $tokens = [];
|
private array $tokens = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use 'token_get_all' to get the tokens for a file,
|
* Use 'PhpToken' to get the tokens for a file,
|
||||||
* organized by row number
|
* organized by row number
|
||||||
*
|
*
|
||||||
* @param string $code
|
* @param string $code
|
||||||
@ -66,7 +66,7 @@ class PHP8 extends PhpToken {
|
|||||||
return $this->tokens;
|
return $this->tokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function processObjectToken(\PhpToken $token): void
|
protected function processObjectToken(PhpToken $token): void
|
||||||
{
|
{
|
||||||
$currentLine = $token->line;
|
$currentLine = $token->line;
|
||||||
$char = tabs_to_spaces($token->text);
|
$char = tabs_to_spaces($token->text);
|
||||||
|
20
src/Traits/EnumTrait.php
Normal file
20
src/Traits/EnumTrait.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo\Traits;
|
||||||
|
|
||||||
|
trait EnumTrait {
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
if (property_exists($this, 'value'))
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property_exists($this, 'name'))
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return print_r($this, true);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
// ! App Constants
|
|
||||||
// -----------------------------------------------------------------------------
|
|
||||||
const KILO_VERSION = '0.3.0';
|
|
||||||
const KILO_TAB_STOP = 4;
|
|
||||||
const KILO_QUIT_TIMES = 3;
|
|
||||||
|
|
||||||
const NO_MATCH = -1;
|
|
||||||
const T_RAW = -1;
|
|
20
test.php
20
test.php
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
interface Ifoo {}
|
interface Ifoo {}
|
||||||
|
|
||||||
|
// Let's see emoji! 🕯️😸⛩⛪
|
||||||
abstract class Foo implements Ifoo {
|
abstract class Foo implements Ifoo {
|
||||||
/**
|
/**
|
||||||
* @param int $a
|
* @param int $a
|
||||||
@ -13,9 +14,12 @@ abstract class Foo implements Ifoo {
|
|||||||
*/
|
*/
|
||||||
abstract public function bar(int $a, float $b, array $c, callable $d, string $e): string;
|
abstract public function bar(int $a, float $b, array $c, callable $d, string $e): string;
|
||||||
|
|
||||||
|
#[ReturnTypeWillChange]
|
||||||
protected function doNothing(): void {}
|
protected function doNothing(): void {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Attribute]
|
||||||
|
#[AllowDynamicProperties]
|
||||||
class Test {
|
class Test {
|
||||||
public function __construct(public string $foo, public string $bar) {}
|
public function __construct(public string $foo, public string $bar) {}
|
||||||
}
|
}
|
||||||
@ -24,14 +28,17 @@ class Test {
|
|||||||
* Docblock comment
|
* Docblock comment
|
||||||
*/
|
*/
|
||||||
class FooBar extends Foo implements Ifoo {
|
class FooBar extends Foo implements Ifoo {
|
||||||
public function bar(int $a, float $b, array $c, callable $d, string $e = 'default'): string
|
public function bar(int $a, float $b, array $c, callable $d, #[SensitiveParameter] string $e = 'default'): string
|
||||||
{
|
{
|
||||||
$cstr = print_r($c, TRUE);
|
$cstr = print_r($c, TRUE);
|
||||||
$d();
|
$d();
|
||||||
|
|
||||||
return "{$a}, ${b}, " . $cstr;
|
$r = $this->operations($a, (int)$b);
|
||||||
|
|
||||||
|
return "{$a}, ${b}, " . $cstr . " = {$r}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Test('a', 'b')]
|
||||||
private function operations(int $a, int $b): int
|
private function operations(int $a, int $b): int
|
||||||
{
|
{
|
||||||
$this?->x?->bar();
|
$this?->x?->bar();
|
||||||
@ -64,6 +71,10 @@ trait Baz {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BazFoo {
|
||||||
|
use Baz;
|
||||||
|
}
|
||||||
|
|
||||||
$square = fn (int $x) => $x ** 2;
|
$square = fn (int $x) => $x ** 2;
|
||||||
|
|
||||||
foreach ([-1, 0, 1, 2] as $x)
|
foreach ([-1, 0, 1, 2] as $x)
|
||||||
@ -100,7 +111,7 @@ $q = ($x !== 2)
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Heredoc
|
Heredoc
|
||||||
*/$z = $x + $y;
|
*/$z = $x + $y[0];
|
||||||
$sql = <<<SQL
|
$sql = <<<SQL
|
||||||
SELECT * FROM "foo" WHERE "bar"='baz' AND id={$x};
|
SELECT * FROM "foo" WHERE "bar"='baz' AND id={$x};
|
||||||
SQL;
|
SQL;
|
||||||
@ -117,7 +128,8 @@ TEMPLATE;
|
|||||||
<title>HTML</title>
|
<title>HTML</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1><?= $_SERVER['HTTP_HOST'] ?></h1>
|
<h1>Test</h1>
|
||||||
|
<div><?php $bf = new BazFoo(); print_r($bf->about()) ?></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<?php exit(); ?>
|
<?php exit(); ?>
|
||||||
|
@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo\Tests;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
use Aviat\Kilo\ANSI;
|
use Aviat\Kilo\Terminal\ANSI;
|
||||||
use Aviat\Kilo\Enum\Color;
|
use Aviat\Kilo\Terminal\Enum\Color;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class ANSITest extends TestCase {
|
class ANSITest extends TestCase {
|
||||||
|
@ -47,7 +47,7 @@ class EditorTest extends TestCase {
|
|||||||
|
|
||||||
public function testOpen(): void
|
public function testOpen(): void
|
||||||
{
|
{
|
||||||
$editor = MockEditor::mock('src/ffi.h');
|
$editor = MockEditor::mock('src/Terminal/ffi.h');
|
||||||
|
|
||||||
$state = json_encode($editor->__debugInfo(), JSON_THROW_ON_ERROR);
|
$state = json_encode($editor->__debugInfo(), JSON_THROW_ON_ERROR);
|
||||||
$this->assertMatchesJsonSnapshot($state);
|
$this->assertMatchesJsonSnapshot($state);
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo\Tests;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\Color;
|
|
||||||
use Aviat\Kilo\Enum\Highlight;
|
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
use function Aviat\Kilo\array_replace_range;
|
use function Aviat\Kilo\array_replace_range;
|
||||||
@ -14,7 +12,6 @@ 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_has;
|
use function Aviat\Kilo\str_has;
|
||||||
use function Aviat\Kilo\syntax_to_color;
|
|
||||||
use function Aviat\Kilo\tabs_to_spaces;
|
use function Aviat\Kilo\tabs_to_spaces;
|
||||||
|
|
||||||
class FunctionTest extends TestCase {
|
class FunctionTest extends TestCase {
|
||||||
@ -88,14 +85,6 @@ class FunctionTest extends TestCase {
|
|||||||
$this->assertTrue(is_separator("\0"));
|
$this->assertTrue(is_separator("\0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_syntax_to_color(): void
|
|
||||||
{
|
|
||||||
// Nonsense input returns FG::White
|
|
||||||
$this->assertEquals(syntax_to_color(999), Color::FG_WHITE);
|
|
||||||
|
|
||||||
$this->assertNotEquals(syntax_to_color(Highlight::OPERATOR), Color::FG_WHITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_str_contains(): void
|
public function test_str_contains(): void
|
||||||
{
|
{
|
||||||
// Search from string offset
|
// Search from string offset
|
||||||
|
@ -2,10 +2,9 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo\Tests;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
|
use Aviat\Kilo\Terminal\Terminal;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
use Aviat\Kilo\Terminal;
|
|
||||||
|
|
||||||
|
|
||||||
class TerminalTest extends TestCase {
|
class TerminalTest extends TestCase {
|
||||||
public function test_getWindowSize(): void
|
public function test_getWindowSize(): void
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
namespace Aviat\Kilo\Tests;
|
namespace Aviat\Kilo\Tests;
|
||||||
|
|
||||||
use Aviat\Kilo\{Termios, TermiosException};
|
use Aviat\Kilo\{Terminal\Termios, Terminal\TermiosException};
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class TermiosTest extends TestCase {
|
class TermiosTest extends TestCase {
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user