This commit is contained in:
parent
5329378b93
commit
99685f3fcb
11
kilo
11
kilo
@ -6,7 +6,7 @@ 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_exception_handler(static function (\Throwable $e) {
|
set_exception_handler(static function (mixed $e) {
|
||||||
$msg = print_r([
|
$msg = print_r([
|
||||||
'code' => $e->getCode(),
|
'code' => $e->getCode(),
|
||||||
'message' => $e->getMessage(),
|
'message' => $e->getMessage(),
|
||||||
@ -31,7 +31,14 @@ return (static function (int $argc, array $argv): int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Input Loop
|
// Input Loop
|
||||||
do { $editor->refreshScreen();} while ($editor->processKeypress() !== NULL);
|
while (true)
|
||||||
|
{
|
||||||
|
$editor->refreshScreen();
|
||||||
|
if ($editor->processKeypress() === NULL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
})($argc, $argv);
|
})($argc, $argv);
|
||||||
|
12
src/ANSI.php
12
src/ANSI.php
@ -53,7 +53,7 @@ class ANSI {
|
|||||||
*/
|
*/
|
||||||
public static function color(int $color): string
|
public static function color(int $color): string
|
||||||
{
|
{
|
||||||
return self::seq('%dm', $color);
|
return self::escapeSequence('%dm', $color);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,7 +66,7 @@ class ANSI {
|
|||||||
*/
|
*/
|
||||||
public static function rgbColor(int $r, int $g, int $b): string
|
public static function rgbColor(int $r, int $g, int $b): string
|
||||||
{
|
{
|
||||||
return self::seq('38;2;%d;%d;%dm', $r, $g, $b);
|
return self::escapeSequence('38;2;%d;%d;%dm', $r, $g, $b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,7 +80,7 @@ class ANSI {
|
|||||||
{
|
{
|
||||||
// The terminal has a 1-based coordinate system,
|
// The terminal has a 1-based coordinate system,
|
||||||
// add one to each to allow 0-based coordinate system input
|
// add one to each to allow 0-based coordinate system input
|
||||||
return self::seq('%d;%dH', $line + 1, $column + 1);
|
return self::escapeSequence('%d;%dH', $line + 1, $column + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,7 +91,7 @@ class ANSI {
|
|||||||
*/
|
*/
|
||||||
public static function scrollUp(int $lines): string
|
public static function scrollUp(int $lines): string
|
||||||
{
|
{
|
||||||
return self::seq('%dS', $lines);
|
return self::escapeSequence('%dS', $lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -102,7 +102,7 @@ class ANSI {
|
|||||||
*/
|
*/
|
||||||
public static function scrollDown(int $lines): string
|
public static function scrollDown(int $lines): string
|
||||||
{
|
{
|
||||||
return self::seq('%dT', $lines);
|
return self::escapeSequence('%dT', $lines);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,7 +112,7 @@ class ANSI {
|
|||||||
* @param mixed ...$args
|
* @param mixed ...$args
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
private static function seq(string $pattern, mixed ...$args): string
|
private static function escapeSequence(string $pattern, mixed ...$args): string
|
||||||
{
|
{
|
||||||
return sprintf("\e[{$pattern}", ...$args);
|
return sprintf("\e[{$pattern}", ...$args);
|
||||||
}
|
}
|
||||||
|
@ -18,14 +18,14 @@ class Editor {
|
|||||||
private string $outputBuffer = '';
|
private string $outputBuffer = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Position The 0-based location of the cursor in the current viewport
|
* @var Point The 0-based location of the cursor in the current viewport
|
||||||
*/
|
*/
|
||||||
protected Position $cursor;
|
protected Point $cursor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var Position The scroll offset of the file in the current viewport
|
* @var Point The scroll offset of the file in the current viewport
|
||||||
*/
|
*/
|
||||||
protected Position $offset;
|
protected Point $offset;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var int The rendered cursor position
|
* @var int The rendered cursor position
|
||||||
@ -42,6 +42,11 @@ class Editor {
|
|||||||
*/
|
*/
|
||||||
protected int $screenCols = 0;
|
protected int $screenCols = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int The number of times to confirm you wish to quit
|
||||||
|
*/
|
||||||
|
protected int $quitTimes = KILO_QUIT_TIMES;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of Row objects
|
* Array of Row objects
|
||||||
*/
|
*/
|
||||||
@ -65,8 +70,8 @@ class Editor {
|
|||||||
private function __construct()
|
private function __construct()
|
||||||
{
|
{
|
||||||
$this->statusMsgTime = time();
|
$this->statusMsgTime = time();
|
||||||
$this->cursor = Position::new();
|
$this->cursor = Point::new();
|
||||||
$this->offset = Position::new();
|
$this->offset = Point::new();
|
||||||
|
|
||||||
[$this->screenRows, $this->screenCols] = Terminal::getWindowSize();
|
[$this->screenRows, $this->screenCols] = Terminal::getWindowSize();
|
||||||
|
|
||||||
@ -507,10 +512,8 @@ class Editor {
|
|||||||
|
|
||||||
protected function find(): void
|
protected function find(): void
|
||||||
{
|
{
|
||||||
$savedCx = $this->cursor->x;
|
$savedCursor = Point::from($this->cursor);
|
||||||
$savedCy = $this->cursor->y;
|
$savedOffset = Point::from($this->offset);
|
||||||
$savedColOff = $this->offset->x;
|
|
||||||
$savedRowOff = $this->offset->y;
|
|
||||||
|
|
||||||
$query = $this->prompt('Search: %s (Use ESC/Arrows/Enter)', [$this, 'findCallback']);
|
$query = $this->prompt('Search: %s (Use ESC/Arrows/Enter)', [$this, 'findCallback']);
|
||||||
|
|
||||||
@ -518,10 +521,8 @@ class Editor {
|
|||||||
// restore original cursor and scroll locations
|
// restore original cursor and scroll locations
|
||||||
if ($query === '')
|
if ($query === '')
|
||||||
{
|
{
|
||||||
$this->cursor->x = $savedCx;
|
$this->cursor = Point::from($savedCursor);
|
||||||
$this->cursor->y = $savedCy;
|
$this->offset = Point::from($savedOffset);
|
||||||
$this->offset->x = $savedColOff;
|
|
||||||
$this->offset->y = $savedRowOff;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -564,11 +565,12 @@ class Editor {
|
|||||||
{
|
{
|
||||||
$filerow = $y + $this->offset->y;
|
$filerow = $y + $this->offset->y;
|
||||||
|
|
||||||
|
$this->outputBuffer .= ANSI::CLEAR_LINE;
|
||||||
|
|
||||||
($filerow >= $this->numRows)
|
($filerow >= $this->numRows)
|
||||||
? $this->drawPlaceholderRow($y)
|
? $this->drawPlaceholderRow($y)
|
||||||
: $this->drawRow($filerow);
|
: $this->drawRow($filerow);
|
||||||
|
|
||||||
$this->outputBuffer .= ANSI::CLEAR_LINE;
|
|
||||||
$this->outputBuffer .= "\r\n";
|
$this->outputBuffer .= "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -851,7 +853,7 @@ class Editor {
|
|||||||
case KeyType::END_KEY:
|
case KeyType::END_KEY:
|
||||||
if ($y < $this->numRows)
|
if ($y < $this->numRows)
|
||||||
{
|
{
|
||||||
$x = $this->rows[$y]->size - 1;
|
$x = $this->rows[$y]->size;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -859,13 +861,13 @@ class Editor {
|
|||||||
// Do nothing
|
// Do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($x > $row->size)
|
// Snap cursor to the end of a row when moving
|
||||||
|
// from a longer row to a shorter one
|
||||||
|
$row = $this->rows[$y];
|
||||||
|
$rowLen = ($row !== NULL) ? $row->size : 0;
|
||||||
|
if ($x > $rowLen)
|
||||||
{
|
{
|
||||||
$x = $row->size;
|
$x = $rowLen;
|
||||||
}
|
|
||||||
if ($y > $this->screenRows)
|
|
||||||
{
|
|
||||||
$y = $this->screenRows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->cursor->x = $x;
|
$this->cursor->x = $x;
|
||||||
@ -874,8 +876,6 @@ class Editor {
|
|||||||
|
|
||||||
public function processKeypress(): ?string
|
public function processKeypress(): ?string
|
||||||
{
|
{
|
||||||
static $quit_times = KILO_QUIT_TIMES;
|
|
||||||
|
|
||||||
$c = $this->readKey();
|
$c = $this->readKey();
|
||||||
|
|
||||||
if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
|
if ($c === KeyCode::NULL || $c === KeyCode::EMPTY)
|
||||||
@ -885,21 +885,13 @@ class Editor {
|
|||||||
|
|
||||||
switch ($c)
|
switch ($c)
|
||||||
{
|
{
|
||||||
|
case KeyCode::CTRL('q'):
|
||||||
|
return $this->quitAttempt();
|
||||||
|
|
||||||
case KeyType::ENTER:
|
case KeyType::ENTER:
|
||||||
$this->insertNewline();
|
$this->insertNewline();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case KeyCode::CTRL('q'):
|
|
||||||
if ($this->dirty > 0 && $quit_times > 0)
|
|
||||||
{
|
|
||||||
$this->setStatusMessage('WARNING!!! File has unsaved changes.' .
|
|
||||||
'Press Ctrl-Q %d more times to quit.', $quit_times);
|
|
||||||
$quit_times--;
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
Terminal::clear();
|
|
||||||
return NULL;
|
|
||||||
|
|
||||||
case KeyCode::CTRL('s'):
|
case KeyCode::CTRL('s'):
|
||||||
$this->save();
|
$this->save();
|
||||||
break;
|
break;
|
||||||
@ -938,15 +930,32 @@ class Editor {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
$quit_times = KILO_QUIT_TIMES;
|
// Reset quit confirmation timer on different keypress
|
||||||
|
$this->quitTimes = KILO_QUIT_TIMES;
|
||||||
|
|
||||||
return $c;
|
return $c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function quitAttempt(): ?string
|
||||||
|
{
|
||||||
|
if ($this->dirty > 0 && $this->quitTimes > 0)
|
||||||
|
{
|
||||||
|
$this->setStatusMessage(
|
||||||
|
'WARNING!!! File has unsaved changes. Press Ctrl-Q %d more times to quit.',
|
||||||
|
$this->quitTimes
|
||||||
|
);
|
||||||
|
$this->quitTimes--;
|
||||||
|
return KeyCode::CTRL('q');
|
||||||
|
}
|
||||||
|
|
||||||
|
Terminal::clear();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
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, static fn (Row $row) => $row->updateSyntax());
|
array_walk($this->rows, static fn (Row $row) => $row->update());
|
||||||
}
|
}
|
||||||
|
|
||||||
private function refreshPHPSyntax(): void
|
private function refreshPHPSyntax(): void
|
||||||
|
33
src/Point.php
Normal file
33
src/Point.php
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A representation of a 2d point
|
||||||
|
*/
|
||||||
|
final class Point {
|
||||||
|
private function __construct(public int $x, public int $y) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Point from coordinates
|
||||||
|
*
|
||||||
|
* @param int $x
|
||||||
|
* @param int $y
|
||||||
|
* @return Point
|
||||||
|
*/
|
||||||
|
public static function new(int $x = 0, int $y = 0): Point
|
||||||
|
{
|
||||||
|
return new Point($x, $y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new Point from another position
|
||||||
|
*
|
||||||
|
* @param Point $pos
|
||||||
|
* @return Point
|
||||||
|
*/
|
||||||
|
public static function from(Point $pos): Point
|
||||||
|
{
|
||||||
|
return Point::new($pos->x, $pos->y);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace Aviat\Kilo;
|
|
||||||
|
|
||||||
class Position {
|
|
||||||
private function __construct(public int $x, public int $y) {}
|
|
||||||
|
|
||||||
public static function new(int $x = 0, int $y = 0): self
|
|
||||||
{
|
|
||||||
return new Position($x, $y);
|
|
||||||
}
|
|
||||||
}
|
|
6
test.php
6
test.php
@ -16,6 +16,10 @@ abstract class Foo implements Ifoo {
|
|||||||
protected function doNothing(): void {}
|
protected function doNothing(): void {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
public function __construct(public string $foo, public string $bar) {}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Docblock comment
|
* Docblock comment
|
||||||
*/
|
*/
|
||||||
@ -116,4 +120,4 @@ TEMPLATE;
|
|||||||
<h1><?= $_SERVER['HTTP_HOST'] ?></h1>
|
<h1><?= $_SERVER['HTTP_HOST'] ?></h1>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
<?php exit(); ?>
|
<?php exit(); ?>
|
||||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user