2
2
3
3
namespace PhpSchool \CliMenu ;
4
4
5
+ use PhpSchool \CliMenu \Dialogue \NumberInput ;
5
6
use PhpSchool \CliMenu \Exception \InvalidInstantiationException ;
6
7
use PhpSchool \CliMenu \Exception \InvalidTerminalException ;
7
8
use PhpSchool \CliMenu \Exception \MenuNotOpenException ;
9
+ use PhpSchool \CliMenu \Input \InputIO ;
10
+ use PhpSchool \CliMenu \Input \Number ;
11
+ use PhpSchool \CliMenu \Input \Password ;
12
+ use PhpSchool \CliMenu \Input \Text ;
8
13
use PhpSchool \CliMenu \MenuItem \LineBreakItem ;
9
14
use PhpSchool \CliMenu \MenuItem \MenuItemInterface ;
10
15
use PhpSchool \CliMenu \MenuItem \StaticItem ;
11
16
use PhpSchool \CliMenu \Dialogue \Confirm ;
12
17
use PhpSchool \CliMenu \Dialogue \Flash ;
13
18
use PhpSchool \CliMenu \Terminal \TerminalFactory ;
14
- use PhpSchool \CliMenu \Terminal \TerminalInterface ;
15
19
use PhpSchool \CliMenu \Util \StringUtil as s ;
20
+ use PhpSchool \Terminal \Exception \NotInteractiveTerminal ;
21
+ use PhpSchool \Terminal \InputCharacter ;
22
+ use PhpSchool \Terminal \NonCanonicalReader ;
23
+ use PhpSchool \Terminal \Terminal ;
24
+ use PhpSchool \Terminal \TerminalReader ;
16
25
17
26
/**
18
27
* @author Michael Woodward <[email protected] >
19
28
*/
20
29
class CliMenu
21
30
{
22
31
/**
23
- * @var TerminalInterface
32
+ * @var Terminal
24
33
*/
25
34
protected $ terminal ;
26
35
@@ -62,7 +71,7 @@ class CliMenu
62
71
public function __construct (
63
72
?string $ title ,
64
73
array $ items ,
65
- TerminalInterface $ terminal = null ,
74
+ Terminal $ terminal = null ,
66
75
MenuStyle $ style = null
67
76
) {
68
77
$ this ->title = $ title ;
@@ -75,40 +84,33 @@ public function __construct(
75
84
76
85
/**
77
86
* Configure the terminal to work with CliMenu
78
- *
79
- * @throws InvalidTerminalException
80
87
*/
81
88
protected function configureTerminal () : void
82
89
{
83
90
$ this ->assertTerminalIsValidTTY ();
84
91
85
- $ this ->terminal ->setCanonicalMode ();
92
+ $ this ->terminal ->disableCanonicalMode ();
93
+ $ this ->terminal ->disableEchoBack ();
86
94
$ this ->terminal ->disableCursor ();
87
95
$ this ->terminal ->clear ();
88
96
}
89
97
90
98
/**
91
99
* Revert changes made to the terminal
92
- *
93
- * @throws InvalidTerminalException
94
100
*/
95
101
protected function tearDownTerminal () : void
96
102
{
97
- $ this ->assertTerminalIsValidTTY ();
98
-
99
- $ this ->terminal ->setCanonicalMode (false );
100
- $ this ->terminal ->enableCursor ();
103
+ $ this ->terminal ->restoreOriginalConfiguration ();
101
104
}
102
105
103
106
private function assertTerminalIsValidTTY () : void
104
107
{
105
- if (!$ this ->terminal ->isTTY ()) {
106
- throw new InvalidTerminalException (
107
- sprintf ('Terminal "%s" is not a valid TTY ' , $ this ->terminal ->getDetails ())
108
- );
108
+ if (!$ this ->terminal ->isInteractive ()) {
109
+ throw new InvalidTerminalException ('Terminal is not interactive (TTY) ' );
109
110
}
110
111
}
111
112
113
+
112
114
public function setParent (CliMenu $ parent ) : void
113
115
{
114
116
$ this ->parent = $ parent ;
@@ -119,7 +121,7 @@ public function getParent() : ?CliMenu
119
121
return $ this ->parent ;
120
122
}
121
123
122
- public function getTerminal () : TerminalInterface
124
+ public function getTerminal () : Terminal
123
125
{
124
126
return $ this ->terminal ;
125
127
}
@@ -161,14 +163,28 @@ private function display() : void
161
163
{
162
164
$ this ->draw ();
163
165
164
- while ($ this ->isOpen () && $ input = $ this ->terminal ->getKeyedInput ()) {
165
- switch ($ input ) {
166
- case 'up ' :
167
- case 'down ' :
168
- $ this ->moveSelection ($ input );
166
+ $ reader = new NonCanonicalReader ($ this ->terminal );
167
+ $ reader ->addControlMappings ([
168
+ '^P ' => InputCharacter::UP ,
169
+ 'k ' => InputCharacter::UP ,
170
+ '^K ' => InputCharacter::DOWN ,
171
+ 'j ' => InputCharacter::DOWN ,
172
+ "\r" => InputCharacter::ENTER ,
173
+ ' ' => InputCharacter::ENTER ,
174
+ ]);
175
+
176
+ while ($ this ->isOpen () && $ char = $ reader ->readCharacter ()) {
177
+ if (!$ char ->isHandledControl ()) {
178
+ continue ;
179
+ }
180
+
181
+ switch ($ char ->getControl ()) {
182
+ case InputCharacter::UP :
183
+ case InputCharacter::DOWN :
184
+ $ this ->moveSelection ($ char ->getControl ());
169
185
$ this ->draw ();
170
186
break ;
171
- case ' enter ' :
187
+ case InputCharacter:: ENTER :
172
188
$ this ->executeCurrentItem ();
173
189
break ;
174
190
}
@@ -183,12 +199,12 @@ protected function moveSelection(string $direction) : void
183
199
do {
184
200
$ itemKeys = array_keys ($ this ->items );
185
201
186
- $ direction === 'up '
202
+ $ direction === 'UP '
187
203
? $ this ->selectedItem --
188
204
: $ this ->selectedItem ++;
189
205
190
206
if (!array_key_exists ($ this ->selectedItem , $ this ->items )) {
191
- $ this ->selectedItem = $ direction === 'up '
207
+ $ this ->selectedItem = $ direction === 'UP '
192
208
? end ($ itemKeys )
193
209
: reset ($ itemKeys );
194
210
} elseif ($ this ->getSelectedItem ()->canSelect ()) {
@@ -219,12 +235,16 @@ protected function executeCurrentItem() : void
219
235
* Redraw the menu
220
236
*/
221
237
public function redraw () : void
238
+ {
239
+ $ this ->assertOpen ();
240
+ $ this ->draw ();
241
+ }
242
+
243
+ private function assertOpen () : void
222
244
{
223
245
if (!$ this ->isOpen ()) {
224
246
throw new MenuNotOpenException ;
225
247
}
226
-
227
- $ this ->draw ();
228
248
}
229
249
230
250
/**
@@ -254,7 +274,7 @@ protected function draw() : void
254
274
$ frame ->newLine (2 );
255
275
256
276
foreach ($ frame ->getRows () as $ row ) {
257
- echo $ row ;
277
+ $ this -> terminal -> write ( $ row) ;
258
278
}
259
279
260
280
$ this ->currentFrame = $ frame ;
@@ -277,7 +297,7 @@ protected function drawMenuItem(MenuItemInterface $item, bool $selected = false)
277
297
278
298
return array_map (function ($ row ) use ($ setColour , $ unsetColour ) {
279
299
return sprintf (
280
- "%s%s%s%s%s%s%s \n\r " ,
300
+ "%s%s%s%s%s%s%s \n" ,
281
301
str_repeat (' ' , $ this ->style ->getMargin ()),
282
302
$ setColour ,
283
303
str_repeat (' ' , $ this ->style ->getPadding ()),
@@ -359,9 +379,7 @@ public function getCurrentFrame() : Frame
359
379
360
380
public function flash (string $ text ) : Flash
361
381
{
362
- if (strpos ($ text , "\n" ) !== false ) {
363
- throw new \InvalidArgumentException ;
364
- }
382
+ $ this ->guardSingleLine ($ text );
365
383
366
384
$ style = (new MenuStyle ($ this ->terminal ))
367
385
->setBg ('yellow ' )
@@ -372,14 +390,52 @@ public function flash(string $text) : Flash
372
390
373
391
public function confirm ($ text ) : Confirm
374
392
{
375
- if (strpos ($ text , "\n" ) !== false ) {
376
- throw new \InvalidArgumentException ;
377
- }
393
+ $ this ->guardSingleLine ($ text );
378
394
379
395
$ style = (new MenuStyle ($ this ->terminal ))
380
396
->setBg ('yellow ' )
381
397
->setFg ('red ' );
382
398
383
399
return new Confirm ($ this , $ style , $ this ->terminal , $ text );
384
400
}
401
+
402
+ public function askNumber () : Number
403
+ {
404
+ $ this ->assertOpen ();
405
+
406
+ $ style = (new MenuStyle ($ this ->terminal ))
407
+ ->setBg ('yellow ' )
408
+ ->setFg ('red ' );
409
+
410
+ return new Number (new InputIO ($ this , $ this ->terminal ), $ style );
411
+ }
412
+
413
+ public function askText () : Text
414
+ {
415
+ $ this ->assertOpen ();
416
+
417
+ $ style = (new MenuStyle ($ this ->terminal ))
418
+ ->setBg ('yellow ' )
419
+ ->setFg ('red ' );
420
+
421
+ return new Text (new InputIO ($ this , $ this ->terminal ), $ style );
422
+ }
423
+
424
+ public function askPassword () : Password
425
+ {
426
+ $ this ->assertOpen ();
427
+
428
+ $ style = (new MenuStyle ($ this ->terminal ))
429
+ ->setBg ('yellow ' )
430
+ ->setFg ('red ' );
431
+
432
+ return new Password (new InputIO ($ this , $ this ->terminal ), $ style );
433
+ }
434
+
435
+ private function guardSingleLine ($ text )
436
+ {
437
+ if (strpos ($ text , "\n" ) !== false ) {
438
+ throw new \InvalidArgumentException ;
439
+ }
440
+ }
385
441
}
0 commit comments