Move terminal handling to its own class
All checks were successful
timw4mail/php-kilo/pipeline/head This commit looks good
All checks were successful
timw4mail/php-kilo/pipeline/head This commit looks good
This commit is contained in:
parent
825966ac54
commit
e6f7095a45
@ -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
105
src/Terminal.php
Normal 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 ');
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
*
|
||||
|
@ -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
17
tests/TerminalTest.php
Normal 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);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user