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

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