Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 16
Terminal
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 3
306
0.00% covered (danger)
0.00%
0 / 16
 size
n/a
0 / 0
1
n/a
0 / 0
 getWindowSize
n/a
0 / 0
5
n/a
0 / 0
 clear
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 read
n/a
0 / 0
3
n/a
0 / 0
 readKey
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 14
 ding
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 1
 write
n/a
0 / 0
3
n/a
0 / 0
 has_tput
n/a
0 / 0
2
n/a
0 / 0
1<?php declare(strict_types=1);
2
3namespace Aviat\Kilo;
4
5use Aviat\Kilo\Enum\RawKeyCode;
6use Aviat\Kilo\Enum\KeyType;
7use Aviat\Kilo\Type\TerminalSize;
8
9class Terminal {
10    /**
11     * Get the size of the current terminal window
12     *
13     * @codeCoverageIgnore
14     * @return TerminalSize
15     */
16    public static function size(): TerminalSize
17    {
18        return new TerminalSize(...self::getWindowSize());
19    }
20
21    /**
22     * Get the size of the current terminal window
23     *
24     * @codeCoverageIgnore
25     * @return array
26     */
27    public static function getWindowSize(): array
28    {
29        $ffiSize = Termios::getWindowSize();
30        if ($ffiSize !== NULL)
31        {
32            return $ffiSize;
33        }
34
35        // Try using tput
36        if (self::has_tput())
37        {
38            $rows = (int)trim((string)shell_exec('tput lines'));
39            $cols = (int)trim((string)shell_exec('tput cols'));
40
41            if ($rows > 0 && $cols > 0)
42            {
43                return [$rows, $cols];
44            }
45        }
46
47        // Worst-case, return an arbitrary 'standard' size
48        return [25, 80];
49    }
50
51    /**
52     * Clear the screen and reset the cursor position
53     */
54    public static function clear(): void
55    {
56        self::write(ANSI::CLEAR_SCREEN . ANSI::RESET_CURSOR);
57    }
58
59    /**
60     * Pull input from the stdin stream.
61     *
62     * @codeCoverageIgnore
63     * @param int $len
64     * @return string
65     */
66    public static function read(int $len = 128): string
67    {
68        $handle = fopen('php://stdin', 'rb');
69        if ($handle === false)
70        {
71            return '';
72        }
73
74        $input = fread($handle, $len);
75        fclose($handle);
76
77        return (is_string($input)) ? $input : '';
78    }
79
80    /**
81     * Get the last key input from the terminal and convert to a
82     * more useful format
83     *
84     * @return string
85     */
86    public static function readKey(): string
87    {
88        $c = Terminal::read();
89
90        return match($c)
91        {
92            // Unambiguous mappings
93            RawKeyCode::ARROW_DOWN => KeyType::ARROW_DOWN,
94            RawKeyCode::ARROW_LEFT => KeyType::ARROW_LEFT,
95            RawKeyCode::ARROW_RIGHT => KeyType::ARROW_RIGHT,
96            RawKeyCode::ARROW_UP => KeyType::ARROW_UP,
97            RawKeyCode::DELETE => KeyType::DELETE,
98            RawKeyCode::ENTER => KeyType::ENTER,
99            RawKeyCode::PAGE_DOWN => KeyType::PAGE_DOWN,
100            RawKeyCode::PAGE_UP => KeyType::PAGE_UP,
101
102            // Backspace
103            RawKeyCode::CTRL('h'), RawKeyCode::BACKSPACE => KeyType::BACKSPACE,
104
105            // Escape
106            RawKeyCode::CTRL('l'), RawKeyCode::ESCAPE => KeyType::ESCAPE,
107
108            // Home Key
109            "\eOH", "\e[7~", "\e[1~", ANSI::RESET_CURSOR => KeyType::HOME,
110
111            // End Key
112            "\eOF", "\e[4~", "\e[8~", "\e[F" => KeyType::END,
113
114            default => $c,
115        };
116    }
117
118    /**
119     * Ring the terminal bell
120     */
121    public static function ding(): void
122    {
123        self::write(RawKeyCode::BELL);
124    }
125
126    /**
127     * Write to the stdout stream
128     *
129     * @codeCoverageIgnore
130     * @param string $str
131     * @param int|NULL $len
132     * @return int|false
133     */
134    public static function write(string $str, int $len = NULL): int|false
135    {
136        $handle = fopen('php://stdout', 'ab');
137        if ($handle === false)
138        {
139            return false;
140        }
141
142        $res = (is_int($len))
143            ? fwrite($handle, $str, $len)
144            : fwrite($handle, $str);
145
146        fflush($handle);
147
148        fclose($handle);
149
150        return $res;
151    }
152
153    /**
154     * See if tput exists for fallback terminal size detection
155     *
156     * @return bool
157     * @codeCoverageIgnore
158     */
159    private static function has_tput(): bool
160    {
161        $cmd = shell_exec('type tput');
162        if ( ! is_string($cmd))
163        {
164            return FALSE;
165        }
166
167        return str_contains($cmd, ' is ');
168    }
169}