@@ -25,6 +25,7 @@ class ConsoleSectionOutput extends StreamOutput
2525private int $ lines =0 ;
2626private array $ sections ;
2727private 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
7593return 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 ;
86114foreach ($ linesas $ 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
120152protected 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.
137181parent ::doWrite ($ deleteLastLine ?$ lastLine .$ message :$ message ,true );
138182parent ::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 ()) {
157201if (!str_ends_with ($ sectionContent , \PHP_EOL )) {
158202$ sectionContent .= \PHP_EOL ;
159203 }