Cursor.php 4.6 KB

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