Move terminal handling to its own class
timw4mail/php-kilo/pipeline/head This commit looks good Details

This commit is contained in:
Timothy Warren 2021-03-05 12:06:23 -05:00
parent 825966ac54
commit e6f7095a45
6 changed files with 178 additions and 142 deletions

View File

@ -46,7 +46,7 @@ class Editor {
{
$this->statusMsgTime = time();
[$this->screenRows, $this->screenCols] = get_window_size();
[$this->screenRows, $this->screenCols] = Terminal::getWindowSize();
// Remove a row for the status bar, and one for the message bar
$this->screenRows -= 2;
@ -86,7 +86,7 @@ class Editor {
// ------------------------------------------------------------------------
protected function readKey(): string
{
$c = read_stdin();
$c = Terminal::read();
return match($c)
{
@ -673,6 +673,8 @@ class Editor {
public function refreshScreen(): void
{
Terminal::clear();
$this->scroll();
$this->outputBuffer = '';
@ -692,7 +694,7 @@ class Editor {
$this->outputBuffer .= ANSI::SHOW_CURSOR;
write_stdout($this->outputBuffer, strlen($this->outputBuffer));
Terminal::write($this->outputBuffer, strlen($this->outputBuffer));
}
public function setStatusMessage(string $fmt, mixed ...$args): void
@ -758,7 +760,7 @@ class Editor {
{
$this->cursorX--;
}
else if ($this->cursorX > 0)
else if ($this->cursorY > 0)
{
$this->cursorY--;
$this->cursorX = $this->rows[$this->cursorY]->size;
@ -827,8 +829,8 @@ class Editor {
$quit_times--;
return '';
}
write_stdout(ANSI::CLEAR_SCREEN);
write_stdout(ANSI::RESET_CURSOR);
Terminal::clear();
return NULL;
break;

105
src/Terminal.php Normal file
View File

@ -0,0 +1,105 @@
<?php declare(strict_types=1);
namespace Aviat\Kilo;
class Terminal {
/**
* Get the size of the current terminal window
*
* @codeCoverageIgnore
* @return array
*/
public static function getWindowSize(): array
{
$ffiSize = Termios::getWindowSize();
if ($ffiSize !== NULL)
{
return $ffiSize;
}
// Try using tput
if (self::has_tput())
{
$rows = (int)trim((string)shell_exec('tput lines'));
$cols = (int)trim((string)shell_exec('tput cols'));
if ($rows > 0 && $cols > 0)
{
return [$rows, $cols];
}
}
// Worst-case, return an arbitrary 'standard' size
return [25, 80];
}
public static function clear(): void
{
self::write(ANSI::CLEAR_SCREEN . ANSI::RESET_CURSOR);
}
/**
* Pull input from the stdin stream.
*
* @codeCoverageIgnore
* @param int $len
* @return string
*/
public static function read(int $len = 128): string
{
$handle = fopen('php://stdin', 'rb');
if ($handle === false)
{
return '';
}
$input = fread($handle, $len);
fclose($handle);
return (is_string($input)) ? $input : '';
}
/**
* Write to the stdout stream
*
* @codeCoverageIgnore
* @param string $str
* @param int|NULL $len
* @return int|false
*/
public static function write(string $str, int $len = NULL): int|false
{
$handle = fopen('php://stdout', 'ab');
if ($handle === false)
{
return false;
}
$res = (is_int($len))
? fwrite($handle, $str, $len)
: fwrite($handle, $str);
fflush($handle);
fclose($handle);
return $res;
}
/**
* See if tput exists for fallback terminal size detection
*
* @return bool
* @codeCoverageIgnore
*/
private static function has_tput(): bool
{
$cmd = shell_exec('type tput');
if ( ! is_string($cmd))
{
return FALSE;
}
return str_contains($cmd, ' is ');
}
}

View File

@ -15,7 +15,7 @@ class Termios {
private function __construct()
{
$ffi = get_ffi();
$ffi = self::ffi();
$termios = $ffi->new('struct termios');
if ($termios === NULL)
{
@ -67,7 +67,8 @@ class Termios {
$termios->c_cc[C::VTIME] = 1;
// Turn on raw mode
$res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios));
$res = self::ffi()
->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($termios));
return $res !== -1;
}
@ -85,15 +86,37 @@ class Termios {
$instance = self::getInstance();
// Cleanup
write_stdout(ANSI::CLEAR_SCREEN);
write_stdout(ANSI::RESET_CURSOR);
write_stdout("\n"); // New line, please
Terminal::clear();
Terminal::write("\n"); // New line, please
$res = get_ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios));
$res = self::ffi()->tcsetattr(C::STDIN_FILENO, C::TCSAFLUSH, FFI::addr($instance->originalTermios));
return $res !== -1;
}
/**
* Get the size of the current terminal window
*
* @codeCoverageIgnore
* @return array|null
*/
public static function getWindowSize(): ?array
{
// First, try to get the answer from ioctl
$ffi = self::ffi();
$ws = $ffi->new('struct winsize');
if ($ws !== NULL)
{
$res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0)
{
return [$ws->ws_row, $ws->ws_col];
}
}
return null;
}
private static function getInstance(): self
{
static $instance;
@ -105,4 +128,21 @@ class Termios {
return $instance;
}
/**
* A 'singleton' function to replace a global variable
*
* @return FFI
*/
private static function ffi(): FFI
{
static $ffi;
if ($ffi === NULL)
{
$ffi = FFI::load(__DIR__ . '/ffi.h');
}
return $ffi;
}
}

View File

@ -2,66 +2,7 @@
namespace Aviat\Kilo;
use FFI;
use Aviat\Kilo\Enum\{C, Color, Highlight, KeyCode};
/**
* See if tput exists for fallback terminal size detection
*
* @return bool
* @codeCoverageIgnore
*/
function has_tput(): bool
{
$cmd = shell_exec('type tput');
if ( ! is_string($cmd))
{
return FALSE;
}
return str_contains($cmd, ' is ');
}
// ----------------------------------------------------------------------------
// ! Terminal size
// ----------------------------------------------------------------------------
/**
* Get the size of the current terminal window
*
* @codeCoverageIgnore
* @return array
*/
function get_window_size(): array
{
// First, try to get the answer from ioctl
$ffi = get_ffi();
$ws = $ffi->new('struct winsize');
if ($ws !== NULL)
{
$res = $ffi->ioctl(C::STDOUT_FILENO, C::TIOCGWINSZ, FFI::addr($ws));
if ($res === 0 && $ws->ws_col !== 0 && $ws->ws_row !== 0)
{
return [$ws->ws_row, $ws->ws_col];
}
}
// Try using tput
if (has_tput())
{
$rows = (int)trim(shell_exec('tput lines'));
$cols = (int)trim(shell_exec('tput cols'));
if ($rows > 0 && $cols > 0)
{
return [$rows, $cols];
}
}
// Worst-case, return an arbitrary 'standard' size
return [25, 80];
}
use Aviat\Kilo\Enum\{Color, Highlight, KeyCode};
// ----------------------------------------------------------------------------
// ! C function/macro equivalents
@ -151,23 +92,6 @@ function is_space(string $char): bool
// ! Helper functions
// ----------------------------------------------------------------------------
/**
* A 'singleton' function to replace a global variable
*
* @return FFI
*/
function get_ffi(): FFI
{
static $ffi;
if ($ffi === NULL)
{
$ffi = FFI::load(__DIR__ . '/ffi.h');
}
return $ffi;
}
/**
* Does the one-character string contain a character that separates tokens?
*
@ -186,52 +110,6 @@ function is_separator(string $char): bool
return is_space($char) || $char === KeyCode::NULL || $isSep;
}
/**
* Pull input from the stdin stream.
*
* @codeCoverageIgnore
* @param int $len
* @return string
*/
function read_stdin(int $len = 128): string
{
$handle = fopen('php://stdin', 'rb');
if ($handle === false)
{
return '';
}
$input = fread($handle, $len);
fclose($handle);
return (is_string($input)) ? $input : '';
}
/**
* Write to the stdout stream
*
* @codeCoverageIgnore
* @param string $str
* @param int|NULL $len
* @return int|false
*/
function write_stdout(string $str, int $len = NULL): int|false
{
$handle = fopen('php://stdout', 'ab');
if ($handle === false)
{
return false;
}
$res = (is_int($len))
? fwrite($handle, $str, $len)
: fwrite($handle, $str);
fclose($handle);
return $res;
}
/**
* Replaces a slice of an array with the same value
*

View File

@ -9,7 +9,6 @@ use PHPUnit\Framework\TestCase;
use function Aviat\Kilo\array_replace_range;
use function Aviat\Kilo\ctrl_key;
use function Aviat\Kilo\get_file_syntax_map;
use function Aviat\Kilo\get_window_size;
use function Aviat\Kilo\is_ascii;
use function Aviat\Kilo\is_ctrl;
use function Aviat\Kilo\is_digit;
@ -20,12 +19,7 @@ use function Aviat\Kilo\syntax_to_color;
use function Aviat\Kilo\tabs_to_spaces;
class FunctionTest extends TestCase {
public function test_get_window_size(): void
{
[$rows, $cols] = get_window_size();
$this->assertGreaterThan(0, $rows);
$this->assertGreaterThan(0, $cols);
}
public function test_is_ascii(): void
{

17
tests/TerminalTest.php Normal file
View File

@ -0,0 +1,17 @@
<?php declare(strict_types=1);
namespace Aviat\Kilo\Tests;
use PHPUnit\Framework\TestCase;
use Aviat\Kilo\Terminal;
class TerminalTest extends TestCase {
public function test_getWindowSize(): void
{
[$rows, $cols] = Terminal::getWindowSize();
$this->assertGreaterThan(0, $rows);
$this->assertGreaterThan(0, $cols);
}
}