From e6f7095a454489e0f4cd5dece6541d496c99bc77 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Fri, 5 Mar 2021 12:06:23 -0500 Subject: [PATCH] Move terminal handling to its own class --- src/Editor.php | 14 +++-- src/Terminal.php | 105 ++++++++++++++++++++++++++++++++++ src/Termios.php | 52 +++++++++++++++-- src/functions.php | 124 +---------------------------------------- tests/FunctionTest.php | 8 +-- tests/TerminalTest.php | 17 ++++++ 6 files changed, 178 insertions(+), 142 deletions(-) create mode 100644 src/Terminal.php create mode 100644 tests/TerminalTest.php diff --git a/src/Editor.php b/src/Editor.php index b06740b..ebef264 100644 --- a/src/Editor.php +++ b/src/Editor.php @@ -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; diff --git a/src/Terminal.php b/src/Terminal.php new file mode 100644 index 0000000..940fd27 --- /dev/null +++ b/src/Terminal.php @@ -0,0 +1,105 @@ + 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 '); + } +} diff --git a/src/Termios.php b/src/Termios.php index 76159f8..bca7e69 100644 --- a/src/Termios.php +++ b/src/Termios.php @@ -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; + } } \ No newline at end of file diff --git a/src/functions.php b/src/functions.php index bb15ce3..bf0f080 100644 --- a/src/functions.php +++ b/src/functions.php @@ -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 * diff --git a/tests/FunctionTest.php b/tests/FunctionTest.php index f7afa26..06f9246 100644 --- a/tests/FunctionTest.php +++ b/tests/FunctionTest.php @@ -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 { diff --git a/tests/TerminalTest.php b/tests/TerminalTest.php new file mode 100644 index 0000000..b97cc4e --- /dev/null +++ b/tests/TerminalTest.php @@ -0,0 +1,17 @@ +assertGreaterThan(0, $rows); + $this->assertGreaterThan(0, $cols); + } +} \ No newline at end of file