Completed
Pull Request — master (#80)
by Aydin
01:50
created

UnixTerminal::disableCursor()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace PhpSchool\CliMenu\Terminal;
4
5
use PhpSchool\CliMenu\IO\InputStream;
6
use PhpSchool\CliMenu\IO\OutputStream;
7
8
/**
9
 * @author Michael Woodward <[email protected]>
10
 */
11
class UnixTerminal implements TerminalInterface
12
{
13
    /**
14
     * @var bool
15
     */
16
    private $isTTY;
17
18
    /**
19
     * @var bool
20
     */
21
    private $isCanonical = false;
22
23
    /**
24
     * @var int
25
     */
26
    private $width;
27
28
    /**
29
     * @var int
30
     */
31
    private $height;
32
33
    /**
34
     * @var string
35
     */
36
    private $details;
37
38
    /**
39
     * @var string
40
     */
41
    private $originalConfiguration;
42
43
    /**
44
     * @var InputStream
45
     */
46
    private $input;
47
48
    /**
49
     * @var OutputStream
50
     */
51
    private $output;
52
53
    /**
54
     * Initialise the terminal from resource
55
     *
56
     */
57
    public function __construct(InputStream $input, OutputStream $output)
58
    {
59
        $this->getOriginalConfiguration();
60
        $this->input = $input;
61
        $this->output = $output;
62
    }
63
64
    /**
65
     * Get the available width of the terminal
66
     */
67
    public function getWidth() : int
68
    {
69
        return $this->width ?: $this->width = (int) exec('tput cols');
70
    }
71
72
    /**
73
     * Get the available height of the terminal
74
     */
75
    public function getHeight() : int
76
    {
77
        return $this->height ?: $this->height = (int) exec('tput lines');
78
    }
79
80
    /**
81
     * Get terminal details
82
     */
83
    public function getDetails() : string
84
    {
85
        if (!$this->details) {
86
            $this->details = function_exists('posix_ttyname')
87
                ? @posix_ttyname(STDOUT)
88
                : "Can't retrieve terminal details";
89
        }
90
91
        return $this->details;
92
    }
93
94
    /**
95
     * Get the original terminal configuration / mode
96
     */
97
    private function getOriginalConfiguration() : string
98
    {
99
        return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g');
100
    }
101
102
    /**
103
     * Toggle canonical mode on TTY
104
     */
105
    public function setCanonicalMode(bool $useCanonicalMode = true) : void
106
    {
107
        if ($useCanonicalMode) {
108
            exec('stty -icanon');
109
            $this->isCanonical = true;
110
        } else {
111
            exec('stty ' . $this->getOriginalConfiguration());
112
            $this->isCanonical = false;
113
        }
114
    }
115
116
    /**
117
     * Check if TTY is in canonical mode
118
     * Assumes the terminal was never in canonical mode
119
     */
120
    public function isCanonical() : bool
121
    {
122
        return $this->isCanonical;
123
    }
124
125
    /**
126
     * Test whether terminal is valid TTY
127
     */
128
    public function isTTY() : bool
129
    {
130
        return $this->isTTY ?: $this->isTTY = function_exists('posix_isatty') && @posix_isatty(STDOUT);
131
    }
132
133
    /**
134
     * Test whether terminal supports colour output
135
     *
136
     * @link https://github.com/symfony/Console/blob/master/Output/StreamOutput.php#L95-L102
137
     */
138
    public function supportsColour() : bool
139
    {
140
        if (DIRECTORY_SEPARATOR === '\\') {
141
            return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
142
        }
143
144
        return $this->isTTY();
145
    }
146
147
    public function getKeyedInput(array $map = []) : ?string
148
    {
149
        // TODO: Move to class var?
150
        // TODO: up, down, enter etc in Abstract CONSTs
151
        $map = [
152
            "\033[A" => 'up',
153
            "k"      => 'up',
154
            ""      => 'up', // emacs ^P
155
            "\033[B" => 'down',
156
            "j"      => 'down',
157
            ""      => 'down', //emacs ^N
158
            "\n"     => 'enter',
159
            "\r"     => 'enter',
160
            " "      => 'enter',
161
        ];
162
163
        $input = '';
164
        $this->input->read(4, function ($buffer) use (&$input) {
165
            $input .= $buffer;
166
        });
167
168
        $this->clearLine();
169
170
        return array_key_exists($input, $map)
171
            ? $map[$input]
172
            : $input;
173
    }
174
175
    /**
176
     * Clear the terminal window
177
     */
178
    public function clear() : void
179
    {
180
        $this->output->write("\033[2J");
181
    }
182
183
    /**
184
     * Enable cursor
185
     */
186
    public function enableCursor() : void
187
    {
188
        $this->output->write("\033[?25h");
189
    }
190
191
    /**
192
     * Disable cursor
193
     */
194
    public function disableCursor() : void
195
    {
196
        $this->output->write("\033[?25l");
197
    }
198
199
    /**
200
     * Move the cursor to the top left of the window
201
     *
202
     * @return void
203
     */
204
    public function moveCursorToTop() : void
205
    {
206
        $this->output->write("\033[H");
207
    }
208
209
    /**
210
     * Move the cursor to the start of a specific row
211
     */
212
    public function moveCursorToRow(int $rowNumber) : void
213
    {
214
        $this->output->write(sprintf("\033[%d;0H", $rowNumber));
215
    }
216
217
    /**
218
     * Move the cursor to the start of a specific column
219
     */
220
    public function moveCursorToColumn(int $column) : void
221
    {
222
        $this->output->write(sprintf("\033[%dC", $column));
223
    }
224
225
    /**
226
     * Clear the current cursors line
227
     */
228
    public function clearLine() : void
229
    {
230
        $this->output->write(sprintf("\033[%dD\033[K", $this->getWidth()));
231
    }
232
233
    /**
234
     * Clean the whole console without jumping the window
235
     */
236
    public function clean() : void
237
    {
238
        foreach (range(0, $this->getHeight()) as $rowNum) {
239
            $this->moveCursorToRow($rowNum);
240
            $this->clearLine();
241
        }
242
    }
243
244
    public function getOutput() : OutputStream
245
    {
246
        return $this->output;
247
    }
248
}
249