ConsoleSectionOutput.php 3.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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\Output;
  11. use Symfony\Component\Console\Formatter\OutputFormatterInterface;
  12. use Symfony\Component\Console\Helper\Helper;
  13. use Symfony\Component\Console\Terminal;
  14. /**
  15. * @author Pierre du Plessis <pdples@gmail.com>
  16. * @author Gabriel Ostrolucký <gabriel.ostrolucky@gmail.com>
  17. */
  18. class ConsoleSectionOutput extends StreamOutput
  19. {
  20. private $content = array();
  21. private $lines = 0;
  22. private $sections;
  23. private $terminal;
  24. /**
  25. * @param resource $stream
  26. * @param ConsoleSectionOutput[] $sections
  27. */
  28. public function __construct($stream, array &$sections, int $verbosity, bool $decorated, OutputFormatterInterface $formatter)
  29. {
  30. parent::__construct($stream, $verbosity, $decorated, $formatter);
  31. array_unshift($sections, $this);
  32. $this->sections = &$sections;
  33. $this->terminal = new Terminal();
  34. }
  35. /**
  36. * Clears previous output for this section.
  37. *
  38. * @param int $lines Number of lines to clear. If null, then the entire output of this section is cleared
  39. */
  40. public function clear(int $lines = null)
  41. {
  42. if (empty($this->content) || !$this->isDecorated()) {
  43. return;
  44. }
  45. if ($lines) {
  46. \array_splice($this->content, -($lines * 2)); // Multiply lines by 2 to cater for each new line added between content
  47. } else {
  48. $lines = $this->lines;
  49. $this->content = array();
  50. }
  51. $this->lines -= $lines;
  52. parent::doWrite($this->popStreamContentUntilCurrentSection($lines), false);
  53. }
  54. /**
  55. * Overwrites the previous output with a new message.
  56. *
  57. * @param array|string $message
  58. */
  59. public function overwrite($message)
  60. {
  61. $this->clear();
  62. $this->writeln($message);
  63. }
  64. public function getContent(): string
  65. {
  66. return implode('', $this->content);
  67. }
  68. /**
  69. * {@inheritdoc}
  70. */
  71. protected function doWrite($message, $newline)
  72. {
  73. if (!$this->isDecorated()) {
  74. return parent::doWrite($message, $newline);
  75. }
  76. $erasedContent = $this->popStreamContentUntilCurrentSection();
  77. foreach (explode(PHP_EOL, $message) as $lineContent) {
  78. $this->lines += ceil($this->getDisplayLength($lineContent) / $this->terminal->getWidth()) ?: 1;
  79. $this->content[] = $lineContent;
  80. $this->content[] = PHP_EOL;
  81. }
  82. parent::doWrite($message, true);
  83. parent::doWrite($erasedContent, false);
  84. }
  85. /**
  86. * At initial stage, cursor is at the end of stream output. This method makes cursor crawl upwards until it hits
  87. * current section. Then it erases content it crawled through. Optionally, it erases part of current section too.
  88. */
  89. private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFromCurrentSection = 0): string
  90. {
  91. $numberOfLinesToClear = $numberOfLinesToClearFromCurrentSection;
  92. $erasedContent = array();
  93. foreach ($this->sections as $section) {
  94. if ($section === $this) {
  95. break;
  96. }
  97. $numberOfLinesToClear += $section->lines;
  98. $erasedContent[] = $section->getContent();
  99. }
  100. if ($numberOfLinesToClear > 0) {
  101. // move cursor up n lines
  102. parent::doWrite(sprintf("\x1b[%dA", $numberOfLinesToClear), false);
  103. // erase to end of screen
  104. parent::doWrite("\x1b[0J", false);
  105. }
  106. return implode('', array_reverse($erasedContent));
  107. }
  108. private function getDisplayLength(string $text): string
  109. {
  110. return Helper::strlenWithoutDecoration($this->getFormatter(), str_replace("\t", ' ', $text));
  111. }
  112. }