Completed
Push — master ( df8184...e3b994 )
by Aydin
02:31
created

src/Input/InputIO.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace PhpSchool\CliMenu\Input;
4
5
use PhpSchool\CliMenu\CliMenu;
6
use PhpSchool\CliMenu\Util\StringUtil;
7
use PhpSchool\Terminal\InputCharacter;
8
use PhpSchool\Terminal\NonCanonicalReader;
9
use PhpSchool\Terminal\Terminal;
10
11
/**
12
 * @author Aydin Hassan <[email protected]>
13
 */
14
class InputIO
15
{
16
    /**
17
     * @var CliMenu
18
     */
19
    private $parentMenu;
20
21
    /**
22
     * @var Terminal
23
     */
24
    private $terminal;
25
26
    /**
27
     * @var callable[][]
28
     */
29
    private $callbacks = [];
30
31
    public function __construct(CliMenu $parentMenu, Terminal $terminal)
32
    {
33
        $this->terminal     = $terminal;
34
        $this->parentMenu   = $parentMenu;
35
    }
36
37
    public function collect(Input $input) : InputResult
38
    {
39
        $this->drawInput($input, $input->getPlaceholderText());
40
41
        $inputValue = $input->getPlaceholderText();
42
        $havePlaceHolderValue = !empty($inputValue);
43
44
        $reader = new NonCanonicalReader($this->terminal);
45
46
        while ($char = $reader->readCharacter()) {
47
            if ($char->isNotControl()) {
48
                if ($havePlaceHolderValue) {
49
                    $inputValue = $char->get();
50
                    $havePlaceHolderValue = false;
51
                } else {
52
                    $inputValue .= $char->get();
53
                }
54
55
                $this->parentMenu->redraw();
56
                $this->drawInput($input, $inputValue);
57
                continue;
58
            }
59
60
            if ($char->isHandledControl()) {
61
                switch ($char->getControl()) {
62
                    case InputCharacter::ENTER:
63
                        if ($input->validate($inputValue)) {
64
                            $this->parentMenu->redraw();
65
                            return new InputResult($inputValue);
66
                        } else {
67
                            $this->drawInputWithError($input, $inputValue);
68
                            continue 2;
69
                        }
70
71
                    case InputCharacter::BACKSPACE:
72
                        $inputValue = substr($inputValue, 0, -1);
73
                        $this->parentMenu->redraw();
74
                        $this->drawInput($input, $inputValue);
75
                        continue 2;
76
                }
77
78
                if (!empty($this->callbacks[$char->getControl()])) {
79
                    foreach ($this->callbacks[$char->getControl()] as $callback) {
80
                        $inputValue = $callback($inputValue);
81
                        $this->drawInput($input, $inputValue);
82
                    }
83
                }
84
            }
85
        }
86
    }
87
88
    public function registerControlCallback(string $control, callable $callback) : void
89
    {
90
        if (!isset($this->callbacks[$control])) {
91
            $this->callbacks[$control] = [];
92
        }
93
94
        $this->callbacks[$control][] = $callback;
95
    }
96
97
    private function getInputWidth(array $lines)
98
    {
99
        return max(
100
            array_map(
101
                function (string $line) {
102
                    return mb_strlen($line);
103
                },
104
                $lines
105
            )
106
        );
107
    }
108
109
    private function calculateYPosition() : int
110
    {
111
        $lines = 5; //1. empty 2. prompt text 3. empty 4. input 5. empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
39% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
112
113
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
114
    }
115
116
    private function calculateYPositionWithError() : int
117
    {
118
        $lines = 7; //1. empty 2. prompt text 3. empty 4. input 5. empty 6. error 7. empty
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
119
120
        return ceil($this->parentMenu->getCurrentFrame()->count() / 2) - ceil($lines /2) + 1;
121
    }
122
123
    private function calculateXPosition(Input $input, string $userInput) : int
124
    {
125
        $width = $this->getInputWidth(
126
            [
127
                $input->getPromptText(),
128
                $input->getValidationFailedText(),
129
                $userInput
130
            ]
131
        );
132
133
        $parentStyle     = $this->parentMenu->getStyle();
134
        $halfWidth       = ($width + ($input->getStyle()->getPaddingLeftRight() * 2)) / 2;
135
        $parentHalfWidth = ceil($parentStyle->getWidth() / 2 + $parentStyle->getMargin());
136
137
        return $parentHalfWidth - $halfWidth;
138
    }
139
140
    private function drawLine(Input $input, string $userInput, string $text) : void
141
    {
142
        $this->terminal->moveCursorToColumn($this->calculateXPosition($input, $userInput));
143
144
        $line = sprintf(
145
            "%s%s%s%s%s\n",
146
            $input->getStyle()->getColoursSetCode(),
147
            str_repeat(' ', $input->getStyle()->getPaddingLeftRight()),
148
            $text,
149
            str_repeat(' ', $input->getStyle()->getPaddingLeftRight()),
150
            $input->getStyle()->getColoursResetCode()
151
        );
152
153
        $this->terminal->write($line);
154
    }
155
156
    private function drawCenteredLine(Input $input, string $userInput, string $text) : void
157
    {
158
        $width = $this->getInputWidth(
159
            [
160
                $input->getPromptText(),
161
                $input->getValidationFailedText(),
162
                $userInput
163
            ]
164
        );
165
166
        $textLength = mb_strlen(StringUtil::stripAnsiEscapeSequence($text));
167
        $leftFill   = ($width / 2) - ($textLength / 2);
168
        $rightFill  = ceil($width - $leftFill - $textLength);
169
170
        $this->drawLine(
171
            $input,
172
            $userInput,
173
            sprintf(
174
                '%s%s%s',
175
                str_repeat(' ', $leftFill),
176
                $text,
177
                str_repeat(' ', $rightFill)
178
            )
179
        );
180
    }
181
182
    private function drawEmptyLine(Input $input, string $userInput) : void
183
    {
184
        $width = $this->getInputWidth(
185
            [
186
                $input->getPromptText(),
187
                $input->getValidationFailedText(),
188
                $userInput
189
            ]
190
        );
191
192
        $this->drawLine(
193
            $input,
194
            $userInput,
195
            str_repeat(' ', $width)
196
        );
197
    }
198
199
    private function drawInput(Input $input, string $userInput) : void
200
    {
201
        $this->terminal->moveCursorToRow($this->calculateYPosition());
202
203
        $this->drawEmptyLine($input, $userInput);
204
        $this->drawTitle($input, $userInput);
205
        $this->drawEmptyLine($input, $userInput);
206
        $this->drawInputField($input, $input->filter($userInput));
207
        $this->drawEmptyLine($input, $userInput);
208
    }
209
210
    private function drawInputWithError(Input $input, string $userInput) : void
211
    {
212
        $this->terminal->moveCursorToRow($this->calculateYPositionWithError());
213
214
        $this->drawEmptyLine($input, $userInput);
215
        $this->drawTitle($input, $userInput);
216
        $this->drawEmptyLine($input, $userInput);
217
        $this->drawInputField($input, $input->filter($userInput));
218
        $this->drawEmptyLine($input, $userInput);
219
        $this->drawCenteredLine(
220
            $input,
221
            $userInput,
222
            $input->getValidationFailedText()
223
        );
224
        $this->drawEmptyLine($input, $userInput);
225
    }
226
227
    private function drawTitle(Input $input, string $userInput) : void
228
    {
229
230
        $this->drawCenteredLine(
231
            $input,
232
            $userInput,
233
            $input->getPromptText()
234
        );
235
    }
236
237
    private function drawInputField(Input $input, string $userInput) : void
238
    {
239
        $this->drawCenteredLine(
240
            $input,
241
            $userInput,
242
            sprintf(
243
                '%s%s%s',
244
                $input->getStyle()->getInvertedColoursSetCode(),
245
                $userInput,
246
                $input->getStyle()->getInvertedColoursUnsetCode()
247
            )
248
        );
249
    }
250
}
251