-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[Console] Modify console output and print multiple modifyable sections #24363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
$buffer = 0; | ||
|
||
foreach ($this->ref as $outputStream) { | ||
if (spl_object_id($outputStream) === spl_object_id($output)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just compare the objects themselves inside of comparing the ids explicitly. It will be easier to understand (and will avoid the dependency on the polyfill).
$content = ''; | ||
|
||
foreach ($this->ref as $outputStream) { | ||
if (spl_object_id($outputStream) === spl_object_id($output)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
this is clearly lacking tests. |
@stof The todo at the end of the description says that tests still needs to be added |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice feature, thanks!
* @param resource $stream | ||
* @param OutputSectionReference $sectionReference | ||
* @param int $verbosity | ||
* @param null $decorated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bool|null
? null for auto-guessing
* @param OutputSectionReference $sectionReference | ||
* @param int $verbosity | ||
* @param null $decorated | ||
* @param OutputFormatterInterface $formatter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OutputFormatterInterface|null
? null to use default OutputFormatter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See reply below, we want to use the values from the original console output
* @param null $decorated | ||
* @param OutputFormatterInterface $formatter | ||
*/ | ||
public function __construct($stream, OutputSectionReference $sectionReference, $verbosity, $decorated, OutputFormatterInterface $formatter) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
..., $verbosity = self::VERBOSITY_NORMAL, $decorated = null, OutputFormatterInterface $formatter = null
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if these values should to be defaulted, as we want to inherit all the values from origin console output
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's pragmatic right now, but out-the-box we would want to use defaults for simplicity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class should never be used on it's own, it's just an implementation detail and should always be constructed from the ConsoleOutput
, which means it should always inherit the values set there (default or otherwise)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That means the PHPDoc of the constructor could be removed altogether if so?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dockblock removed
* | ||
* @author Pierre du Plessis <[email protected]> | ||
*/ | ||
final class OutputSectionReference |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wondering if we can remove the final
constraint and add @final
annotation instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The annotation would only be useful if we wanted to declare the class final after a stable release. Since it's a new class, the final
keyword would be the way to go, imho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ostrolucky This is a purely internal class, whether it's final or not should not make any difference to the user as they would never know about this class
$prevLines = explode("\n", $messages); | ||
|
||
for ($i = 0; $i < count($prevLines); ++$i) { | ||
$total += ceil(strlen($prevLines[$i]) / $this->terminal->getWidth()) ?: 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should use a better function than strlen (only visible characters must be counted)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helper::strlen() should be used
Only first example works properly for me. It also works based on a viewport, I don't think that's by purpose //edit: it has been fixed, removed images as they took too much space |
@ostrolucky Can you please try the examples again, there was some missing changes that I forgot to commit
A limitation with the console is that you can only manipulate anything in the viewport. So if any content stretches outside the viewport, it becomes extremely difficult (if not impossible) to modify the contents. |
*/ | ||
public function section() | ||
{ | ||
return new ConsoleSectionOutput($this->getStream(), $this->outputReference, $this->getVerbosity(), $this->isDecorated(), $this->getFormatter()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method should throw if !$this->isDecorated()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chalasr I don't think this should throw as it might cause issues in E.G a CI environment where the output should just continue printing without decoration. If this throws an exception, the commands would fail and not continue to run, which might not be the expected behaviour. I have instead added checks for decorated to skip the ANSI codes
$prevLines = explode("\n", $messages); | ||
|
||
for ($i = 0; $i < count($prevLines); ++$i) { | ||
$total += ceil(strlen($prevLines[$i]) / $this->terminal->getWidth()) ?: 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helper::strlen() should be used
$prevLines = explode("\n", $messages); | ||
|
||
for ($i = 0; $i < count($prevLines); ++$i) { | ||
$total += ceil(Helper::strlen($prevLines[$i]) / $this->terminal->getWidth()) ?: 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's still the good way to calculate the length of a line in a console, as an example a tabulation must be count as 8 when displaying (or other number depending on the term configuration) or a colored string / background will have special chars which here will be counted, but they must not.
I was confronted with the same behavior and use this implementation : https://github.com/jolicode/asynit/blob/master/src/Output/TestOutput.php#L110 far from being perfect but gives a good start.
@@ -279,6 +280,11 @@ public function setRow($column, array $row) | |||
*/ | |||
public function render() | |||
{ | |||
if ($this->output instanceof ConsoleSectionOutput) { | |||
// TODO: Calculate the number of lines to remove for the table |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outdated todo?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No this still needs to be done, currently it's only calculating the number of rows and headers, but not taking the row separator into account
This kinda changes behaviour of Table helper. I don't think it's acceptable. Table helper without this patch doesn't redraw it's content. Calling $table->render();
$table->render(); would normally output table twice, but this patch makes it erase first one and replace it with second one Second thing is that ConsoleSectionOutput always appends newline to output. Third thing, ProgressBar only works thanks to special patch. Anything other utilizing escape sequences won't work. Trying to figure out how to make this universal. |
The change in behaviour for the table helper is intented. The table is rendered in a section output, that can be modified. This adds a feature to the table helper where you can modify and re-render the table without rendering it twice. If you want the normal behaviour, just pass the normal console output instance to the table and not a sectioned output.
The newline at the end is currently a requirement, as it becomes difficult to navigate the console and change the contents without it. But I am working on a patch.
I'm not sure if we should cater for output with escape sequences. If a user wants to use escape sequences, they should just use the standard console output. If they want to modify the output, then they should use the section output. This PR is not finished, there are a lot of edge cases and some missing functionality that I'm working on to solve, which I will push once it's done. Thanks for the feedback so far |
@ostrolucky If the Table behavior is different only when using the new output class then that means Table just leverages the new feature. If passing the existing outputs gives the same result as before (reading the code I believe it does) then there is no issue. |
@chalasr I still don't think this feature should affect behaviour of current components. Purpose of this feature is just ability to split screen to sections. How is forcing table to redraw itself related to that? Here is better example why is this wrong: $table1->render();
$table2->render(); You would expect both tables to be shown on the screen. Instead, content of second table will overwrite content of first table, unless explicitly assigned different output section. I don't think this is expected. If you do this in a loop or have branching logic which determines if, when and which table is supposed to be outputted, you will quickly find out that this approach is unmaintainable. Rest of my points are not so important, solving them would require to make implementation a lot more complicated, I was just not sure if they are important for you so wanted to brought them up. If they aren't, they aren't for me either. |
@ostrolucky How will the second table overwrite the first one? If you use different output sections, they will still render both, as the table is only redrawn within it's own section. If you use the normal console output and not the new class, both tables will still be shown as it won't use the new functionality. So IMO there is no unexpected behaviour. The only missing part in this instance is that the new class doesn't take into account the output of the normal console. But that is one of the fixes I'm working on. So if you combine thr normal console output and the output of the new class, then you might run into some rendering issue, but that should be fixed soon |
You didn't continue to read. I did mention that you if you want to avoid this, you must use different output sections and that's complicated when you have any kind of logic. For example: $section1 = $output->section();
$section2 = $output->section();
$section2->writeln('some stuff');
while (true) {
$table = new Table($section1);
$table->setHeaders(['Stuff']);
$table->addRow([$rand]);
$table->render();
} Now, how do I output contents of these tables above section2 without redrawing them? Now I am forced to manipulate with order of the sections container directly, because I have to create new output sections on the fly and push them in between. |
Then just use |
2ff25c1
to
00ea1f1
Compare
00ea1f1
to
e6573b7
Compare
Last comments addressed and table bug fixed. I tested all the original examples and every one now works as expected. @ostrolucky Thank you for all the effort in properly testing and reviewing this. Status: Needs Review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A new entry in the console CHANGELOG and 👍 for me, great work @pierredup!
@@ -5,6 +5,7 @@ CHANGELOG | |||
----- | |||
|
|||
* added option to run suggested command if command is not found and only 1 alternative is available | |||
* added option to modify console output and print multiple modifyable sections |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
modifiable
@@ -252,6 +256,25 @@ public function addRow($row) | |||
return $this; | |||
} | |||
|
|||
/** | |||
* Add a row to the table, and re-render the table. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adds
re-renders
} | ||
|
||
if ($numberOfLinesToClear > 0) { | ||
// Move cursor up n lines |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move
Thank you @pierredup. |
…ifyable sections (pierredup) This PR was squashed before being merged into the 4.1-dev branch (closes #24363). Discussion ---------- [Console] Modify console output and print multiple modifyable sections | Q | A | ------------- | --- | Branch? | 4.1 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | TBD | Fixed tickets | | License | MIT | Doc PR | symfony/symfony-docs#9304 Add support to create different output sections for the console output. Each section is it's own 'stream' of output, where the output can be modified (even if there are other output after it). This allows you to modify previous output in the console, either by appending new lines, modifying previous lines or clearing the output. Modifying a sections output doesn't affect the output after that or in other sections. Some examples of what can be done: **Overwriting content in a previous section:** Code: ```php $section1 = $output->section(); $section2 = $output->section(); $section1->writeln("<comment>Doing something</comment>\n"); usleep(500000); $section2->writeln('<info>Result of first operation</info>'); usleep(500000); $section1->overwrite("<comment>Doing something else</comment>\n"); usleep(500000); $section2->writeln('<info>Result of second operation</info>'); usleep(500000); $section1->overwrite("<comment>Finishing</comment>\n"); usleep(500000); $section2->writeln('<info>Last Result</info>'); ``` Result:  **Multiple Progress Bars:** Code: ```php $section1 = $output->section(); $section2 = $output->section(); $progress = new ProgressBar($section1); $progress2 = new ProgressBar($section2); $progress->start(100); $progress2->start(100); $c = 0; while (++$c < 100) { $progress->advance(); if ($c % 2 === 0) { $progress2->advance(4); } usleep(500000); } ``` Result:  **Modifying content of a table & updating a progress bar:** Code: ```php $section1 = $output->section(); $section2 = $output->section(); $progress = new ProgressBar($section1); $table = new Table($section2); $table->addRow(['Row 1']); $table->render(); $progress->start(5); $c = 0; while (++$c < 5) { $table->appendRow(['Row '.($c + 1)]); $progress->advance(); usleep(500000); } $progress->finish(); $section1->clear(); ``` Result:  **Example with Symfony Installer:*** Before:  After:  TODO: - [x] Add unit tests Commits ------- 9ec51a1 [Console] Modify console output and print multiple modifyable sections
…redup) This PR was squashed before being merged into the 4.1 branch (closes #9304). Discussion ---------- Add documentation about manipulating console output Add documentation about manipulating console output (symfony/symfony#24363) Commits ------- 3b0e00c Add documentation about manipulating console output
Add support to create different output sections for the console output.
Each section is it's own 'stream' of output, where the output can be modified (even if there are other output after it). This allows you to modify previous output in the console, either by appending new lines, modifying previous lines or clearing the output. Modifying a sections output doesn't affect the output after that or in other sections.
Some examples of what can be done:
Overwriting content in a previous section:
Code:
Result:

Multiple Progress Bars:
Code:
Result:

Modifying content of a table & updating a progress bar:
Code:
Result:

Example with Symfony Installer:*
Before:

After:

TODO: