Completed
Push — master ( a6ad7c...f98c61 )
by Aydin
45s queued 12s
created

UnixTerminal::clearDown()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
namespace PhpSchool\Terminal;
4
5
use PhpSchool\Terminal\Exception\NotInteractiveTerminal;
6
use PhpSchool\Terminal\IO\InputStream;
7
use PhpSchool\Terminal\IO\OutputStream;
8
9
/**
10
 * @author Michael Woodward <[email protected]>
11
 * @author Aydin Hassan <[email protected]>
12
 */
13
class UnixTerminal implements Terminal
14
{
15
    /**
16
     * @var bool
17
     */
18
    private $isCanonical;
19
20
    /**
21
     * Whether terminal echo back is enabled or not.
22
     * Eg. user key presses and the terminal immediately shows it.
23
     *
24
     * @var bool
25
     */
26
    private $echoBack = true;
27
28
    /**
29
     * @var int
30
     */
31
    private $width;
32
33
    /**
34
     * @var int
35
     */
36
    private $height;
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
    public function __construct(InputStream $input, OutputStream $output)
54
    {
55
        $this->getOriginalConfiguration();
56
        $this->getOriginalCanonicalMode();
57
        $this->input = $input;
58
        $this->output = $output;
59
    }
60
61
    private function getOriginalCanonicalMode() : void
62
    {
63
        exec('stty -a', $output);
64
        $this->isCanonical = (strpos(implode("\n", $output), ' icanon') !== false);
65
    }
66
67
    public function getWidth() : int
68
    {
69
        return $this->width ?: $this->width = (int) exec('tput cols');
70
    }
71
72
    public function getHeight() : int
73
    {
74
        return $this->height ?: $this->height = (int) exec('tput lines');
75
    }
76
77
    private function getOriginalConfiguration() : string
78
    {
79
        return $this->originalConfiguration ?: $this->originalConfiguration = exec('stty -g');
80
    }
81
82
    /**
83
     * Disables echoing every character back to the terminal. This means
84
     * we do not have to clear the line when reading.
85
     */
86
    public function disableEchoBack() : void
87
    {
88
        exec('stty -echo');
89
        $this->echoBack = false;
90
    }
91
92
    /**
93
     * Enable echoing back every character input to the terminal.
94
     */
95
    public function enableEchoBack() : void
96
    {
97
        exec('stty echo');
98
        $this->echoBack = true;
99
    }
100
101
    /**
102
     * Is echo back mode enabled
103
     */
104
    public function isEchoBack() : bool
105
    {
106
        return $this->echoBack;
107
    }
108
109
    /**
110
     * Disable canonical input (allow each key press for reading, rather than the whole line)
111
     *
112
     * @see https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
113
     */
114
    public function disableCanonicalMode() : void
115
    {
116
        if ($this->isCanonical) {
117
            exec('stty -icanon');
118
            $this->isCanonical = false;
119
        }
120
    }
121
122
    /**
123
     * Enable canonical input - read input by line
124
     *
125
     * @see https://www.gnu.org/software/libc/manual/html_node/Canonical-or-Not.html
126
     */
127
    public function enableCanonicalMode() : void
128
    {
129
        if (!$this->isCanonical) {
130
            exec('stty canon');
131
            $this->isCanonical = true;
132
        }
133
    }
134
135
    /**
136
     * Is canonical mode enabled or not
137
     */
138
    public function isCanonicalMode() : bool
139
    {
140
        return $this->isCanonical;
141
    }
142
143
    /**
144
     * Restore the original terminal configuration
145
     */
146
    public function restoreOriginalConfiguration() : void
147
    {
148
        exec('stty ' . $this->getOriginalConfiguration());
149
    }
150
151
    /**
152
     * Check if the Input & Output streams are interactive. Eg - they are
153
     * connected to a terminal.
154
     *
155
     * @return bool
156
     */
157
    public function isInteractive() : bool
158
    {
159
        return $this->input->isInteractive() && $this->output->isInteractive();
160
    }
161
162
    /**
163
     * Assert that both the Input & Output streams are interactive. Throw
164
     * `NotInteractiveTerminal` if not.
165
     */
166
    public function mustBeInteractive() : void
167
    {
168
        if (!$this->input->isInteractive()) {
169
            throw NotInteractiveTerminal::inputNotInteractive();
170
        }
171
172
        if (!$this->output->isInteractive()) {
173
            throw NotInteractiveTerminal::outputNotInteractive();
174
        }
175
    }
176
177
    /**
178
     * @see https://github.com/symfony/Console/blob/master/Output/StreamOutput.php#L95-L102
179
     */
180
    public function supportsColour() : bool
181
    {
182
        if (DIRECTORY_SEPARATOR === '\\') {
183
            return false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM');
184
        }
185
186
        return $this->isInteractive();
187
    }
188
189
    public function clear() : void
190
    {
191
        $this->output->write("\033[2J");
192
    }
193
194
    public function clearLine() : void
195
    {
196
        $this->output->write("\033[2K");
197
    }
198
199
    /**
200
     * Erase screen from the current line down to the bottom of the screen
201
     */
202
    public function clearDown() : void
203
    {
204
        $this->output->write("\033[J");
205
    }
206
207
    public function clean() : void
208
    {
209
        foreach (range(0, $this->getHeight()) as $rowNum) {
210
            $this->moveCursorToRow($rowNum);
211
            $this->clearLine();
212
        }
213
    }
214
215
    public function enableCursor() : void
216
    {
217
        $this->output->write("\033[?25h");
218
    }
219
220
    public function disableCursor() : void
221
    {
222
        $this->output->write("\033[?25l");
223
    }
224
225
    public function moveCursorToTop() : void
226
    {
227
        $this->output->write("\033[H");
228
    }
229
230
    public function moveCursorToRow(int $rowNumber) : void
231
    {
232
        $this->output->write(sprintf("\033[%d;0H", $rowNumber));
233
    }
234
235
    public function moveCursorToColumn(int $column) : void
236
    {
237
        $this->output->write(sprintf("\033[%dC", $column));
238
    }
239
240
    public function showSecondaryScreen() : void
241
    {
242
        $this->output->write("\033[?47h");
243
    }
244
245
    public function showPrimaryScreen() : void
246
    {
247
        $this->output->write("\033[?47l");
248
    }
249
250
    /**
251
     * Read bytes from the input stream
252
     */
253
    public function read(int $bytes): string
254
    {
255
        $buffer = '';
256
        $this->input->read($bytes, function ($data) use (&$buffer) {
257
            $buffer .= $data;
258
        });
259
        return $buffer;
260
    }
261
262
    /**
263
     * Write to the output stream
264
     */
265
    public function write(string $buffer): void
266
    {
267
        $this->output->write($buffer);
268
    }
269
270
    /**
271
     * Restore the original terminal configuration on shutdown.
272
     */
273
    public function __destruct()
274
    {
275
        $this->restoreOriginalConfiguration();
276
    }
277
}
278