@@ -25,6 +25,7 @@ class ConsoleSectionOutput extends StreamOutput
2525 private int $ lines = 0 ;
2626 private array $ sections ;
2727 private Terminal $ terminal ;
28+ private int $ maxHeight = 0 ;
2829
2930 /**
3031 * @param resource $stream
@@ -38,6 +39,23 @@ public function __construct($stream, array &$sections, int $verbosity, bool $dec
3839 $ this ->terminal = new Terminal ();
3940 }
4041
42+ /**
43+ * Defines a maximum number of lines for this section.
44+ *
45+ * When more lines are added, the section will automatically scroll to the
46+ * end (i.e. remove the first lines to comply with the max height).
47+ */
48+ public function setMaxHeight (int $ maxHeight ): void
49+ {
50+ // when changing max height, clear output of current section and redraw again with the new height
51+ $ existingContent = $ this ->popStreamContentUntilCurrentSection ($ this ->maxHeight ? min ($ this ->maxHeight , $ this ->lines ) : $ this ->lines );
52+
53+ $ this ->maxHeight = $ maxHeight ;
54+
55+ parent ::doWrite ($ this ->getVisibleContent (), false );
56+ parent ::doWrite ($ existingContent , false );
57+ }
58+
4159 /**
4260 * Clears previous output for this section.
4361 *
@@ -58,7 +76,7 @@ public function clear(int $lines = null)
5876
5977 $ this ->lines -= $ lines ;
6078
61- parent ::doWrite ($ this ->popStreamContentUntilCurrentSection ($ lines ), false );
79+ parent ::doWrite ($ this ->popStreamContentUntilCurrentSection ($ this -> maxHeight ? min ( $ this -> maxHeight , $ lines ) : $ lines ), false );
6280 }
6381
6482 /**
@@ -75,13 +93,23 @@ public function getContent(): string
7593 return implode ('' , $ this ->content );
7694 }
7795
96+ public function getVisibleContent (): string
97+ {
98+ if (0 === $ this ->maxHeight ) {
99+ return $ this ->getContent ();
100+ }
101+
102+ return implode ('' , \array_slice ($ this ->content , -$ this ->maxHeight ));
103+ }
104+
78105 /**
79106 * @internal
80107 */
81- public function addContent (string $ input , bool $ newline = true )
108+ public function addContent (string $ input , bool $ newline = true ): int
82109 {
83110 $ width = $ this ->terminal ->getWidth ();
84111 $ lines = explode (\PHP_EOL , $ input );
112+ $ linesAdded = 0 ;
85113 $ count = \count ($ lines ) - 1 ;
86114 foreach ($ lines as $ i => $ lineContent ) {
87115 // re-add the line break (that has been removed in the above `explode()` for
@@ -113,8 +141,12 @@ public function addContent(string $input, bool $newline = true)
113141 $ this ->content [] = $ lineContent ;
114142 }
115143
116- $ this -> lines += (int ) ceil ($ this ->getDisplayLength ($ lineContent ) / $ width ) ?: 1 ;
144+ $ linesAdded += (int ) ceil ($ this ->getDisplayLength ($ lineContent ) / $ width ) ?: 1 ;
117145 }
146+
147+ $ this ->lines += $ linesAdded ;
148+
149+ return $ linesAdded ;
118150 }
119151
120152 protected function doWrite (string $ message , bool $ newline )
@@ -127,13 +159,25 @@ protected function doWrite(string $message, bool $newline)
127159
128160 // Check if the previous line (last entry of `$this->content`) needs to be continued
129161 // (i.e. does not end with a line break). In which case, it needs to be erased first.
130- $ deleteLastLine = ($ lastLine = end ($ this ->content ) ?: '' ) && !str_ends_with ($ lastLine , \PHP_EOL ) ? 1 : 0 ;
131- $ erasedContent = $ this ->popStreamContentUntilCurrentSection ($ deleteLastLine );
162+ $ linesToClear = $ deleteLastLine = ($ lastLine = end ($ this ->content ) ?: '' ) && !str_ends_with ($ lastLine , \PHP_EOL ) ? 1 : 0 ;
132163
133- $ this ->addContent ($ message , $ newline );
164+ $ linesAdded = $ this ->addContent ($ message , $ newline );
165+
166+ if ($ lineOverflow = $ this ->maxHeight > 0 && $ this ->lines > $ this ->maxHeight ) {
167+ // on overflow, clear the whole section and redraw again (to remove the first lines)
168+ $ linesToClear = $ this ->maxHeight ;
169+ }
170+
171+ $ erasedContent = $ this ->popStreamContentUntilCurrentSection ($ linesToClear );
172+
173+ if ($ lineOverflow ) {
174+ // redraw existing lines of the section
175+ $ previousLinesOfSection = \array_slice ($ this ->content , $ this ->lines - $ this ->maxHeight , $ this ->maxHeight - $ linesAdded );
176+ parent ::doWrite (implode ('' , $ previousLinesOfSection ), false );
177+ }
134178
135- // If the last line was removed, re-print its content together with the new content.
136- // Otherwise , just print the new content.
179+ // if the last line was removed, re-print its content together with the new content.
180+ // otherwise , just print the new content.
137181 parent ::doWrite ($ deleteLastLine ? $ lastLine .$ message : $ message , true );
138182 parent ::doWrite ($ erasedContent , false );
139183 }
@@ -153,7 +197,7 @@ private function popStreamContentUntilCurrentSection(int $numberOfLinesToClearFr
153197 }
154198
155199 $ numberOfLinesToClear += $ section ->lines ;
156- if ('' !== $ sectionContent = $ section ->getContent ()) {
200+ if ('' !== $ sectionContent = $ section ->getVisibleContent ()) {
157201 if (!str_ends_with ($ sectionContent , \PHP_EOL )) {
158202 $ sectionContent .= \PHP_EOL ;
159203 }
0 commit comments