Replace inline ANSI escapes with constants and static methods
Gitea - Tutorials/php-kilo/master There was a failure building this commit Details

This commit is contained in:
Timothy Warren 2019-12-04 10:54:15 -05:00
parent 7acf38da9c
commit fb5dd66bee
8 changed files with 6281 additions and 4062 deletions

97
src/ANSI.php Normal file
View 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);
}
}

View File

@ -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;

View File

@ -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
} }
} }
} }

View File

@ -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));

View File

@ -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>
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------

View File

@ -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);
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff