Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 1
<?php
2
 
3
/**
4
 * Hoa
5
 *
6
 *
7
 * @license
8
 *
9
 * New BSD License
10
 *
11
 * Copyright © 2007-2017, Hoa community. All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *     * Redistributions of source code must retain the above copyright
16
 *       notice, this list of conditions and the following disclaimer.
17
 *     * Redistributions in binary form must reproduce the above copyright
18
 *       notice, this list of conditions and the following disclaimer in the
19
 *       documentation and/or other materials provided with the distribution.
20
 *     * Neither the name of the Hoa nor the names of its contributors may be
21
 *       used to endorse or promote products derived from this software without
22
 *       specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 */
36
 
37
namespace Psy\Readline\Hoa;
38
 
39
/**
40
 * Class \Hoa\Console\Readline.
41
 *
42
 * Read, edit, bind… a line from the input.
43
 */
44
class Readline
45
{
46
    /**
47
     * State: continue to read.
48
     */
49
    const STATE_CONTINUE = 1;
50
 
51
    /**
52
     * State: stop to read.
53
     */
54
    const STATE_BREAK = 2;
55
 
56
    /**
57
     * State: no output the current buffer.
58
     */
59
    const STATE_NO_ECHO = 4;
60
 
61
    /**
62
     * Current editing line.
63
     */
64
    protected $_line = null;
65
 
66
    /**
67
     * Current editing line seek.
68
     */
69
    protected $_lineCurrent = 0;
70
 
71
    /**
72
     * Current editing line length.
73
     */
74
    protected $_lineLength = 0;
75
 
76
    /**
77
     * Current buffer (most of the time, a char).
78
     */
79
    protected $_buffer = null;
80
 
81
    /**
82
     * Mapping.
83
     */
84
    protected $_mapping = [];
85
 
86
    /**
87
     * History.
88
     */
89
    protected $_history = [];
90
 
91
    /**
92
     * History current position.
93
     */
94
    protected $_historyCurrent = 0;
95
 
96
    /**
97
     * History size.
98
     */
99
    protected $_historySize = 0;
100
 
101
    /**
102
     * Prefix.
103
     */
104
    protected $_prefix = null;
105
 
106
    /**
107
     * Autocompleter.
108
     */
109
    protected $_autocompleter = null;
110
 
111
    /**
112
     * Initialize the readline editor.
113
     */
114
    public function __construct()
115
    {
116
        if (\defined('PHP_WINDOWS_VERSION_PLATFORM')) {
117
            return;
118
        }
119
 
120
        $this->_mapping["\033[A"] = [$this, '_bindArrowUp'];
121
        $this->_mapping["\033[B"] = [$this, '_bindArrowDown'];
122
        $this->_mapping["\033[C"] = [$this, '_bindArrowRight'];
123
        $this->_mapping["\033[D"] = [$this, '_bindArrowLeft'];
124
        $this->_mapping["\001"] = [$this, '_bindControlA'];
125
        $this->_mapping["\002"] = [$this, '_bindControlB'];
126
        $this->_mapping["\005"] = [$this, '_bindControlE'];
127
        $this->_mapping["\006"] = [$this, '_bindControlF'];
128
        $this->_mapping["\010"] =
129
        $this->_mapping["\177"] = [$this, '_bindBackspace'];
130
        $this->_mapping["\027"] = [$this, '_bindControlW'];
131
        $this->_mapping["\n"] = [$this, '_bindNewline'];
132
        $this->_mapping["\t"] = [$this, '_bindTab'];
133
 
134
        return;
135
    }
136
 
137
    /**
138
     * Read a line from the input.
139
     */
140
    public function readLine(string $prefix = null)
141
    {
142
        $input = Console::getInput();
143
 
144
        if (true === $input->eof()) {
145
            return false;
146
        }
147
 
148
        $direct = Console::isDirect($input->getStream()->getStream());
149
        $output = Console::getOutput();
150
 
151
        if (false === $direct || \defined('PHP_WINDOWS_VERSION_PLATFORM')) {
152
            $out = $input->readLine();
153
 
154
            if (false === $out) {
155
                return false;
156
            }
157
 
158
            $out = \substr($out, 0, -1);
159
 
160
            if (true === $direct) {
161
                $output->writeAll($prefix);
162
            } else {
163
                $output->writeAll($prefix.$out."\n");
164
            }
165
 
166
            return $out;
167
        }
168
 
169
        $this->resetLine();
170
        $this->setPrefix($prefix);
171
        $read = [$input->getStream()->getStream()];
172
        $write = $except = [];
173
        $output->writeAll($prefix);
174
 
175
        while (true) {
176
            @\stream_select($read, $write, $except, 30, 0);
177
 
178
            if (empty($read)) {
179
                $read = [$input->getStream()->getStream()];
180
 
181
                continue;
182
            }
183
 
184
            $char = $this->_read();
185
            $this->_buffer = $char;
186
            $return = $this->_readLine($char);
187
 
188
            if (0 === ($return & self::STATE_NO_ECHO)) {
189
                $output->writeAll($this->_buffer);
190
            }
191
 
192
            if (0 !== ($return & self::STATE_BREAK)) {
193
                break;
194
            }
195
        }
196
 
197
        return $this->getLine();
198
    }
199
 
200
    /**
201
     * Readline core.
202
     */
203
    public function _readLine(string $char)
204
    {
205
        if (isset($this->_mapping[$char]) &&
206
            \is_callable($this->_mapping[$char])) {
207
            $mapping = $this->_mapping[$char];
208
 
209
            return $mapping($this);
210
        }
211
 
212
        if (isset($this->_mapping[$char])) {
213
            $this->_buffer = $this->_mapping[$char];
214
        } elseif (false === Ustring::isCharPrintable($char)) {
215
            ConsoleCursor::bip();
216
 
217
            return static::STATE_CONTINUE | static::STATE_NO_ECHO;
218
        }
219
 
220
        if ($this->getLineLength() === $this->getLineCurrent()) {
221
            $this->appendLine($this->_buffer);
222
 
223
            return static::STATE_CONTINUE;
224
        }
225
 
226
        $this->insertLine($this->_buffer);
227
        $tail = \mb_substr(
228
            $this->getLine(),
229
            $this->getLineCurrent() - 1
230
        );
231
        $this->_buffer = "\033[K".$tail.\str_repeat(
232
            "\033[D",
233
            \mb_strlen($tail) - 1
234
        );
235
 
236
        return static::STATE_CONTINUE;
237
    }
238
 
239
    /**
240
     * Add mappings.
241
     */
242
    public function addMappings(array $mappings)
243
    {
244
        foreach ($mappings as $key => $mapping) {
245
            $this->addMapping($key, $mapping);
246
        }
247
    }
248
 
249
    /**
250
     * Add a mapping.
251
     * Supported key:
252
     *     • \e[… for \033[…;
253
     *     • \C-… for Ctrl-…;
254
     *     • abc for a simple mapping.
255
     * A mapping is a callable that has only one parameter of type
256
     * Hoa\Console\Readline and that returns a self::STATE_* constant.
257
     */
258
    public function addMapping(string $key, $mapping)
259
    {
260
        if ('\e[' === \substr($key, 0, 3)) {
261
            $this->_mapping["\033[".\substr($key, 3)] = $mapping;
262
        } elseif ('\C-' === \substr($key, 0, 3)) {
263
            $_key = \ord(\strtolower(\substr($key, 3))) - 96;
264
            $this->_mapping[\chr($_key)] = $mapping;
265
        } else {
266
            $this->_mapping[$key] = $mapping;
267
        }
268
    }
269
 
270
    /**
271
     * Add an entry in the history.
272
     */
273
    public function addHistory(string $line = null)
274
    {
275
        if (empty($line)) {
276
            return;
277
        }
278
 
279
        $this->_history[] = $line;
280
        $this->_historyCurrent = $this->_historySize++;
281
    }
282
 
283
    /**
284
     * Clear history.
285
     */
286
    public function clearHistory()
287
    {
288
        unset($this->_history);
289
        $this->_history = [];
290
        $this->_historyCurrent = 0;
291
        $this->_historySize = 1;
292
    }
293
 
294
    /**
295
     * Get an entry in the history.
296
     */
297
    public function getHistory(int $i = null)
298
    {
299
        if (null === $i) {
300
            $i = $this->_historyCurrent;
301
        }
302
 
303
        if (!isset($this->_history[$i])) {
304
            return null;
305
        }
306
 
307
        return $this->_history[$i];
308
    }
309
 
310
    /**
311
     * Go backward in the history.
312
     */
313
    public function previousHistory()
314
    {
315
        if (0 >= $this->_historyCurrent) {
316
            return $this->getHistory(0);
317
        }
318
 
319
        return $this->getHistory($this->_historyCurrent--);
320
    }
321
 
322
    /**
323
     * Go forward in the history.
324
     */
325
    public function nextHistory()
326
    {
327
        if ($this->_historyCurrent + 1 >= $this->_historySize) {
328
            return $this->getLine();
329
        }
330
 
331
        return $this->getHistory(++$this->_historyCurrent);
332
    }
333
 
334
    /**
335
     * Get current line.
336
     */
337
    public function getLine()
338
    {
339
        return $this->_line;
340
    }
341
 
342
    /**
343
     * Append to current line.
344
     */
345
    public function appendLine(string $append)
346
    {
347
        $this->_line .= $append;
348
        $this->_lineLength = \mb_strlen($this->_line);
349
        $this->_lineCurrent = $this->_lineLength;
350
    }
351
 
352
    /**
353
     * Insert into current line at the current seek.
354
     */
355
    public function insertLine(string $insert)
356
    {
357
        if ($this->_lineLength === $this->_lineCurrent) {
358
            return $this->appendLine($insert);
359
        }
360
 
361
        $this->_line = \mb_substr($this->_line, 0, $this->_lineCurrent).
362
                               $insert.
363
                               \mb_substr($this->_line, $this->_lineCurrent);
364
        $this->_lineLength = \mb_strlen($this->_line);
365
        $this->_lineCurrent += \mb_strlen($insert);
366
 
367
        return;
368
    }
369
 
370
    /**
371
     * Reset current line.
372
     */
373
    protected function resetLine()
374
    {
375
        $this->_line = null;
376
        $this->_lineCurrent = 0;
377
        $this->_lineLength = 0;
378
    }
379
 
380
    /**
381
     * Get current line seek.
382
     */
383
    public function getLineCurrent(): int
384
    {
385
        return $this->_lineCurrent;
386
    }
387
 
388
    /**
389
     * Get current line length.
390
     *
391
     * @return int
392
     */
393
    public function getLineLength(): int
394
    {
395
        return $this->_lineLength;
396
    }
397
 
398
    /**
399
     * Set prefix.
400
     */
401
    public function setPrefix(string $prefix)
402
    {
403
        $this->_prefix = $prefix;
404
    }
405
 
406
    /**
407
     * Get prefix.
408
     */
409
    public function getPrefix()
410
    {
411
        return $this->_prefix;
412
    }
413
 
414
    /**
415
     * Get buffer. Not for user.
416
     */
417
    public function getBuffer()
418
    {
419
        return $this->_buffer;
420
    }
421
 
422
    /**
423
     * Set an autocompleter.
424
     */
425
    public function setAutocompleter(Autocompleter $autocompleter)
426
    {
427
        $old = $this->_autocompleter;
428
        $this->_autocompleter = $autocompleter;
429
 
430
        return $old;
431
    }
432
 
433
    /**
434
     * Get the autocompleter.
435
     *
436
     * @return ?Autocompleter
437
     */
438
    public function getAutocompleter()
439
    {
440
        return $this->_autocompleter;
441
    }
442
 
443
    /**
444
     * Read on input. Not for user.
445
     */
446
    public function _read(int $length = 512): string
447
    {
448
        return Console::getInput()->read($length);
449
    }
450
 
451
    /**
452
     * Set current line. Not for user.
453
     */
454
    public function setLine(string $line)
455
    {
456
        $this->_line = $line;
457
        $this->_lineLength = \mb_strlen($this->_line ?: '');
458
        $this->_lineCurrent = $this->_lineLength;
459
    }
460
 
461
    /**
462
     * Set current line seek. Not for user.
463
     */
464
    public function setLineCurrent(int $current)
465
    {
466
        $this->_lineCurrent = $current;
467
    }
468
 
469
    /**
470
     * Set line length. Not for user.
471
     */
472
    public function setLineLength(int $length)
473
    {
474
        $this->_lineLength = $length;
475
    }
476
 
477
    /**
478
     * Set buffer. Not for user.
479
     */
480
    public function setBuffer(string $buffer)
481
    {
482
        $this->_buffer = $buffer;
483
    }
484
 
485
    /**
486
     * Up arrow binding.
487
     * Go backward in the history.
488
     */
489
    public function _bindArrowUp(self $self): int
490
    {
491
        if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) {
492
            ConsoleCursor::clear('↔');
493
            Console::getOutput()->writeAll($self->getPrefix());
494
        }
495
        $buffer = $self->previousHistory() ?? '';
496
        $self->setBuffer($buffer);
497
        $self->setLine($buffer);
498
 
499
        return static::STATE_CONTINUE;
500
    }
501
 
502
    /**
503
     * Down arrow binding.
504
     * Go forward in the history.
505
     */
506
    public function _bindArrowDown(self $self): int
507
    {
508
        if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) {
509
            ConsoleCursor::clear('↔');
510
            Console::getOutput()->writeAll($self->getPrefix());
511
        }
512
 
513
        $self->setBuffer($buffer = $self->nextHistory());
514
        $self->setLine($buffer);
515
 
516
        return static::STATE_CONTINUE;
517
    }
518
 
519
    /**
520
     * Right arrow binding.
521
     * Move cursor to the right.
522
     */
523
    public function _bindArrowRight(self $self): int
524
    {
525
        if ($self->getLineLength() > $self->getLineCurrent()) {
526
            if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) {
527
                ConsoleCursor::move('→');
528
            }
529
 
530
            $self->setLineCurrent($self->getLineCurrent() + 1);
531
        }
532
 
533
        $self->setBuffer('');
534
 
535
        return static::STATE_CONTINUE;
536
    }
537
 
538
    /**
539
     * Left arrow binding.
540
     * Move cursor to the left.
541
     */
542
    public function _bindArrowLeft(self $self): int
543
    {
544
        if (0 < $self->getLineCurrent()) {
545
            if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) {
546
                ConsoleCursor::move('←');
547
            }
548
 
549
            $self->setLineCurrent($self->getLineCurrent() - 1);
550
        }
551
 
552
        $self->setBuffer('');
553
 
554
        return static::STATE_CONTINUE;
555
    }
556
 
557
    /**
558
     * Backspace and Control-H binding.
559
     * Delete the first character at the right of the cursor.
560
     */
561
    public function _bindBackspace(self $self): int
562
    {
563
        $buffer = '';
564
 
565
        if (0 < $self->getLineCurrent()) {
566
            if (0 === (static::STATE_CONTINUE & static::STATE_NO_ECHO)) {
567
                ConsoleCursor::move('←');
568
                ConsoleCursor::clear('→');
569
            }
570
 
571
            if ($self->getLineLength() === $current = $self->getLineCurrent()) {
572
                $self->setLine(\mb_substr($self->getLine(), 0, -1));
573
            } else {
574
                $line = $self->getLine();
575
                $current = $self->getLineCurrent();
576
                $tail = \mb_substr($line, $current);
577
                $buffer = $tail.\str_repeat("\033[D", \mb_strlen($tail));
578
                $self->setLine(\mb_substr($line, 0, $current - 1).$tail);
579
                $self->setLineCurrent($current - 1);
580
            }
581
        }
582
 
583
        $self->setBuffer($buffer);
584
 
585
        return static::STATE_CONTINUE;
586
    }
587
 
588
    /**
589
     * Control-A binding.
590
     * Move cursor to beginning of line.
591
     */
592
    public function _bindControlA(self $self): int
593
    {
594
        for ($i = $self->getLineCurrent() - 1; 0 <= $i; --$i) {
595
            $self->_bindArrowLeft($self);
596
        }
597
 
598
        return static::STATE_CONTINUE;
599
    }
600
 
601
    /**
602
     * Control-B binding.
603
     * Move cursor backward one word.
604
     */
605
    public function _bindControlB(self $self): int
606
    {
607
        $current = $self->getLineCurrent();
608
 
609
        if (0 === $current) {
610
            return static::STATE_CONTINUE;
611
        }
612
 
613
        $words = \preg_split(
614
            '#\b#u',
615
            $self->getLine(),
616
            -1,
617
            \PREG_SPLIT_OFFSET_CAPTURE | \PREG_SPLIT_NO_EMPTY
618
        );
619
 
620
        for (
621
            $i = 0, $max = \count($words) - 1;
622
            $i < $max && $words[$i + 1][1] < $current;
623
            ++$i
624
        ) {
625
        }
626
 
627
        for ($j = $words[$i][1] + 1; $current >= $j; ++$j) {
628
            $self->_bindArrowLeft($self);
629
        }
630
 
631
        return static::STATE_CONTINUE;
632
    }
633
 
634
    /**
635
     * Control-E binding.
636
     * Move cursor to end of line.
637
     */
638
    public function _bindControlE(self $self): int
639
    {
640
        for (
641
            $i = $self->getLineCurrent(), $max = $self->getLineLength();
642
            $i < $max;
643
            ++$i
644
        ) {
645
            $self->_bindArrowRight($self);
646
        }
647
 
648
        return static::STATE_CONTINUE;
649
    }
650
 
651
    /**
652
     * Control-F binding.
653
     * Move cursor forward one word.
654
     */
655
    public function _bindControlF(self $self): int
656
    {
657
        $current = $self->getLineCurrent();
658
 
659
        if ($self->getLineLength() === $current) {
660
            return static::STATE_CONTINUE;
661
        }
662
 
663
        $words = \preg_split(
664
            '#\b#u',
665
            $self->getLine(),
666
            -1,
667
            \PREG_SPLIT_OFFSET_CAPTURE | \PREG_SPLIT_NO_EMPTY
668
        );
669
 
670
        for (
671
            $i = 0, $max = \count($words) - 1;
672
            $i < $max && $words[$i][1] < $current;
673
            ++$i
674
        ) {
675
        }
676
 
677
        if (!isset($words[$i + 1])) {
678
            $words[$i + 1] = [1 => $self->getLineLength()];
679
        }
680
 
681
        for ($j = $words[$i + 1][1]; $j > $current; --$j) {
682
            $self->_bindArrowRight($self);
683
        }
684
 
685
        return static::STATE_CONTINUE;
686
    }
687
 
688
    /**
689
     * Control-W binding.
690
     * Delete first backward word.
691
     */
692
    public function _bindControlW(self $self): int
693
    {
694
        $current = $self->getLineCurrent();
695
 
696
        if (0 === $current) {
697
            return static::STATE_CONTINUE;
698
        }
699
 
700
        $words = \preg_split(
701
            '#\b#u',
702
            $self->getLine(),
703
            -1,
704
            \PREG_SPLIT_OFFSET_CAPTURE | \PREG_SPLIT_NO_EMPTY
705
        );
706
 
707
        for (
708
            $i = 0, $max = \count($words) - 1;
709
            $i < $max && $words[$i + 1][1] < $current;
710
            ++$i
711
        ) {
712
        }
713
 
714
        for ($j = $words[$i][1] + 1; $current >= $j; ++$j) {
715
            $self->_bindBackspace($self);
716
        }
717
 
718
        return static::STATE_CONTINUE;
719
    }
720
 
721
    /**
722
     * Newline binding.
723
     */
724
    public function _bindNewline(self $self): int
725
    {
726
        $self->addHistory($self->getLine());
727
 
728
        return static::STATE_BREAK;
729
    }
730
 
731
    /**
732
     * Tab binding.
733
     */
734
    public function _bindTab(self $self): int
735
    {
736
        $output = Console::getOutput();
737
        $autocompleter = $self->getAutocompleter();
738
        $state = static::STATE_CONTINUE | static::STATE_NO_ECHO;
739
 
740
        if (null === $autocompleter) {
741
            return $state;
742
        }
743
 
744
        $current = $self->getLineCurrent();
745
        $line = $self->getLine();
746
 
747
        if (0 === $current) {
748
            return $state;
749
        }
750
 
751
        $matches = \preg_match_all(
752
            '#'.$autocompleter->getWordDefinition().'$#u',
753
            \mb_substr($line, 0, $current),
754
            $words
755
        );
756
 
757
        if (0 === $matches) {
758
            return $state;
759
        }
760
 
761
        $word = $words[0][0];
762
 
763
        if ('' === \trim($word)) {
764
            return $state;
765
        }
766
 
767
        $solution = $autocompleter->complete($word);
768
        $length = \mb_strlen($word);
769
 
770
        if (null === $solution) {
771
            return $state;
772
        }
773
 
774
        if (\is_array($solution)) {
775
            $_solution = $solution;
776
            $count = \count($_solution) - 1;
777
            $cWidth = 0;
778
            $window = ConsoleWindow::getSize();
779
            $wWidth = $window['x'];
780
            $cursor = ConsoleCursor::getPosition();
781
 
782
            \array_walk($_solution, function (&$value) use (&$cWidth) {
783
                $handle = \mb_strlen($value);
784
 
785
                if ($handle > $cWidth) {
786
                    $cWidth = $handle;
787
                }
788
 
789
                return;
790
            });
791
            \array_walk($_solution, function (&$value) use (&$cWidth) {
792
                $handle = \mb_strlen($value);
793
 
794
                if ($handle >= $cWidth) {
795
                    return;
796
                }
797
 
798
                $value .= \str_repeat(' ', $cWidth - $handle);
799
 
800
                return;
801
            });
802
 
803
            $mColumns = (int) \floor($wWidth / ($cWidth + 2));
804
            $mLines = (int) \ceil(($count + 1) / $mColumns);
805
            --$mColumns;
806
            $i = 0;
807
 
808
            if (0 > $window['y'] - $cursor['y'] - $mLines) {
809
                ConsoleWindow::scroll('↑', $mLines);
810
                ConsoleCursor::move('↑', $mLines);
811
            }
812
 
813
            ConsoleCursor::save();
814
            ConsoleCursor::hide();
815
            ConsoleCursor::move('↓ LEFT');
816
            ConsoleCursor::clear('↓');
817
 
818
            foreach ($_solution as $j => $s) {
819
                $output->writeAll("\033[0m".$s."\033[0m");
820
 
821
                if ($i++ < $mColumns) {
822
                    $output->writeAll('  ');
823
                } else {
824
                    $i = 0;
825
 
826
                    if (isset($_solution[$j + 1])) {
827
                        $output->writeAll("\n");
828
                    }
829
                }
830
            }
831
 
832
            ConsoleCursor::restore();
833
            ConsoleCursor::show();
834
 
835
            ++$mColumns;
836
            $input = Console::getInput();
837
            $read = [$input->getStream()->getStream()];
838
            $write = $except = [];
839
            $mColumn = -1;
840
            $mLine = -1;
841
            $coord = -1;
842
            $unselect = function () use (
843
                &$mColumn,
844
                &$mLine,
845
                &$coord,
846
                &$_solution,
847
                &$cWidth,
848
                $output
849
            ) {
850
                ConsoleCursor::save();
851
                ConsoleCursor::hide();
852
                ConsoleCursor::move('↓ LEFT');
853
                ConsoleCursor::move('→', $mColumn * ($cWidth + 2));
854
                ConsoleCursor::move('↓', $mLine);
855
                $output->writeAll("\033[0m".$_solution[$coord]."\033[0m");
856
                ConsoleCursor::restore();
857
                ConsoleCursor::show();
858
 
859
                return;
860
            };
861
            $select = function () use (
862
                &$mColumn,
863
                &$mLine,
864
                &$coord,
865
                &$_solution,
866
                &$cWidth,
867
                $output
868
            ) {
869
                ConsoleCursor::save();
870
                ConsoleCursor::hide();
871
                ConsoleCursor::move('↓ LEFT');
872
                ConsoleCursor::move('→', $mColumn * ($cWidth + 2));
873
                ConsoleCursor::move('↓', $mLine);
874
                $output->writeAll("\033[7m".$_solution[$coord]."\033[0m");
875
                ConsoleCursor::restore();
876
                ConsoleCursor::show();
877
 
878
                return;
879
            };
880
            $init = function () use (
881
                &$mColumn,
882
                &$mLine,
883
                &$coord,
884
                &$select
885
            ) {
886
                $mColumn = 0;
887
                $mLine = 0;
888
                $coord = 0;
889
                $select();
890
 
891
                return;
892
            };
893
 
894
            while (true) {
895
                @\stream_select($read, $write, $except, 30, 0);
896
 
897
                if (empty($read)) {
898
                    $read = [$input->getStream()->getStream()];
899
 
900
                    continue;
901
                }
902
 
903
                switch ($char = $self->_read()) {
904
                    case "\033[A":
905
                        if (-1 === $mColumn && -1 === $mLine) {
906
                            $init();
907
 
908
                            break;
909
                        }
910
 
911
                        $unselect();
912
                        $coord = \max(0, $coord - $mColumns);
913
                        $mLine = (int) \floor($coord / $mColumns);
914
                        $mColumn = $coord % $mColumns;
915
                        $select();
916
 
917
                        break;
918
 
919
                    case "\033[B":
920
                        if (-1 === $mColumn && -1 === $mLine) {
921
                            $init();
922
 
923
                            break;
924
                        }
925
 
926
                        $unselect();
927
                        $coord = \min($count, $coord + $mColumns);
928
                        $mLine = (int) \floor($coord / $mColumns);
929
                        $mColumn = $coord % $mColumns;
930
                        $select();
931
 
932
                        break;
933
 
934
                    case "\t":
935
                    case "\033[C":
936
                        if (-1 === $mColumn && -1 === $mLine) {
937
                            $init();
938
 
939
                            break;
940
                        }
941
 
942
                        $unselect();
943
                        $coord = \min($count, $coord + 1);
944
                        $mLine = (int) \floor($coord / $mColumns);
945
                        $mColumn = $coord % $mColumns;
946
                        $select();
947
 
948
                        break;
949
 
950
                    case "\033[D":
951
                        if (-1 === $mColumn && -1 === $mLine) {
952
                            $init();
953
 
954
                            break;
955
                        }
956
 
957
                        $unselect();
958
                        $coord = \max(0, $coord - 1);
959
                        $mLine = (int) \floor($coord / $mColumns);
960
                        $mColumn = $coord % $mColumns;
961
                        $select();
962
 
963
                        break;
964
 
965
                    case "\n":
966
                        if (-1 !== $mColumn && -1 !== $mLine) {
967
                            $tail = \mb_substr($line, $current);
968
                            $current -= $length;
969
                            $self->setLine(
970
                                \mb_substr($line, 0, $current).
971
                                $solution[$coord].
972
                                $tail
973
                            );
974
                            $self->setLineCurrent(
975
                                $current + \mb_strlen($solution[$coord])
976
                            );
977
 
978
                            ConsoleCursor::move('←', $length);
979
                            $output->writeAll($solution[$coord]);
980
                            ConsoleCursor::clear('→');
981
                            $output->writeAll($tail);
982
                            ConsoleCursor::move('←', \mb_strlen($tail));
983
                        }
984
 
985
                        // no break
986
                    default:
987
                        $mColumn = -1;
988
                        $mLine = -1;
989
                        $coord = -1;
990
                        ConsoleCursor::save();
991
                        ConsoleCursor::move('↓ LEFT');
992
                        ConsoleCursor::clear('↓');
993
                        ConsoleCursor::restore();
994
 
995
                        if ("\033" !== $char && "\n" !== $char) {
996
                            $self->setBuffer($char);
997
 
998
                            return $self->_readLine($char);
999
                        }
1000
 
1001
                        break 2;
1002
                }
1003
            }
1004
 
1005
            return $state;
1006
        }
1007
 
1008
        $tail = \mb_substr($line, $current);
1009
        $current -= $length;
1010
        $self->setLine(
1011
            \mb_substr($line, 0, $current).
1012
            $solution.
1013
            $tail
1014
        );
1015
        $self->setLineCurrent(
1016
            $current + \mb_strlen($solution)
1017
        );
1018
 
1019
        ConsoleCursor::move('←', $length);
1020
        $output->writeAll($solution);
1021
        ConsoleCursor::clear('→');
1022
        $output->writeAll($tail);
1023
        ConsoleCursor::move('←', \mb_strlen($tail));
1024
 
1025
        return $state;
1026
    }
1027
}
1028
 
1029
/*
1030
 * Advanced interaction.
1031
 */
1032
Console::advancedInteraction();