Replace inline ANSI escapes with constants and static methods
Some checks failed
Gitea - Tutorials/php-kilo/master There was a failure building this commit
Some checks failed
Gitea - Tutorials/php-kilo/master There was a failure building this commit
This commit is contained in:
parent
7acf38da9c
commit
fb5dd66bee
97
src/ANSI.php
Normal file
97
src/ANSI.php
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ANSI
|
||||||
|
*/
|
||||||
|
class ANSI {
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// General ANSI standard escape sequences
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the terminal window
|
||||||
|
*/
|
||||||
|
public const CLEAR_SCREEN = "\e[2J";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the terminal line
|
||||||
|
*/
|
||||||
|
public const CLEAR_LINE = "\e[K";
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// Text formatting escape sequences
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes text attributes, such as bold, underline, blink, inverted colors
|
||||||
|
*/
|
||||||
|
public const RESET_TEXT = "\e[0m";
|
||||||
|
|
||||||
|
public const BOLD_TEXT = "\e[1m";
|
||||||
|
|
||||||
|
public const UNDERLINE_TEXT = "\e[4m";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor to position 0,0 which is the top left
|
||||||
|
*/
|
||||||
|
public const RESET_CURSOR = "\e[H";
|
||||||
|
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
// VT-series escapes, not really ANSI standard
|
||||||
|
// ------------------------------------------------------------------------
|
||||||
|
|
||||||
|
public const HIDE_CURSOR = "\e[?25l";
|
||||||
|
public const SHOW_CURSOR = "\e[?25h";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the ascii sequence for basic text color
|
||||||
|
*
|
||||||
|
* @param int $color
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function color(int $color): string
|
||||||
|
{
|
||||||
|
return sprintf("\e[%dm", $color);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function rgbColor(int $r, int $g, int $b): string
|
||||||
|
{
|
||||||
|
return sprintf("\e[38;2;%d;%d;%bm", $r, $g, $b);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move the cursor the specified position
|
||||||
|
*
|
||||||
|
* @param int $line
|
||||||
|
* @param int $column
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function moveCursor(int $line, int $column): string
|
||||||
|
{
|
||||||
|
return sprintf("\e[%d;%dH", $line, $column);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll the specified number of lines up
|
||||||
|
*
|
||||||
|
* @param int $lines
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function scrollUp(int $lines): string
|
||||||
|
{
|
||||||
|
return sprintf("\e[%dS", $lines);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scroll the specified number of lines down
|
||||||
|
*
|
||||||
|
* @param int $lines
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function scrollDown(int $lines): string
|
||||||
|
{
|
||||||
|
return sprintf("\e[%dT", $lines);
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
namespace Aviat\Kilo;
|
namespace Aviat\Kilo;
|
||||||
|
|
||||||
use Aviat\Kilo\Enum\{
|
use Aviat\Kilo\Enum\{
|
||||||
|
Color,
|
||||||
Key,
|
Key,
|
||||||
Highlight,
|
Highlight,
|
||||||
};
|
};
|
||||||
@ -111,7 +112,7 @@ class Editor {
|
|||||||
case "\x1bOH":
|
case "\x1bOH":
|
||||||
case "\x1b[1~":
|
case "\x1b[1~":
|
||||||
case "\x1b[7~":
|
case "\x1b[7~":
|
||||||
case "\x1b[H":
|
case ANSI::RESET_CURSOR:
|
||||||
return Key::HOME_KEY;
|
return Key::HOME_KEY;
|
||||||
|
|
||||||
case "\x1bOF":
|
case "\x1bOF":
|
||||||
@ -363,8 +364,8 @@ class Editor {
|
|||||||
$handle = fopen($filename, 'rb');
|
$handle = fopen($filename, 'rb');
|
||||||
if ($handle === FALSE)
|
if ($handle === FALSE)
|
||||||
{
|
{
|
||||||
write_stdout("\x1b[2J"); // Clear the screen
|
write_stdout(ANSI::CLEAR_SCREEN);
|
||||||
write_stdout("\x1b[H"); // Reposition cursor to top-left
|
write_stdout(ANSI::RESET_CURSOR); // Reposition cursor to top-left
|
||||||
Termios::disableRawMode();
|
Termios::disableRawMode();
|
||||||
print_r(error_get_last());
|
print_r(error_get_last());
|
||||||
die();
|
die();
|
||||||
@ -604,20 +605,20 @@ class Editor {
|
|||||||
$sym = (ord($c[$i]) <= 26)
|
$sym = (ord($c[$i]) <= 26)
|
||||||
? chr(ord('@') + ord($c[$i]))
|
? chr(ord('@') + ord($c[$i]))
|
||||||
: '?';
|
: '?';
|
||||||
$this->ab .= "\x1b[7m";
|
$this->ab .= ANSI::color(Color::INVERT);
|
||||||
$this->ab .= $sym;
|
$this->ab .= $sym;
|
||||||
$this->ab .= "\x1b[m";
|
$this->ab .= ANSI::RESET_TEXT;
|
||||||
if ($currentColor !== -1)
|
if ($currentColor !== -1)
|
||||||
{
|
{
|
||||||
$this->ab .= sprintf("\x1b%dm", $currentColor);
|
$this->ab .= ANSI::color($currentColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if ($hl[$i] === Highlight::NORMAL)
|
else if ($hl[$i] === Highlight::NORMAL)
|
||||||
{
|
{
|
||||||
if ($currentColor !== -1)
|
if ($currentColor !== -1)
|
||||||
{
|
{
|
||||||
$this->ab .= "\x1b[0m"; // Reset background color
|
$this->ab .= ANSI::RESET_TEXT;
|
||||||
$this->ab .= "\x1b[39m"; // Reset foreground color
|
$this->ab .= ANSI::color(Color::FG_WHITE);
|
||||||
$currentColor = -1;
|
$currentColor = -1;
|
||||||
}
|
}
|
||||||
$this->ab .= $c[$i];
|
$this->ab .= $c[$i];
|
||||||
@ -628,25 +629,25 @@ class Editor {
|
|||||||
if ($color !== $currentColor)
|
if ($color !== $currentColor)
|
||||||
{
|
{
|
||||||
$currentColor = $color;
|
$currentColor = $color;
|
||||||
$this->ab .= "\x1b[0m"; // Reset background color
|
$this->ab .= ANSI::RESET_TEXT;
|
||||||
$this->ab .= sprintf("\x1b[%dm", $color);
|
$this->ab .= ANSI::color($color);
|
||||||
}
|
}
|
||||||
$this->ab .= $c[$i];
|
$this->ab .= $c[$i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ab .= "\x1b[0m";
|
$this->ab .= ANSI::RESET_TEXT;
|
||||||
$this->ab .= "\x1b[39m";
|
$this->ab .= ANSI::color(Color::FG_WHITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->ab .= "\x1b[K"; // Clear the current line
|
$this->ab .= ANSI::CLEAR_LINE;
|
||||||
$this->ab .= "\r\n";
|
$this->ab .= "\r\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function drawStatusBar(): void
|
protected function drawStatusBar(): void
|
||||||
{
|
{
|
||||||
$this->ab .= "\x1b[7m";
|
$this->ab .= ANSI::color(Color::INVERT);
|
||||||
|
|
||||||
$statusFilename = $this->filename !== '' ? $this->filename : '[No Name]';
|
$statusFilename = $this->filename !== '' ? $this->filename : '[No Name]';
|
||||||
$syntaxType = ($this->syntax !== NULL) ? $this->syntax->filetype : 'no ft';
|
$syntaxType = ($this->syntax !== NULL) ? $this->syntax->filetype : 'no ft';
|
||||||
@ -671,13 +672,13 @@ class Editor {
|
|||||||
$this->ab .= ' ';
|
$this->ab .= ' ';
|
||||||
$len++;
|
$len++;
|
||||||
}
|
}
|
||||||
$this->ab .= "\x1b[m";
|
$this->ab .= ANSI::RESET_TEXT;
|
||||||
$this->ab .= "\r\n";
|
$this->ab .= "\r\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function drawMessageBar(): void
|
protected function drawMessageBar(): void
|
||||||
{
|
{
|
||||||
$this->ab .= "\x1b[K";
|
$this->ab .= ANSI::CLEAR_LINE;
|
||||||
$len = strlen($this->statusMsg);
|
$len = strlen($this->statusMsg);
|
||||||
if ($len > $this->screenCols)
|
if ($len > $this->screenCols)
|
||||||
{
|
{
|
||||||
@ -696,20 +697,20 @@ class Editor {
|
|||||||
|
|
||||||
$this->ab = '';
|
$this->ab = '';
|
||||||
|
|
||||||
$this->ab .= "\x1b[?25l"; // Hide the cursor
|
$this->ab .= ANSI::HIDE_CURSOR;
|
||||||
$this->ab .= "\x1b[H"; // Reposition cursor to top-left
|
$this->ab .= ANSI::RESET_CURSOR;
|
||||||
|
|
||||||
$this->drawRows();
|
$this->drawRows();
|
||||||
$this->drawStatusBar();
|
$this->drawStatusBar();
|
||||||
$this->drawMessageBar();
|
$this->drawMessageBar();
|
||||||
|
|
||||||
// Specify the current cursor position
|
// Specify the current cursor position
|
||||||
$this->ab .= sprintf("\x1b[%d;%dH",
|
$this->ab .= ANSI::moveCursor(
|
||||||
($this->cursorY - $this->rowOffset) + 1,
|
($this->cursorY - $this->rowOffset) + 1,
|
||||||
($this->renderX - $this->colOffset) + 1
|
($this->renderX - $this->colOffset) + 1
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->ab .= "\x1b[?25h"; // Show the cursor
|
$this->ab .= ANSI::SHOW_CURSOR;
|
||||||
|
|
||||||
echo $this->ab;
|
echo $this->ab;
|
||||||
}
|
}
|
||||||
@ -855,8 +856,8 @@ class Editor {
|
|||||||
$quit_times--;
|
$quit_times--;
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
write_stdout("\x1b[2J"); // Clear the screen
|
write_stdout(ANSI::CLEAR_SCREEN);
|
||||||
write_stdout("\x1b[H"); // Reposition cursor to top-left
|
write_stdout(ANSI::RESET_CURSOR);
|
||||||
return NULL;
|
return NULL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
17
src/Row.php
17
src/Row.php
@ -319,11 +319,6 @@ class Row {
|
|||||||
$char = $this->render[$i];
|
$char = $this->render[$i];
|
||||||
$prevHl = ($i > 0) ? $this->hl[$i - 1] : Highlight::NORMAL;
|
$prevHl = ($i > 0) ? $this->hl[$i - 1] : Highlight::NORMAL;
|
||||||
|
|
||||||
if ($this->parent->syntax === NULL)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Single-line comments
|
// Single-line comments
|
||||||
if ($scsLen > 0 && $inString === '' && $inComment === FALSE
|
if ($scsLen > 0 && $inString === '' && $inComment === FALSE
|
||||||
&& substr($this->render, $i, $scsLen) === $scs)
|
&& substr($this->render, $i, $scsLen) === $scs)
|
||||||
@ -384,7 +379,7 @@ class Row {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( $char === '""' || $char === '\'')
|
if ( $char === '"' || $char === '\'')
|
||||||
{
|
{
|
||||||
$inString = $char;
|
$inString = $char;
|
||||||
$this->hl[$i] = Highlight::STRING;
|
$this->hl[$i] = Highlight::STRING;
|
||||||
@ -441,7 +436,9 @@ class Row {
|
|||||||
$this->hlOpenComment = $inComment;
|
$this->hlOpenComment = $inComment;
|
||||||
if ($changed && $this->idx + 1 < $this->parent->numRows)
|
if ($changed && $this->idx + 1 < $this->parent->numRows)
|
||||||
{
|
{
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$this->parent->rows[$this->idx + 1]->updateSyntax();
|
$this->parent->rows[$this->idx + 1]->updateSyntax();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -456,7 +453,9 @@ class Row {
|
|||||||
$this->idx < $this->parent->numRows
|
$this->idx < $this->parent->numRows
|
||||||
))
|
))
|
||||||
{
|
{
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
return;
|
return;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
$tokens = $this->parent->tokens[$rowNum];
|
$tokens = $this->parent->tokens[$rowNum];
|
||||||
@ -471,7 +470,9 @@ class Row {
|
|||||||
{
|
{
|
||||||
if ($offset >= $this->rsize)
|
if ($offset >= $this->rsize)
|
||||||
{
|
{
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
break;
|
break;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
|
|
||||||
// A multi-line comment can end in the middle of a line...
|
// A multi-line comment can end in the middle of a line...
|
||||||
@ -497,7 +498,9 @@ class Row {
|
|||||||
$charLen = strlen($char);
|
$charLen = strlen($char);
|
||||||
if ($charLen === 0 || $offset >= $this->rsize)
|
if ($charLen === 0 || $offset >= $this->rsize)
|
||||||
{
|
{
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
continue;
|
continue;
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
$charStart = strpos($this->render, $char, $offset);
|
$charStart = strpos($this->render, $char, $offset);
|
||||||
if ($charStart === FALSE)
|
if ($charStart === FALSE)
|
||||||
@ -581,7 +584,9 @@ class Row {
|
|||||||
$this->hlOpenComment = $inComment;
|
$this->hlOpenComment = $inComment;
|
||||||
if ($changed && $this->idx + 1 < $this->parent->numRows)
|
if ($changed && $this->idx + 1 < $this->parent->numRows)
|
||||||
{
|
{
|
||||||
|
// @codeCoverageIgnoreStart
|
||||||
$this->parent->rows[$this->idx + 1]->updateSyntax();
|
$this->parent->rows[$this->idx + 1]->updateSyntax();
|
||||||
|
// @codeCoverageIgnoreEnd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,9 +80,10 @@ class Termios {
|
|||||||
{
|
{
|
||||||
$instance = self::getInstance();
|
$instance = self::getInstance();
|
||||||
|
|
||||||
write_stdout("\x1b[2J"); // Clear the screen
|
// Cleanup
|
||||||
write_stdout("\x1b[H"); // Reposition cursor to top-left
|
write_stdout(ANSI::CLEAR_SCREEN);
|
||||||
echo "\n";
|
write_stdout(ANSI::RESET_CURSOR);
|
||||||
|
write_stdout("\n"); // New line, please
|
||||||
|
|
||||||
$res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios));
|
$res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios));
|
||||||
|
|
||||||
|
@ -11,6 +11,13 @@
|
|||||||
#define FFI_SCOPE "terminal"
|
#define FFI_SCOPE "terminal"
|
||||||
#define FFI_LIB "libc.so.6"
|
#define FFI_LIB "libc.so.6"
|
||||||
|
|
||||||
|
// 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>
|
//! <termios.h>
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
@ -5,6 +5,17 @@ namespace Aviat\Kilo\Tests\Traits;
|
|||||||
use Aviat\Kilo\Editor;
|
use Aviat\Kilo\Editor;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Spatie\Snapshots\MatchesSnapshots;
|
use Spatie\Snapshots\MatchesSnapshots;
|
||||||
|
use function Aviat\Kilo\get_window_size;
|
||||||
|
|
||||||
|
class MockEditor extends Editor {
|
||||||
|
public function __set(string $key, $value): void
|
||||||
|
{
|
||||||
|
if (property_exists($this, $key))
|
||||||
|
{
|
||||||
|
$this->$key = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class EditorTest extends TestCase {
|
class EditorTest extends TestCase {
|
||||||
use MatchesSnapshots;
|
use MatchesSnapshots;
|
||||||
@ -15,7 +26,10 @@ class EditorTest extends TestCase {
|
|||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
$this->editor = Editor::new();
|
// Mock the screen size, since that can vary widely
|
||||||
|
$this->editor = MockEditor::new();
|
||||||
|
$this->editor->__set('screenRows', 23);
|
||||||
|
$this->editor->__set('screenCols', 80);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSanity(): void
|
public function testSanity(): void
|
||||||
@ -26,14 +40,22 @@ class EditorTest extends TestCase {
|
|||||||
|
|
||||||
public function test__debugInfo(): void
|
public function test__debugInfo(): void
|
||||||
{
|
{
|
||||||
$state = json_encode($this->editor->__debugInfo());
|
$state = json_encode($this->editor->__debugInfo(), JSON_THROW_ON_ERROR);
|
||||||
|
$this->assertMatchesJsonSnapshot($state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOpenPHP(): void
|
||||||
|
{
|
||||||
|
$this->editor->open('test.php');
|
||||||
|
$state = json_encode($this->editor, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
$this->assertMatchesJsonSnapshot($state);
|
$this->assertMatchesJsonSnapshot($state);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testOpen(): void
|
public function testOpen(): void
|
||||||
{
|
{
|
||||||
$this->editor->open('test.php');
|
$this->editor->open('src/ffi.h');
|
||||||
$state = json_encode($this->editor);
|
$state = json_encode($this->editor, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
$this->assertMatchesJsonSnapshot($state);
|
$this->assertMatchesJsonSnapshot($state);
|
||||||
}
|
}
|
||||||
|
4397
tests/__snapshots__/EditorTest__testOpenPHP__1.json
Normal file
4397
tests/__snapshots__/EditorTest__testOpenPHP__1.json
Normal file
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