Refactor and simplify

This commit is contained in:
Timothy Warren 2020-02-05 14:50:31 -05:00
parent f8894b971a
commit 6f870aa922
11 changed files with 196 additions and 191 deletions

View File

@ -1,6 +1,6 @@
# PHP Kilo # PHP Kilo
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=Gitea+-+Tutorials%2Fphp-kilo%2Fmaster)](https://jenkins.timshomepage.net/job/Gitea%20-%20Tutorials/job/php-kilo/job/master/) [![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=timw4mail%2Fphp-kilo%2Fmaster)](https://jenkins.timshomepage.net/job/timw4mail/job/php-kilo/job/master/)
A reimplementation of the [Kilo](https://viewsourcecode.org/snaptoken/kilo/index.html) tutorial in PHP. Requires PHP 7.4, A reimplementation of the [Kilo](https://viewsourcecode.org/snaptoken/kilo/index.html) tutorial in PHP. Requires PHP 7.4,
due to requiring the `FFI` extension. due to requiring the `FFI` extension.

117
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "f11505a6676b236651e7784cfcd62ec8", "content-hash": "6fa43d16a15a27c27d5070fe477fd13f",
"packages": [], "packages": [],
"packages-dev": [ "packages-dev": [
{ {
@ -65,16 +65,16 @@
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.9.3", "version": "1.9.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/b2c28789e80a97badd14145fda39b545d83ca3ef",
"reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", "reference": "b2c28789e80a97badd14145fda39b545d83ca3ef",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -109,7 +109,7 @@
"object", "object",
"object graph" "object graph"
], ],
"time": "2019-08-09T12:45:53+00:00" "time": "2020-01-17T21:11:47+00:00"
}, },
{ {
"name": "phar-io/manifest", "name": "phar-io/manifest",
@ -267,16 +267,16 @@
}, },
{ {
"name": "phpdocumentor/reflection-docblock", "name": "phpdocumentor/reflection-docblock",
"version": "4.3.2", "version": "4.3.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e" "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/b83ff7cfcfee7827e1e78b637a5904fe6a96698e", "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/da3fd972d6bafd628114f7e7e036f45944b62e9c",
"reference": "b83ff7cfcfee7827e1e78b637a5904fe6a96698e", "reference": "da3fd972d6bafd628114f7e7e036f45944b62e9c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -288,6 +288,7 @@
"require-dev": { "require-dev": {
"doctrine/instantiator": "^1.0.5", "doctrine/instantiator": "^1.0.5",
"mockery/mockery": "^1.0", "mockery/mockery": "^1.0",
"phpdocumentor/type-resolver": "0.4.*",
"phpunit/phpunit": "^6.4" "phpunit/phpunit": "^6.4"
}, },
"type": "library", "type": "library",
@ -314,7 +315,7 @@
} }
], ],
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
"time": "2019-09-12T14:27:41+00:00" "time": "2019-12-28T18:55:12+00:00"
}, },
{ {
"name": "phpdocumentor/type-resolver", "name": "phpdocumentor/type-resolver",
@ -365,33 +366,33 @@
}, },
{ {
"name": "phpspec/prophecy", "name": "phpspec/prophecy",
"version": "1.9.0", "version": "v1.10.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpspec/prophecy.git", "url": "https://github.com/phpspec/prophecy.git",
"reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203" "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/f6811d96d97bdf400077a0cc100ae56aa32b9203", "url": "https://api.github.com/repos/phpspec/prophecy/zipball/b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
"reference": "f6811d96d97bdf400077a0cc100ae56aa32b9203", "reference": "b4400efc9d206e83138e2bb97ed7f5b14b831cd9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"doctrine/instantiator": "^1.0.2", "doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0", "php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
"sebastian/comparator": "^1.1|^2.0|^3.0", "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0" "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
}, },
"require-dev": { "require-dev": {
"phpspec/phpspec": "^2.5|^3.2", "phpspec/phpspec": "^2.5 || ^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.8.x-dev" "dev-master": "1.10.x-dev"
} }
}, },
"autoload": { "autoload": {
@ -424,7 +425,7 @@
"spy", "spy",
"stub" "stub"
], ],
"time": "2019-10-03T11:07:50+00:00" "time": "2020-01-20T15:57:02+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -680,16 +681,16 @@
}, },
{ {
"name": "phpunit/phpunit", "name": "phpunit/phpunit",
"version": "8.4.3", "version": "8.5.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git", "url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e" "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/018b6ac3c8ab20916db85fa91bf6465acb64d1e0",
"reference": "67f9e35bffc0dd52d55d565ddbe4230454fd6a4e", "reference": "018b6ac3c8ab20916db85fa91bf6465acb64d1e0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -733,7 +734,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "8.4-dev" "dev-master": "8.5-dev"
} }
}, },
"autoload": { "autoload": {
@ -759,7 +760,7 @@
"testing", "testing",
"xunit" "xunit"
], ],
"time": "2019-11-06T09:42:23+00:00" "time": "2020-01-08T08:49:49+00:00"
}, },
{ {
"name": "sebastian/code-unit-reverse-lookup", "name": "sebastian/code-unit-reverse-lookup",
@ -1378,23 +1379,23 @@
}, },
{ {
"name": "spatie/phpunit-snapshot-assertions", "name": "spatie/phpunit-snapshot-assertions",
"version": "2.2.0", "version": "2.2.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/phpunit-snapshot-assertions.git", "url": "https://github.com/spatie/phpunit-snapshot-assertions.git",
"reference": "7da647e383d5ba960b384a45e8bd59c4211b366d" "reference": "cc6769ab92a41d1d58d72f228e15d82d180f0b44"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/phpunit-snapshot-assertions/zipball/7da647e383d5ba960b384a45e8bd59c4211b366d", "url": "https://api.github.com/repos/spatie/phpunit-snapshot-assertions/zipball/cc6769ab92a41d1d58d72f228e15d82d180f0b44",
"reference": "7da647e383d5ba960b384a45e8bd59c4211b366d", "reference": "cc6769ab92a41d1d58d72f228e15d82d180f0b44",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"ext-dom": "*", "ext-dom": "*",
"php": "^7.2", "php": "^7.2",
"phpunit/phpunit": "^8.0", "phpunit/phpunit": "^8.0",
"symfony/yaml": "^4.0" "symfony/yaml": "^4.0|^5.0"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -1424,20 +1425,20 @@
"spatie", "spatie",
"testing" "testing"
], ],
"time": "2019-10-23T15:00:34+00:00" "time": "2019-11-21T23:15:29+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
"version": "v1.12.0", "version": "v1.13.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git", "url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4" "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4", "reference": "f8f0b461be3385e56d6de3dbb5a0df24c0c275e3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1449,7 +1450,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.12-dev" "dev-master": "1.13-dev"
} }
}, },
"autoload": { "autoload": {
@ -1482,31 +1483,31 @@
"polyfill", "polyfill",
"portable" "portable"
], ],
"time": "2019-08-06T08:03:45+00:00" "time": "2019-11-27T13:56:44+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v4.3.8", "version": "v5.0.4",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/yaml.git", "url": "https://github.com/symfony/yaml.git",
"reference": "324cf4b19c345465fad14f3602050519e09e361d" "reference": "69b44e3b8f90949aee2eb3aa9b86ceeb01cbf62a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/324cf4b19c345465fad14f3602050519e09e361d", "url": "https://api.github.com/repos/symfony/yaml/zipball/69b44e3b8f90949aee2eb3aa9b86ceeb01cbf62a",
"reference": "324cf4b19c345465fad14f3602050519e09e361d", "reference": "69b44e3b8f90949aee2eb3aa9b86ceeb01cbf62a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.1.3", "php": "^7.2.5",
"symfony/polyfill-ctype": "~1.8" "symfony/polyfill-ctype": "~1.8"
}, },
"conflict": { "conflict": {
"symfony/console": "<3.4" "symfony/console": "<4.4"
}, },
"require-dev": { "require-dev": {
"symfony/console": "~3.4|~4.0" "symfony/console": "^4.4|^5.0"
}, },
"suggest": { "suggest": {
"symfony/console": "For validating YAML files using the lint command" "symfony/console": "For validating YAML files using the lint command"
@ -1514,7 +1515,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.3-dev" "dev-master": "5.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -1541,7 +1542,7 @@
], ],
"description": "Symfony Yaml Component", "description": "Symfony Yaml Component",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"time": "2019-10-30T12:58:49+00:00" "time": "2020-01-21T11:12:28+00:00"
}, },
{ {
"name": "theseer/tokenizer", "name": "theseer/tokenizer",
@ -1585,31 +1586,29 @@
}, },
{ {
"name": "webmozart/assert", "name": "webmozart/assert",
"version": "1.5.0", "version": "1.6.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/webmozart/assert.git", "url": "https://github.com/webmozart/assert.git",
"reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" "reference": "573381c0a64f155a0d9a23f4b0c797194805b925"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", "url": "https://api.github.com/repos/webmozart/assert/zipball/573381c0a64f155a0d9a23f4b0c797194805b925",
"reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", "reference": "573381c0a64f155a0d9a23f4b0c797194805b925",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^5.3.3 || ^7.0", "php": "^5.3.3 || ^7.0",
"symfony/polyfill-ctype": "^1.8" "symfony/polyfill-ctype": "^1.8"
}, },
"conflict": {
"vimeo/psalm": "<3.6.0"
},
"require-dev": { "require-dev": {
"phpunit/phpunit": "^4.8.36 || ^7.5.13" "phpunit/phpunit": "^4.8.36 || ^7.5.13"
}, },
"type": "library", "type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Webmozart\\Assert\\": "src/" "Webmozart\\Assert\\": "src/"
@ -1631,7 +1630,7 @@
"check", "check",
"validate" "validate"
], ],
"time": "2019-08-24T08:43:50+00:00" "time": "2019-11-24T13:36:37+00:00"
} }
], ],
"aliases": [], "aliases": [],
@ -1642,5 +1641,7 @@
"platform": { "platform": {
"ext-ffi": "*" "ext-ffi": "*"
}, },
"platform-dev": [] "platform-dev": {
"ext-json": "*"
}
} }

View File

@ -135,35 +135,25 @@ class Editor {
} }
// In PHP, `strchr` and `strstr` are the same function // In PHP, `strchr` and `strstr` are the same function
$ext = (string)strstr($this->filename, '.'); $ext = (string)strstr(basename($this->filename), '.');
foreach (get_file_syntax_map() as $syntax) foreach (get_file_syntax_map() as $syntax)
{ {
foreach ($syntax->filematch as $searchExt) if (
{ in_array($ext, $syntax->filematch, TRUE) ||
$is_ext = (strpos($searchExt, '.') === 0); in_array(basename($this->filename), $syntax->filematch, TRUE)
if ( ) {
($is_ext && ( ! strcmp($ext, $searchExt))) || $this->syntax = $syntax;
(( ! $is_ext) && strpos($this->filename, $searchExt) !== FALSE)
) {
$this->syntax = $syntax;
// Pre-tokenize the file // Pre-tokenize the file
if ($this->syntax->filetype === 'PHP') if ($this->syntax->filetype === 'PHP')
{ {
$this->tokens = PHP::getFileTokens($this->filename); $this->tokens = PHP::getFileTokens($this->filename);
}
// Update the syntax highlighting for all the rows of the file
for ($i = 0; $i < $this->numRows; $i++)
{
// @codeCoverageIgnoreStart
$this->rows[$i]->updateSyntax();
// @codeCoverageIgnoreEnd
}
return;
} }
$this->refreshSyntax();
return;
} }
} }
} }
@ -177,7 +167,7 @@ class Editor {
$rx = 0; $rx = 0;
for ($i = 0; $i < $cx; $i++) for ($i = 0; $i < $cx; $i++)
{ {
if ($row->chars[$i] === "\t") if ($row->chars[$i] === KeyCode::TAB)
{ {
$rx += (KILO_TAB_STOP - 1) - ($rx % KILO_TAB_STOP); $rx += (KILO_TAB_STOP - 1) - ($rx % KILO_TAB_STOP);
} }
@ -192,7 +182,7 @@ class Editor {
$cur_rx = 0; $cur_rx = 0;
for ($cx = 0; $cx < $row->size; $cx++) for ($cx = 0; $cx < $row->size; $cx++)
{ {
if ($row->chars[$cx] === "\t") if ($row->chars[$cx] === KeyCode::TAB)
{ {
$cur_rx += (KILO_TAB_STOP - 1) - ($cur_rx % KILO_TAB_STOP); $cur_rx += (KILO_TAB_STOP - 1) - ($cur_rx % KILO_TAB_STOP);
} }
@ -231,12 +221,6 @@ class Editor {
ksort($this->rows); ksort($this->rows);
// Update row indexes
for ($i = 0; $i < $this->numRows; $i++)
{
$this->rows[$i]->idx = $i;
}
$this->rows[$at]->update(); $this->rows[$at]->update();
$this->dirty++; $this->dirty++;
@ -358,15 +342,11 @@ class Editor {
$this->selectSyntaxHighlight(); $this->selectSyntaxHighlight();
// #TODO gracefully handle issues with loading a file
$handle = fopen($filename, 'rb'); $handle = fopen($filename, 'rb');
if ($handle === FALSE) if ($handle === FALSE)
{ {
write_stdout(ANSI::CLEAR_SCREEN); $this->setStatusMessage('Failed to open file: %s', $filename);
write_stdout(ANSI::RESET_CURSOR); // Reposition cursor to top-left return;
Termios::disableRawMode();
print_r(error_get_last());
die();
} }
while (($line = fgets($handle)) !== FALSE) while (($line = fgets($handle)) !== FALSE)
@ -426,25 +406,27 @@ class Editor {
$savedHl = []; $savedHl = [];
} }
if ($key === "\r" || $key === "\e") switch ($key)
{ {
$lastMatch = -1; case KeyCode::ENTER:
$direction = 1; case KeyCode::ESCAPE:
return; $lastMatch = -1;
} $direction = 1;
return;
if ($key === KeyType::ARROW_RIGHT || $key === KeyType::ARROW_DOWN) case KeyType::ARROW_DOWN:
{ case KeyType::ARROW_RIGHT:
$direction = 1; $direction = 1;
} break;
else if ($key === KeyType::ARROW_LEFT || $key === KeyType::ARROW_UP)
{ case KeyType::ARROW_UP:
$direction = -1; case KeyType::ARROW_LEFT:
} $direction = -1;
else break;
{
$lastMatch = -1; default:
$direction = 1; $lastMatch = -1;
$direction = 1;
} }
if ($lastMatch === -1) if ($lastMatch === -1)
@ -452,11 +434,6 @@ class Editor {
$direction = 1; $direction = 1;
} }
if (empty($query))
{
return;
}
$current = $lastMatch; $current = $lastMatch;
for ($i = 0; $i < $this->numRows; $i++) for ($i = 0; $i < $this->numRows; $i++)
@ -598,7 +575,7 @@ class Editor {
for ($i = 0; $i < $len; $i++) for ($i = 0; $i < $len; $i++)
{ {
// Handle 'non-printable' characters // Handle 'non-printable' characters
if (is_cntrl($c[$i])) if (is_ctrl($c[$i]))
{ {
$sym = (ord($c[$i]) <= 26) $sym = (ord($c[$i]) <= 26)
? chr(ord('@') + ord($c[$i])) ? chr(ord('@') + ord($c[$i]))
@ -736,7 +713,7 @@ class Editor {
$c = $this->readKey(); $c = $this->readKey();
if ($c === KeyType::ESCAPE) if ($c === KeyType::ESCAPE || ($c === KeyType::ENTER && $buffer !== ''))
{ {
$this->setStatusMessage(''); $this->setStatusMessage('');
if ($callback !== NULL) if ($callback !== NULL)
@ -746,21 +723,11 @@ class Editor {
return ''; return '';
} }
if ($c === KeyType::ENTER && $buffer !== '') if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE || KeyType::CTRL('h'))
{
$this->setStatusMessage('');
if ($callback !== NULL)
{
$callback($buffer, $c);
}
return $buffer;
}
if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE || $c === chr(ctrl_key('h')))
{ {
$buffer = substr($buffer, 0, -1); $buffer = substr($buffer, 0, -1);
} }
else if (is_ascii($c) && ( ! is_cntrl($c)) && ! in_array($c, $modifiers, TRUE)) else if (is_ascii($c) && ( ! is_ctrl($c)) && ! in_array($c, $modifiers, TRUE))
{ {
$buffer .= $c; $buffer .= $c;
} }
@ -835,7 +802,7 @@ class Editor {
$c = $this->readKey(); $c = $this->readKey();
if ($c === "\0" || $c === '') if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
{ {
return ''; return '';
} }
@ -846,7 +813,7 @@ class Editor {
$this->insertNewline(); $this->insertNewline();
break; break;
case chr(ctrl_key('q')): case KeyType::CTRL('q'):
if ($this->dirty > 0 && $quit_times > 0) if ($this->dirty > 0 && $quit_times > 0)
{ {
$this->setStatusMessage('WARNING!!! File has unsaved changes.' . $this->setStatusMessage('WARNING!!! File has unsaved changes.' .
@ -859,7 +826,7 @@ class Editor {
return NULL; return NULL;
break; break;
case chr(ctrl_key('s')): case KeyType::CTRL('s'):
$this->save(); $this->save();
break; break;
@ -874,12 +841,12 @@ class Editor {
} }
break; break;
case chr(ctrl_key('f')): case KeyType::CTRL('f'):
$this->find(); $this->find();
break; break;
case KeyType::BACKSPACE: case KeyType::BACKSPACE:
case chr(ctrl_key('h')): case KeyType::CTRL('h'):
case KeyType::DEL_KEY: case KeyType::DEL_KEY:
if ($c === KeyType::DEL_KEY) if ($c === KeyType::DEL_KEY)
{ {
@ -900,7 +867,7 @@ class Editor {
$this->moveCursor($c); $this->moveCursor($c);
break; break;
case chr(ctrl_key('l')): case KeyType::CTRL('l'):
case KeyType::ESCAPE: case KeyType::ESCAPE:
// Do nothing // Do nothing
break; break;
@ -915,7 +882,7 @@ class Editor {
return $c; return $c;
} }
private function pageUpOrDown(string $c): void public function pageUpOrDown(string $c): void
{ {
if ($c === KeyType::PAGE_UP) if ($c === KeyType::PAGE_UP)
{ {
@ -937,6 +904,12 @@ class Editor {
} }
} }
protected function refreshSyntax(): void
{
// Update the syntax highlighting for all the rows of the file
array_walk($this->rows, fn (Row $row) => $row->updateSyntax());
}
private function refreshPHPSyntax(): void private function refreshPHPSyntax(): void
{ {
if ($this->syntax->filetype !== 'PHP') if ($this->syntax->filetype !== 'PHP')
@ -945,9 +918,6 @@ class Editor {
} }
$this->tokens = PHP::getTokens($this->rowsToString()); $this->tokens = PHP::getTokens($this->rowsToString());
for ($i = 0; $i < $this->numRows; $i++) $this->refreshSyntax();
{
$this->rows[$i]->update();
}
} }
} }

View File

@ -1,12 +0,0 @@
<?php declare(strict_types=1);
namespace Aviat\Kilo\Enum;
use Aviat\Kilo\Traits;
class Event {
use Traits\ConstList;
public const INPUT_KEY = 'INPUT_KEY';
public const QUIT_ATTEMPT = 'QUIT_ATTEMPT';
}

View File

@ -4,6 +4,9 @@ namespace Aviat\Kilo\Enum;
use Aviat\Kilo\Traits; use Aviat\Kilo\Traits;
/**
* 'Raw' input from stdin
*/
class KeyCode { class KeyCode {
use Traits\ConstList; use Traits\ConstList;
@ -12,9 +15,17 @@ class KeyCode {
public const ARROW_RIGHT = "\e[C"; public const ARROW_RIGHT = "\e[C";
public const ARROW_UP = "\e[A"; public const ARROW_UP = "\e[A";
public const BACKSPACE = "\x7f"; public const BACKSPACE = "\x7f";
public const CARRIAGE_RETURN = "\r";
public const DEL_KEY = "\e[3~"; public const DEL_KEY = "\e[3~";
public const EMPTY = '';
public const ENTER = "\r"; public const ENTER = "\r";
public const ESCAPE = "\e"; public const ESCAPE = "\e";
public const FORM_FEED = "\f";
public const NEWLINE = "\n";
public const NULL = "\0";
public const PAGE_DOWN = "\e[6~"; public const PAGE_DOWN = "\e[6~";
public const PAGE_UP = "\e[5~"; public const PAGE_UP = "\e[5~";
public const SPACE = ' ';
public const TAB = "\t";
public const VERTICAL_TAB = "\v";
} }

View File

@ -3,7 +3,11 @@
namespace Aviat\Kilo\Enum; namespace Aviat\Kilo\Enum;
use Aviat\Kilo\Traits; use Aviat\Kilo\Traits;
use function Aviat\Kilo\ctrl_key;
/**
* Constants representing various control keys
*/
class KeyType { class KeyType {
use Traits\ConstList; use Traits\ConstList;
@ -19,4 +23,27 @@ class KeyType {
public const HOME_KEY = 'HOME'; public const HOME_KEY = 'HOME';
public const PAGE_DOWN = 'PAGE_DOWN'; public const PAGE_DOWN = 'PAGE_DOWN';
public const PAGE_UP = 'PAGE_UP'; public const PAGE_UP = 'PAGE_UP';
/**
* Returns the ascii character for the specified
* ctrl + letter combo
*
* @param string $char
* @return string
*/
public static function CTRL(string $char): ?string
{
$char = strtolower($char);
$ord = ord($char);
// a = 0x61
// z = 0x7a
if ($ord >= 0x61 && $ord <= 0x7a)
{
return chr(ctrl_key($char));
}
// Invalid input, not an ascii letter
return NULL;
}
} }

View File

@ -2,9 +2,18 @@
namespace Aviat\Kilo; namespace Aviat\Kilo;
use Aviat\Kilo\Enum\Event as EventEnum;
class Event { class Event {
use Traits\ConstList;
// ------------------------------------------------------------------------
// Valid Events
// ------------------------------------------------------------------------
public const INPUT_KEY = 'INPUT_KEY';
public const PAGE_CHANGE = 'PAGE_CHANGE';
public const MOVE_CURSOR = 'MOVE_CURSOR';
public const QUIT_ATTEMPT = 'QUIT_ATTEMPT';
// Mapping of events to handlers
private static $subscribeMap = []; private static $subscribeMap = [];
public static function fire(string $eventName, $value): void public static function fire(string $eventName, $value): void
@ -20,7 +29,7 @@ class Event {
} }
} }
public static function bind(string $eventName, callable $fn): void public static function on(string $eventName, callable $fn): void
{ {
static::validateEvent($eventName); static::validateEvent($eventName);
@ -37,11 +46,11 @@ class Event {
private static function validateEvent(string $eventName): void private static function validateEvent(string $eventName): void
{ {
$validEvents = EventEnum::getConstList(); $validEvents = self::getConstList();
if ( ! array_key_exists($eventName, $validEvents)) if ( ! array_key_exists($eventName, $validEvents))
{ {
throw new \InvalidArgumentException("Invalid event '{$eventName}'. Event const must exist in Aviat\\Kilo\\Enum\\Event."); throw new \InvalidArgumentException("Invalid event '{$eventName}'. Event const must exist in Aviat\\Kilo\\Event.");
} }
} }
} }

View File

@ -3,6 +3,7 @@
namespace Aviat\Kilo; namespace Aviat\Kilo;
use Aviat\Kilo\Enum\Highlight; use Aviat\Kilo\Enum\Highlight;
use Aviat\Kilo\Enum\KeyCode;
/** /**
* @property-read int size * @property-read int size
@ -413,7 +414,7 @@ class Row {
$klen = strlen($k); $klen = strlen($k);
$nextCharOffset = $i + $klen; $nextCharOffset = $i + $klen;
$isEndOfLine = $nextCharOffset >= $this->rsize; $isEndOfLine = $nextCharOffset >= $this->rsize;
$nextChar = ($isEndOfLine) ? "\0" : $this->render[$nextCharOffset]; $nextChar = ($isEndOfLine) ? KeyCode::NULL : $this->render[$nextCharOffset];
if (substr($this->render, $i, $klen) === $k && is_separator($nextChar)) if (substr($this->render, $i, $klen) === $k && is_separator($nextChar))
{ {
@ -559,14 +560,12 @@ class Row {
} }
// Types/identifiers/keywords that don't have their own token // Types/identifiers/keywords that don't have their own token
if ($token['type'] === T_STRING) if ($token['type'] === T_STRING &&
in_array($token['char'], $this->parent->syntax->keywords2, TRUE))
{ {
if (in_array($token['char'], $this->parent->syntax->keywords2, TRUE)) array_replace_range($this->hl, $charStart, $charLen, Highlight::KEYWORD2);
{ $offset = $charEnd;
array_replace_range($this->hl, $charStart, $charLen, Highlight::KEYWORD2); continue;
$offset = $charEnd;
continue;
}
} }
} }

View File

@ -4,11 +4,7 @@ namespace Aviat\Kilo;
use FFI; use FFI;
use Aviat\Kilo\Enum\{ use Aviat\Kilo\Enum\{C, Color, Highlight, KeyCode};
C,
Color,
Highlight,
};
/** /**
* See if tput exists for fallback terminal size detection * See if tput exists for fallback terminal size detection
@ -18,7 +14,7 @@ use Aviat\Kilo\Enum\{
*/ */
function has_tput(): bool function has_tput(): bool
{ {
return (int)shell_exec('type tput') === 0; return str_contains(shell_exec('type tput'), ' is ');
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -104,7 +100,7 @@ function is_ascii(string $single_char): bool
* @param string $char * @param string $char
* @return bool * @return bool
*/ */
function is_cntrl(string $char): bool function is_ctrl(string $char): bool
{ {
$c = ord($char); $c = ord($char);
return is_ascii($char) && ( $c === 0x7f || $c < 0x20 ); return is_ascii($char) && ( $c === 0x7f || $c < 0x20 );
@ -130,7 +126,14 @@ function is_digit(string $char): bool
*/ */
function is_space(string $char): bool function is_space(string $char): bool
{ {
$ws = [' ', "\t", "\n", "\r", "\v", "\f"]; $ws = [
KeyCode::CARRIAGE_RETURN,
KeyCode::FORM_FEED,
KeyCode::NEWLINE,
KeyCode::SPACE,
KeyCode::TAB,
KeyCode::VERTICAL_TAB,
];
return is_ascii($char) && in_array($char, $ws, TRUE); return is_ascii($char) && in_array($char, $ws, TRUE);
} }
@ -168,11 +171,9 @@ function is_separator(string $char): bool
return FALSE; return FALSE;
} }
// `strpos` is used instead of `strchr`/`strstr` as we don't care about the actual match $isSep = str_contains(',.()+-/*=~%<>[];', $char);
// while `strchr` would match the C version, it also returns the match
$isSep = (strpos(',.()+-/*=~%<>[];', $char) !== FALSE);
return is_space($char) || $char === "\0" || $isSep; return is_space($char) || $char === KeyCode::NULL || $isSep;
} }
/** /**
@ -287,7 +288,7 @@ function syntax_to_color(int $hl): int
*/ */
function tabs_to_spaces(string $str, ?int $number = KILO_TAB_STOP): string function tabs_to_spaces(string $str, ?int $number = KILO_TAB_STOP): string
{ {
return str_replace("\t", str_repeat(' ', $number), $str); return str_replace(KeyCode::TAB, str_repeat(KeyCode::SPACE, $number), $str);
} }
/** /**

View File

@ -3,7 +3,6 @@
namespace Aviat\Kilo\Tests; namespace Aviat\Kilo\Tests;
use Aviat\Kilo\Event; use Aviat\Kilo\Event;
use Aviat\Kilo\Enum\Event as EventType;
use InvalidArgumentException; use InvalidArgumentException;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
@ -11,7 +10,7 @@ class EventTest extends TestCase {
public function testRequiresValidEvent(): void public function testRequiresValidEvent(): void
{ {
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
Event::bind('badEventName', fn () => null); Event::on('badEventName', fn () => null);
$this->expectException(InvalidArgumentException::class); $this->expectException(InvalidArgumentException::class);
Event::fire('badEventName', []); Event::fire('badEventName', []);
@ -22,7 +21,7 @@ class EventTest extends TestCase {
$fn = static function($value = false) { $fn = static function($value = false) {
static::assertTrue($value); static::assertTrue($value);
}; };
Event::bind(EventType::INPUT_KEY, $fn); Event::on(Event::INPUT_KEY, $fn);
Event::fire(EventType::INPUT_KEY, TRUE); Event::fire(Event::INPUT_KEY, TRUE);
} }
} }

View File

@ -11,7 +11,7 @@ use function Aviat\Kilo\ctrl_key;
use function Aviat\Kilo\get_file_syntax_map; use function Aviat\Kilo\get_file_syntax_map;
use function Aviat\Kilo\get_window_size; use function Aviat\Kilo\get_window_size;
use function Aviat\Kilo\is_ascii; use function Aviat\Kilo\is_ascii;
use function Aviat\Kilo\is_cntrl; use function Aviat\Kilo\is_ctrl;
use function Aviat\Kilo\is_digit; use function Aviat\Kilo\is_digit;
use function Aviat\Kilo\is_separator; use function Aviat\Kilo\is_separator;
use function Aviat\Kilo\is_space; use function Aviat\Kilo\is_space;
@ -42,22 +42,22 @@ class FunctionTest extends TestCase {
$this->assertEquals(0x01, ctrl_key('a')); $this->assertEquals(0x01, ctrl_key('a'));
} }
public function test_is_cntrl(): void public function test_is_ctrl(): void
{ {
for ($i = 0x0; $i < 0x20; $i++) for ($i = 0x0; $i < 0x20; $i++)
{ {
$char = chr($i); $char = chr($i);
$this->assertTrue(is_cntrl($char), 'Should be a control character'); $this->assertTrue(is_ctrl($char), 'Should be a control character');
} }
for ($n = 0x20; $n < 0x7f; $n++) for ($n = 0x20; $n < 0x7f; $n++)
{ {
$char = chr($n); $char = chr($n);
$this->assertFalse(is_cntrl($char), 'Should not be a control character'); $this->assertFalse(is_ctrl($char), 'Should not be a control character');
} }
// Escape, code 7f, is an outlier // Escape, code 7f, is an outlier
$this->assertTrue(is_cntrl(chr(0x7f))); $this->assertTrue(is_ctrl(chr(0x7f)));
} }
public function test_is_space(): void public function test_is_space(): void