Create php8 fork
All checks were successful
timw4mail/php-kilo/pipeline/head This commit looks good

This commit is contained in:
Timothy Warren 2020-12-04 11:18:21 -05:00
parent 1e4735ee38
commit 7cd7d1baa5
11 changed files with 2718 additions and 1184 deletions

View File

@ -15,8 +15,8 @@
}, },
"require-dev": { "require-dev": {
"ext-json": "*", "ext-json": "*",
"phpunit/phpunit": "^8", "phpunit/phpunit": "^9.5.0",
"spatie/phpunit-snapshot-assertions": "^2.2.0" "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",

2072
composer.lock generated

File diff suppressed because it is too large Load Diff

43
kilo
View File

@ -6,16 +6,16 @@ namespace Aviat\Kilo;
require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/vendor/autoload.php';
// Log notices/errors/warnings to file // Log notices/errors/warnings to file
set_error_handler(static function (int $no, $str, $file, $line) { set_exception_handler(static function (\Throwable $e) {
$msg = print_r([ $msg = print_r([
'errno' => $no, 'code' => $e->getCode(),
'message' => $str, 'message' => $e->getMessage(),
'file' => $file, 'file' => $e->getFile(),
'line' => $line, 'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
], TRUE); ], TRUE);
file_put_contents('kilo.log', $msg, FILE_APPEND); file_put_contents('kilo.log', $msg, FILE_APPEND);
});
}, -1);
// ! Init with an IIFE // ! Init with an IIFE
return (static function (int $argc, array $argv): int { return (static function (int $argc, array $argv): int {
@ -23,38 +23,15 @@ return (static function (int $argc, array $argv): int {
register_shutdown_function([Termios::class, 'disableRawMode']); register_shutdown_function([Termios::class, 'disableRawMode']);
$editor = Editor::new(); $editor = Editor::new();
$editor->setStatusMessage('HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find');
if ($argc >= 2) if ($argc >= 2)
{ {
$editor->open($argv[1]); $editor->open($argv[1]);
} }
$editor->setStatusMessage('HELP: Ctrl-S = save | Ctrl-Q = quit | Ctrl-F = find'); // Input Loop
do { $editor->refreshScreen();} while ($editor->processKeypress() !== NULL);
try
{
// Input Loop
while (true)
{
$editor->refreshScreen();
$char = $editor->processKeypress();
if ($char === NULL)
{
break;
}
}
}
catch (\Throwable $e)
{
$msg = print_r([
'code' => $e->getCode(),
'message' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'trace' => $e->getTraceAsString(),
], TRUE);
file_put_contents('kilo.exception.log', $msg, FILE_APPEND);
}
return 0; return 0;
})($argc, $argv); })($argc, $argv);

View File

@ -1,24 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<phpunit <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" colors="true" stopOnFailure="false" beStrictAboutTestsThatDoNotTestAnything="true" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
colors="true" <coverage>
stopOnFailure="false" <include>
beStrictAboutTestsThatDoNotTestAnything="true" <directory suffix=".php">src</directory>
> </include>
<filter> <exclude>
<whitelist> <file>src/constants.php</file>
<directory suffix=".php">src</directory> </exclude>
<exclude> <report>
<file>src/constants.php</file> <html outputDirectory="coverage"/>
</exclude> <text outputFile="php://stdout" showUncoveredFiles="true"/>
</whitelist> </report>
</filter> </coverage>
<testsuites> <testsuites>
<testsuite name="PHPKilo"> <testsuite name="PHPKilo">
<directory phpVersion="7.4.0" phpVersionOperator=">=">tests</directory> <directory phpVersion="7.4.0" phpVersionOperator="&gt;=">tests</directory>
</testsuite> </testsuite>
</testsuites> </testsuites>
<logging> <logging/>
<log type="coverage-html" target="coverage"/> </phpunit>
<log type="coverage-text" target="php://stdout" showUncoveredFiles="true" />
</logging>
</phpunit>

View File

@ -52,7 +52,7 @@ class Editor {
$this->screenRows -= 2; $this->screenRows -= 2;
} }
public function __get(string $name) public function __get(string $name): ?int
{ {
if ($name === 'numRows') if ($name === 'numRows')
{ {
@ -96,7 +96,6 @@ class Editor {
KeyCode::ARROW_UP => KeyType::ARROW_UP, KeyCode::ARROW_UP => KeyType::ARROW_UP,
KeyCode::DEL_KEY => KeyType::DEL_KEY, KeyCode::DEL_KEY => KeyType::DEL_KEY,
KeyCode::ENTER => KeyType::ENTER, KeyCode::ENTER => KeyType::ENTER,
KeyCode::ESCAPE => KeyType::ESCAPE,
KeyCode::PAGE_DOWN => KeyType::PAGE_DOWN, KeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
KeyCode::PAGE_UP => KeyType::PAGE_UP, KeyCode::PAGE_UP => KeyType::PAGE_UP,
@ -104,6 +103,10 @@ class Editor {
KeyCode::CTRL('h') => KeyType::BACKSPACE, KeyCode::CTRL('h') => KeyType::BACKSPACE,
KeyCode::BACKSPACE => KeyType::BACKSPACE, KeyCode::BACKSPACE => KeyType::BACKSPACE,
// Escape
KeyCode::CTRL('l') => KeyType::ESCAPE,
KeyCode::ESCAPE => KeyType::ESCAPE,
// Home Key // Home Key
"\eOH" => KeyType::HOME_KEY, "\eOH" => KeyType::HOME_KEY,
"\e[1~" => KeyType::HOME_KEY, "\e[1~" => KeyType::HOME_KEY,
@ -271,6 +274,8 @@ class Editor {
protected function insertNewline(): void protected function insertNewline(): void
{ {
// @TODO attempt smart indentation on newline?
if ($this->cursorX === 0) if ($this->cursorX === 0)
{ {
$this->insertRow($this->cursorY, ''); $this->insertRow($this->cursorY, '');
@ -432,6 +437,11 @@ class Editor {
$current = $lastMatch; $current = $lastMatch;
if (empty($query))
{
return;
}
for ($i = 0; $i < $this->numRows; $i++) for ($i = 0; $i < $this->numRows; $i++)
{ {
$current += $direction; $current += $direction;
@ -717,7 +727,7 @@ class Editor {
{ {
$callback($buffer, $c); $callback($buffer, $c);
} }
return ''; return ($c === KeyType::ENTER) ? $buffer : '';
} }
if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE) if ($c === KeyType::DEL_KEY || $c === KeyType::BACKSPACE)
@ -786,7 +796,7 @@ class Editor {
$row = ($this->cursorY >= $this->numRows) $row = ($this->cursorY >= $this->numRows)
? NULL ? NULL
: $this->rows[$this->cursorY]; : $this->rows[$this->cursorY];
$rowlen = $row ? $row->size : 0; $rowlen = $row->size ?? 0;
if ($this->cursorX > $rowlen) if ($this->cursorX > $rowlen)
{ {
$this->cursorX = $rowlen; $this->cursorX = $rowlen;
@ -903,7 +913,7 @@ class Editor {
protected function refreshSyntax(): void protected function refreshSyntax(): void
{ {
// Update the syntax highlighting for all the rows of the file // Update the syntax highlighting for all the rows of the file
array_walk($this->rows, fn (Row $row) => $row->updateSyntax()); array_walk($this->rows, static fn (Row $row) => $row->updateSyntax());
} }
private function refreshPHPSyntax(): void private function refreshPHPSyntax(): void

View File

@ -495,7 +495,7 @@ class Row {
break; break;
} }
$char = $token['char']; $char = $token['char'] ?? '';
$charLen = strlen($char); $charLen = strlen($char);
if ($charLen === 0 || $offset >= $this->rsize) if ($charLen === 0 || $offset >= $this->rsize)
{ {

View File

@ -7,156 +7,166 @@ use function Aviat\Kilo\tabs_to_spaces;
class PHP { class PHP {
private string $code; private string $code;
private array $rawLines; private array $rawLines;
private array $tokens; private array $tokens;
private int $lineNum = 1; private int $lineNum = 1;
private function __construct(string $code) private function __construct(string $code)
{
$lines = explode("\n", $code);
array_unshift($lines, '');
unset($lines[0]);
$this->code = $code;
$this->rawLines = $lines;
$this->tokens = array_fill(1, count($lines), []);
}
/**
* Use 'token_get_all' to get the tokens for a file,
* organized by row number
*
* @param string $code
* @return array
*/
public static function getTokens(string $code): array
{
return (new self($code))->organizeTokens();
}
/**
* Return tokens for the current $filename, organized
* by row number
*
* @param string $filename
* @return array
*/
public static function getFileTokens(string $filename): array
{
$code = @file_get_contents($filename);
if ($code === FALSE)
{ {
return []; $lines = explode("\n", $code);
array_unshift($lines, '');
unset($lines[0]);
$this->code = $code;
$this->rawLines = $lines;
$this->tokens = array_fill(1, count($lines), []);
} }
return self::getTokens($code); /**
} * Use 'token_get_all' to get the tokens for a file,
* organized by row number
protected function organizeTokens(): array *
{ * @param string $code
$rawTokens = token_get_all($this->code); * @return array
foreach ($rawTokens as $t) */
public static function getTokens(string $code): array
{ {
if (is_array($t)) if (class_exists('PhpToken'))
{ {
$this->processArrayToken($t); return \PhpToken::tokenize($code);
} }
else if (is_string($t))
return (new self($code))->organizeTokens();
}
/**
* Return tokens for the current $filename, organized
* by row number
*
* @param string $filename
* @return array
*/
public static function getFileTokens(string $filename): array
{
$code = @file_get_contents($filename);
if ($code === FALSE)
{ {
$this->processStringToken($t); return [];
} }
return self::getTokens($code);
} }
ksort($this->tokens); protected function organizeTokens(): array
return $this->tokens;
}
protected function processArrayToken(array $token): void
{
[$type, $rawChar, $currentLine] = $token;
$char = tabs_to_spaces($rawChar);
$this->lineNum = $currentLine;
$current = [
'type' => $type,
'typeName' => token_name($type),
'char' => $char,
'line' => $currentLine,
];
// Single new line, or starts with a new line with other whitespace
if (strpos($char, "\n") === 0 && trim($char) === '')
{ {
$this->tokens[$this->lineNum][] = $current; $rawTokens = token_get_all($this->code);
$this->lineNum++; foreach ($rawTokens as $t)
return;
}
// Only return the first line of a multi-line token for this line array
if (str_contains($char, "\n"))
{
$chars = explode("\n", $char);
$current['original'] = [
'string' => $char,
'lines' => $chars,
];
$current['char'] = array_shift($chars);
// Add new lines for additional newline characters
$nextLine = $currentLine;
foreach ($chars as $char)
{ {
$nextLine++; if (is_array($t))
if ( ! empty($char))
{ {
$this->processStringToken($char, $nextLine); $this->processArrayToken($t);
}
else if (is_string($t))
{
$this->processStringToken($t);
} }
} }
ksort($this->tokens);
return $this->tokens;
} }
$this->tokens[$this->lineNum][] = $current; protected function processObjectToken(\PhpToken $token)
}
protected function processStringToken(string $token, ?int $startLine = NULL): void
{
$char = tabs_to_spaces($token);
$startLine = $startLine ?? $this->lineNum;
$lineNumber = $this->findCorrectLine($char, $startLine) ?? $startLine;
// Simple characters, usually delimiters or single character operators
$this->tokens[$lineNumber][] = [
'type' => -1,
'typeName' => 'RAW',
'char' => tabs_to_spaces($token),
];
}
private function findCorrectLine(string $search, int $rowOffset, int $searchLength = 5): ?int
{
$end = $rowOffset + $searchLength;
if ($end > count($this->rawLines))
{ {
$end = count($this->rawLines);
} }
for ($i = $rowOffset; $i < $end; $i++) protected function processArrayToken(array $token): void
{ {
if (str_contains($this->rawLines[$i], $search)) [$type, $rawChar, $currentLine] = $token;
$char = tabs_to_spaces($rawChar);
$this->lineNum = $currentLine;
$current = [
'type' => $type,
'typeName' => token_name($type),
'char' => $char,
'line' => $currentLine,
];
// Single new line, or starts with a new line with other whitespace
if (strpos($char, "\n") === 0 && trim($char) === '')
{ {
return $i; $this->tokens[$this->lineNum][] = $current;
$this->lineNum++;
return;
} }
// Only return the first line of a multi-line token for this line array
if (str_contains($char, "\n"))
{
$chars = explode("\n", $char);
$current['original'] = [
'string' => $char,
'lines' => $chars,
];
$current['char'] = array_shift($chars);
// Add new lines for additional newline characters
$nextLine = $currentLine;
foreach ($chars as $char)
{
$nextLine++;
if ( ! empty($char))
{
$this->processStringToken($char, $nextLine);
}
}
}
$this->tokens[$this->lineNum][] = $current;
} }
protected function processStringToken(string $token, ?int $startLine = NULL): void
{
$char = tabs_to_spaces($token);
$startLine = $startLine ?? $this->lineNum;
$lineNumber = $this->findCorrectLine($char, $startLine) ?? $startLine;
// Simple characters, usually delimiters or single character operators
$this->tokens[$lineNumber][] = [
'type' => -1,
'typeName' => 'RAW',
'char' => tabs_to_spaces($token),
];
}
private function findCorrectLine(string $search, int $rowOffset, int $searchLength = 5): ?int
{
$end = $rowOffset + $searchLength;
if ($end > count($this->rawLines))
{
$end = count($this->rawLines);
}
for ($i = $rowOffset; $i < $end; $i++)
{
if (str_contains($this->rawLines[$i], $search))
{
return $i;
}
}
return NULL; return NULL;
} }
} }

View File

@ -283,7 +283,7 @@ function syntax_to_color(int $hl): int
* Replace tabs with the specified number of spaces. * Replace tabs with the specified number of spaces.
* *
* @param string $str * @param string $str
* @param int? $number * @param int|null $number
* @return string * @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

View File

@ -8,9 +8,10 @@ abstract class Foo implements Ifoo {
* @param float $b * @param float $b
* @param array $c * @param array $c
* @param callable $d * @param callable $d
* @param string $e
* @return string * @return string
*/ */
abstract public function bar(int $a, float $b, array $c, callable $d): string; abstract public function bar(int $a, float $b, array $c, callable $d, string $e): string;
protected function doNothing(): void {} protected function doNothing(): void {}
} }
@ -41,6 +42,8 @@ class FooBar extends Foo implements Ifoo {
} }
} }
$square = fn (int $x) => $x ** 2;
/* /*
* Multi-line comment * Multi-line comment
*/ */
@ -76,7 +79,11 @@ $template = <<<'TEMPLATE'
TEMPLATE; TEMPLATE;
?> ?>
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<title>HTML</title>
</head>
<body> <body>
<h1><?= $_SERVER['HTTP_HOST'] ?></h1> <h1><?= $_SERVER['HTTP_HOST'] ?></h1>
</body> </body>

View File

@ -18,7 +18,7 @@ class KeyCodeTest extends TestCase {
$actual = KeyCode::CTRL($char); $actual = KeyCode::CTRL($char);
$this->assertEquals(ctrl_key($char), $ord, "chr(ctrl_key) !== CTRL"); $this->assertEquals(ctrl_key($char), $ord, 'chr(ctrl_key) !== CTRL');
$this->assertEquals($expected, $actual, "CTRL+'{$char}' should return chr($ord)"); $this->assertEquals($expected, $actual, "CTRL+'{$char}' should return chr($ord)");
} }
} }

File diff suppressed because it is too large Load Diff