Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Symfony Terminal Component

The Symfony Terminal Component

Introducing the Symfony Terminal Component

Fabien Potencier

December 08, 2023
Tweet

More Decks by Fabien Potencier

Other Decks in Technology

Transcript

  1. Symfony Console > Commands Input: parses CLI arguments/options Code: your

    own business logic Output: prints, redirect, pipe > Low-level terminal abstractions Cursor Color Style ...
  2. namespace Symfony\Component\Console; final class Color { public function __construct(string $foreground

    = '', string $background = '', array $options = []) bold underscore blink reverse conceal Is it a color? $color = new Color('#000', 'white', ['underscore', 'reverse']); Color Class Console 7.0
  3. class Color { public function __construct( private readonly string $value,

    ) { } ANSI-4 16 colors ANSI-256 256 colors ANSI-24 "true colors" Just a color! $color = new Color('#000'); $color = new Color('red'); Color Class Terminal 8.0 $color = $color->tint(20); $color = $color->shade(20); $color = $color->scale(20); $color = $color->mix($anotherColor, 20); Like Saas $context = new Context( colorMode: ColorMode::Ansi8, ); Color "degradation"
  4. $color = new AdaptiveColor('#0f0', '#ff0'); $context = new Context( darkMode:

    true, ); Color adapts depending on your terminal background Light/Dark background? Terminal 8.0 Light Dark
  5. $theme = new BootstrapColorTheme(); $color = $theme['primary']; $color = $theme['bg-primary'];

    // dark mode supported $color = $theme['blue']; $color = $theme['blue-200']; "Semantic" colors ColorTheme > BootstrapColorTheme Terminal 8.0 "Base" colors Dark/Light colors
  6. From Colors to Styles namespace Symfony\Component\Console; final class Color {

    public function __construct(string $foreground = '', string $background = '', array $options = [])
  7. Styling, Formatting, ... /** * Formats a message within a

    section. * * @param string $section The section name * @param string $text The text message * @param integer $size The maximum size allowed for a line (65 by default) */ public function formatSection($section, $text, $size = null) { return sprintf(">> %-$9s %s", $section, $this->excerpt($text, $size)); } /** * Formats a message within a section. */ public function formatSection(string $section, string $message, string $style = 'info'): string { return sprintf('<%s>[%s]</%s> %s', $style, $section, $style, $message); } Console 7.0 circa 2023 Tasks 1.1 circa 2008
  8. $style = new Style(); echo $style->render('Hello World!')."\n"; echo $style->foreground('#0f0')->render('Hello World!')."\n";

    echo $style->background('#0f0')->render('Hello World!')."\n"; echo $style->foreground('#0f0')->background('#0f0')->render('Hello World!')."\n"; echo $style->bold()->render('Hello World!')."\n"; echo $style->italic()->render('Hello World!')."\n"; echo $style->bold()->italic()->render('Hello World!')."\n"; echo $style->link('https://symfony.com')->render('Hello World!')."\n"; $style = (new Style()) ->foreground('#0f0') ->background('#0f0') ->bold() ->italic() ; Rendering a Style Style Terminal 8.0 Colors "Options" like bold/italic Reuse styles
  9. echo $style ->foreground($theme['indigo']) ->background($theme['yellow']) ->render('Hello World!')."\n" ; Inline vs Block

    Terminal 8.0 inline by default echo $style ->display(Style::DISPLAY_BLOCK) ->render('Hello World!') ; block display echo $style ->foreground($theme['indigo']) ->background($theme['yellow']) ->render('Hello World!')."\n" ; Style with a ColorTheme
  10. echo $style ->display(Style::DISPLAY_BLOCK) ->foreground($theme['yellow-300']) ->background($theme['pink-600']) ->height(10) ->align(Position::MIDDLE) ->render('Hello World!') ;

    echo $style ->display(Style::DISPLAY_BLOCK) ->foreground($theme['yellow-300']) ->background($theme['pink-600']) ->width(40) ->align(Position::CENTER) ->render('Hello World!') ; Size & Alignement Terminal 8.0
  11. $borderStyle = $style ->display(Style::DISPLAY_BLOCK) ->padding(0, 1) ->marginBottom(1) ->background($theme['yellow']) ->foreground($theme['indigo']) ->borderBackground($theme['yellow'])

    ->borderForeground($theme['indigo']) ->align(Position::MIDDLE | Position::CENTER) ; Let's reuse the base style Terminal 8.0
  12. Smart Borders Terminal 8.0 ['▁', '▁', '▁'] ['▎', ' ',

    '▊'] ['▔', '▔', '▔'] ['▊', '▔', '▎'] ['▊', ' ', '▎'] ['▊', '▁', '▎'] [2, 0, 1] [2, 0, 1] [2, 0, 1] [1, 1, 1] [0, 1, 3] [1, 1, 1]
  13. $titleStyle = $style ->display(Style::DISPLAY_BLOCK) ->padding(0, 2) ->foreground($theme['indigo']) ->align(Position::MIDDLE | Position::CENTER)

    ->borderStyle(BorderStyle::WIDE) ->borderBottom(true) ; echo $titleStyle->render('Hello World!'); Be creative with borders Terminal 8.0
  14. printf('%-10s ', 'Shade'); for ($i = 1; $i <= 9;

    $i++) { echo $style->foreground($theme['indigo-'.(100 * $i)])->render('▃▃'); } printf('%-10s ', 'Scale'); for ($i = -$max; $i <= $max; $i++) { echo $style->foreground($theme['indigo']->scale((int) (- $i / $max * 100)))->render('▃'); } printf('%-10s ', 'Shade'); for ($i = 0; $i <= $max; $i++) { echo $style->foreground($theme['indigo']->shade((int) (100 - $i / $max * 100)))->render('▃'); } printf('%-10s ', 'Tint'); for ($i = 0; $i <= $max; $i++) { echo $style->foreground($theme['indigo']->tint((int) (100 - $i / $max * 100)))->render('▃'); } printf('%-10s ', 'Mix'); for ($i = 0; $i <= $max; $i++) { echo $style->foreground($theme['indigo']->mix($theme['yellow'], (int) (100 - $i / $max * 100)))->render('▃'); } Terminal 8.0 foreach ($theme::BASE_SEMANTIC_COLORS as $name) { printf('%-10s ', ucfirst($name)); for ($i = 1; $i <= 9; $i++) { print($style->foreground($theme[$name.'-'.($i * 100)]) ->render('██')); } } printf('%-10s ', 'Gray'); for ($i = 1; $i <= 9; $i++) { print($style->foreground($theme['gray-'.($i * 100)]) ->render('██')); }
  15. $stylesheet = new DefaultStylesheet(); echo $stylesheet['span']->render('Hello World!')."\n"; echo $stylesheet['div']->render('Hello World!');

    echo $stylesheet['div']->bold()->render('Hello World!'); echo $stylesheet['b']->render('Hello World!')."\n"; echo $stylesheet['div bold']->render('Hello World!'); echo $stylesheet['h1']->render('Hello World!'); echo $stylesheet['h2']->render('Hello World!'); Stylesheet Terminal 8.0
  16. $stylesheet = new DefaultStylesheet(); foreach (ColorTheme::BASE_SEMANTIC_COLORS as $name) { echo

    $stylesheet['.alert-'.$name]->render('This is a '.$name.' alert box!'); } $content = $stylesheet['h1']->render('Well done!'). $stylesheet['p']->render('Lorem ipsum...'). $stylesheet['p']->marginBottom(0)->render('Lorem ipsum...') ; echo $stylesheet['.alert-success']->render($content); Default useful styles like Bootstrap alerts Terminal 8.0
  17. $term = new Terminal(); $stdout = $term->stdout(); dump( $stdout->hasDarkBackground(), $stdout->getWidth(),

    $stdout->getHeight(), ); print "\n\n"; $context = $stdout->getContext(); dump($context); print "\n\n"; $cursor = $stdout->cursor() $cursor->moveToPosition(10, 10); // operations on the screen $screen = $stdout->screen(); $screen->enableMouse(); $screen->clear(); Terminal Terminal 8.0
  18. $screen = new Columns( 'LEFT', 'RIGHT', ); echo $screen->render(); $stylesheet['.screen']

    = (new Style()) ->width('terminal["width"]') ->height(20) ; $screen = (new Columns( new StyledContent('LEFT', $stylesheet['.cell w-2fr']), new StyledContent('RIGHT', $stylesheet['.cell w-1fr']), ))->style($stylesheet['.screen']); echo $screen->render(); $screen = (new Rows( $screen, $screen, ))->style($stylesheet['.screen']); echo $screen->render(); Columns and Rows Terminal 8.0 2fr 1fr $stylesheet['.cell'] = (new Style()) ->borderStyle(BorderStyle::ROUNDED) ->align(Position::CENTER) ->borderForeground('color("border-primary")') ;
  19. $screen = (new Columns( (new Rows( new StyledContent('UP', $stylesheet['.cell']), new

    StyledContent('MIDDLE', $stylesheet['.cell']), new StyledContent(' LOW ', $stylesheet['.cell']), ))->position(Position::MIDDLE)->style($stylesheet['.cell w-1fr']), new StyledContent('CENTER', $stylesheet['.cell w-1fr']), new StyledContent(trim(str_repeat("RIGHT\n", 15)), $stylesheet['.cell w-1fr']), ))->position(Position::BOTTOM)->style($stylesheet['.screen']); echo $screen->render(); Columns and Rows Terminal 8.0
  20. interface WidgetInterface extends RenderInterface { public function init(Stylesheet $stylesheet, LoggerInterface

    $logger): void; public function update(MessageInterface $message): ?MessageInterface; } interface RenderInterface { public function render(): string; } WidgetInterface Terminal 8.0 ExitMessage KeyMessage MouseMessage TickMessage WindowSizeMessage
  21. class HelloWorldWidget implements WidgetInterface { public function init(Stylesheet $stylesheet, LoggerInterface

    $logger): void { } public function update(MessageInterface $message): ?MessageInterface { if ($message instanceof KeyMessage && 'q' === (string) $message->key) { return new ExitMessage(); } return null; } public function render(): string { return 'Hello World!'; } } $term = new Terminal(); $stylesheet = new DefaultStylesheet(); $app = new Application($term, $stylesheet, new HelloWorldWidget()); $app->run(); HelloWorldWidget Terminal 8.0 Render Update the state Infinite loop
  22. A Pager Terminal 8.0 Supported keys up and down page

    up and down go to top go to bottom
  23. public function __construct(string $content) { $this->viewport = new ViewportWidget(); $this->viewport->setContent($content,

    substr_count($content, "\n")); } public function init(Stylesheet $stylesheet, LoggerInterface $logger): void { $this->stylesheet = $stylesheet; $this->viewport->init($stylesheet, $logger); } public function update(MessageInterface $message): ?MessageInterface { $this->viewport->update($message); return null; } public function render(): string { return $this->stylesheet['span p-1 border-double'] ->render($this->viewport->render()); } A Pager Terminal 8.0 Update the Viewport Propagate init() Render the Viewport Rely on another widget
  24. $screen->enableMouse(); try { $app->run(); } finally { $screen->disableMouse(); } Mouse

    support Terminal 8.0 if ($message instanceof WindowSizeMessage) { $this->viewport->width($message->width); $this->viewport->height($message->height); } Window resizing support
  25. A component with many sub-parts Multi-year effort - release gradually

    7.1 -> 8.0 Progressively use it internally in Console Help from the community needed Terminal