1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\Console;
13
14use Symfony\Component\Console\Output\OutputInterface;
15
16/**
17 * @author Pierre du Plessis <pdples@gmail.com>
18 */
19final class Cursor
20{
21    private $output;
22    private $input;
23
24    public function __construct(OutputInterface $output, $input = null)
25    {
26        $this->output = $output;
27        $this->input = $input ?? (\defined('STDIN') ? \STDIN : fopen('php://input', 'r+'));
28    }
29
30    public function moveUp(int $lines = 1): self
31    {
32        $this->output->write(sprintf("\x1b[%dA", $lines));
33
34        return $this;
35    }
36
37    public function moveDown(int $lines = 1): self
38    {
39        $this->output->write(sprintf("\x1b[%dB", $lines));
40
41        return $this;
42    }
43
44    public function moveRight(int $columns = 1): self
45    {
46        $this->output->write(sprintf("\x1b[%dC", $columns));
47
48        return $this;
49    }
50
51    public function moveLeft(int $columns = 1): self
52    {
53        $this->output->write(sprintf("\x1b[%dD", $columns));
54
55        return $this;
56    }
57
58    public function moveToColumn(int $column): self
59    {
60        $this->output->write(sprintf("\x1b[%dG", $column));
61
62        return $this;
63    }
64
65    public function moveToPosition(int $column, int $row): self
66    {
67        $this->output->write(sprintf("\x1b[%d;%dH", $row + 1, $column));
68
69        return $this;
70    }
71
72    public function savePosition(): self
73    {
74        $this->output->write("\x1b7");
75
76        return $this;
77    }
78
79    public function restorePosition(): self
80    {
81        $this->output->write("\x1b8");
82
83        return $this;
84    }
85
86    public function hide(): self
87    {
88        $this->output->write("\x1b[?25l");
89
90        return $this;
91    }
92
93    public function show(): self
94    {
95        $this->output->write("\x1b[?25h\x1b[?0c");
96
97        return $this;
98    }
99
100    /**
101     * Clears all the output from the current line.
102     */
103    public function clearLine(): self
104    {
105        $this->output->write("\x1b[2K");
106
107        return $this;
108    }
109
110    /**
111     * Clears all the output from the current line after the current position.
112     */
113    public function clearLineAfter(): self
114    {
115        $this->output->write("\x1b[K");
116
117        return $this;
118    }
119
120    /**
121     * Clears all the output from the cursors' current position to the end of the screen.
122     */
123    public function clearOutput(): self
124    {
125        $this->output->write("\x1b[0J");
126
127        return $this;
128    }
129
130    /**
131     * Clears the entire screen.
132     */
133    public function clearScreen(): self
134    {
135        $this->output->write("\x1b[2J");
136
137        return $this;
138    }
139
140    /**
141     * Returns the current cursor position as x,y coordinates.
142     */
143    public function getCurrentPosition(): array
144    {
145        static $isTtySupported;
146
147        if (null === $isTtySupported && \function_exists('proc_open')) {
148            $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
149        }
150
151        if (!$isTtySupported) {
152            return [1, 1];
153        }
154
155        $sttyMode = shell_exec('stty -g');
156        shell_exec('stty -icanon -echo');
157
158        @fwrite($this->input, "\033[6n");
159
160        $code = trim(fread($this->input, 1024));
161
162        shell_exec(sprintf('stty %s', $sttyMode));
163
164        sscanf($code, "\033[%d;%dR", $row, $col);
165
166        return [$col, $row];
167    }
168}
169