tipo: modificación del cpanel y gitignore de la carpeta vendor

This commit is contained in:
2026-07-07 11:35:09 -06:00
parent a0c91dc567
commit 676ef0d506
8510 changed files with 1132803 additions and 4 deletions
@@ -0,0 +1,11 @@
<?php
namespace Laravel\Prompts\Themes\Contracts;
interface Scrolling
{
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int;
}
@@ -0,0 +1,14 @@
<?php
namespace Laravel\Prompts\Themes\Default;
class ClearRenderer extends Renderer
{
/**
* Clear the terminal.
*/
public function __invoke(): string
{
return "\033[H\033[J";
}
}
@@ -0,0 +1,56 @@
<?php
namespace Laravel\Prompts\Themes\Default\Concerns;
use Laravel\Prompts\Prompt;
trait DrawsBoxes
{
use InteractsWithStrings;
protected int $minWidth = 60;
/**
* Draw a box.
*
* @return $this
*/
protected function box(
string $title,
string $body,
string $footer = '',
string $color = 'gray',
string $info = '',
): self {
$this->minWidth = min($this->minWidth, Prompt::terminal()->cols() - 6);
$bodyLines = explode(PHP_EOL, $body);
$footerLines = array_filter(explode(PHP_EOL, $footer));
$width = $this->longest(array_merge($bodyLines, $footerLines, [$title]));
$titleLength = mb_strwidth($this->stripEscapeSequences($title));
$titleLabel = $titleLength > 0 ? " {$title} " : '';
$topBorder = str_repeat('─', $width - $titleLength + ($titleLength > 0 ? 0 : 2));
$this->line("{$this->{$color}(' ┌')}{$titleLabel}{$this->{$color}($topBorder.'┐')}");
foreach ($bodyLines as $line) {
$this->line("{$this->{$color}(' │')} {$this->pad($line, $width)} {$this->{$color}('│')}");
}
if (count($footerLines) > 0) {
$this->line($this->{$color}(' ├'.str_repeat('─', $width + 2).'┤'));
foreach ($footerLines as $line) {
$this->line("{$this->{$color}(' │')} {$this->pad($line, $width)} {$this->{$color}('│')}");
}
}
$this->line($this->{$color}(' └'.str_repeat(
'─', $info ? ($width - mb_strwidth($this->stripEscapeSequences($info))) : ($width + 2)
).($info ? " {$info} " : '').'┘'));
return $this;
}
}
@@ -0,0 +1,58 @@
<?php
namespace Laravel\Prompts\Themes\Default\Concerns;
use Illuminate\Support\Collection;
trait DrawsScrollbars
{
/**
* Render a scrollbar beside the visible items.
*
* @template T of array<int, string>|\Illuminate\Support\Collection<int, string>
*
* @param T $visible
* @return T
*/
protected function scrollbar(array|Collection $visible, int $firstVisible, int $height, int $total, int $width, string $color = 'cyan'): array|Collection
{
if ($height >= $total) {
return $visible;
}
$scrollPosition = $this->scrollPosition($firstVisible, $height, $total);
$lines = $visible instanceof Collection ? $visible->all() : $visible;
$result = array_map(fn ($line, $index) => match ($index) {
$scrollPosition => preg_replace('/.$/', $this->{$color}('┃'), $this->pad($line, $width)) ?? '',
default => preg_replace('/.$/', $this->gray('│'), $this->pad($line, $width)) ?? '',
}, array_values($lines), range(0, count($lines) - 1));
return $visible instanceof Collection ? new Collection($result) : $result; // @phpstan-ignore return.type (https://github.com/phpstan/phpstan/issues/11663)
}
/**
* Return the position where the scrollbar "handle" should be rendered.
*/
protected function scrollPosition(int $firstVisible, int $height, int $total): int
{
if ($firstVisible === 0) {
return 0;
}
$maxPosition = $total - $height;
if ($firstVisible === $maxPosition) {
return $height - 1;
}
if ($height <= 2) {
return -1;
}
$percent = $firstVisible / $maxPosition;
return (int) round($percent * ($height - 3)) + 1;
}
}
@@ -0,0 +1,44 @@
<?php
namespace Laravel\Prompts\Themes\Default\Concerns;
trait InteractsWithStrings
{
/**
* Get the length of the longest line.
*
* @param array<string> $lines
*/
protected function longest(array $lines, int $padding = 0): int
{
return max(
$this->minWidth,
count($lines) > 0 ? max(array_map(fn ($line) => mb_strwidth($this->stripEscapeSequences($line)) + $padding, $lines)) : null
);
}
/**
* Pad text ignoring ANSI escape sequences.
*/
protected function pad(string $text, int $length, string $char = ' '): string
{
$rightPadding = str_repeat($char, max(0, $length - mb_strwidth($this->stripEscapeSequences($text))));
return "{$text}{$rightPadding}";
}
/**
* Strip ANSI escape sequences from the given text.
*/
protected function stripEscapeSequences(string $text): string
{
// Strip ANSI escape sequences.
$text = preg_replace("/\e[^m]*m/", '', $text);
// Strip Symfony named style tags.
$text = preg_replace("/<(info|comment|question|error)>(.*?)<\/\\1>/", '$2', $text);
// Strip Symfony inline style tags.
return preg_replace("/<(?:(?:[fb]g|options)=[a-z,;]+)+>(.*?)<\/>/i", '$1', $text);
}
}
@@ -0,0 +1,71 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\ConfirmPrompt;
class ConfirmPromptRenderer extends Renderer
{
use Concerns\DrawsBoxes;
/**
* Render the confirm prompt.
*/
public function __invoke(ConfirmPrompt $prompt): string
{
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->truncate($prompt->label(), $prompt->terminal()->cols() - 6)
),
'cancel' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->renderOptions($prompt),
color: 'red'
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->renderOptions($prompt),
color: 'yellow',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->renderOptions($prompt),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
),
};
}
/**
* Render the confirm prompt options.
*/
protected function renderOptions(ConfirmPrompt $prompt): string
{
$length = (int) floor(($prompt->terminal()->cols() - 14) / 2);
$yes = $this->truncate($prompt->yes, $length);
$no = $this->truncate($prompt->no, $length);
if ($prompt->state === 'cancel') {
return $this->dim($prompt->confirmed
? "{$this->strikethrough($yes)} / ○ {$this->strikethrough($no)}"
: "{$this->strikethrough($yes)} / ● {$this->strikethrough($no)}");
}
return $prompt->confirmed
? "{$this->green('●')} {$yes} {$this->dim('/ ○ '.$no)}"
: "{$this->dim('○ '.$yes.' /')} {$this->green('●')} {$no}";
}
}
@@ -0,0 +1,178 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\MultiSearchPrompt;
use Laravel\Prompts\Themes\Contracts\Scrolling;
class MultiSearchPromptRenderer extends Renderer implements Scrolling
{
use Concerns\DrawsBoxes;
use Concerns\DrawsScrollbars;
/**
* Render the suggest prompt.
*/
public function __invoke(MultiSearchPrompt $prompt): string
{
$maxWidth = $prompt->terminal()->cols() - 6;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->renderSelectedOptions($prompt),
),
'cancel' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->strikethrough($this->dim($this->truncate($prompt->searchValue() ?: $prompt->placeholder, $maxWidth))),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$prompt->valueWithCursor($maxWidth),
$this->renderOptions($prompt),
color: 'yellow',
info: $this->getInfoText($prompt),
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
'searching' => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->valueWithCursorAndSearchIcon($prompt, $maxWidth),
$this->renderOptions($prompt),
info: $this->getInfoText($prompt),
)
->hint($prompt->hint),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$prompt->valueWithCursor($maxWidth),
$this->renderOptions($prompt),
info: $this->getInfoText($prompt),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
)
->spaceForDropdown($prompt)
};
}
/**
* Render the value with the cursor and a search icon.
*/
protected function valueWithCursorAndSearchIcon(MultiSearchPrompt $prompt, int $maxWidth): string
{
return preg_replace(
'/\s$/',
$this->cyan('…'),
$this->pad($prompt->valueWithCursor($maxWidth - 1).' ', min($this->longest($prompt->matches(), padding: 2), $maxWidth))
);
}
/**
* Render a spacer to prevent jumping when the suggestions are displayed.
*/
protected function spaceForDropdown(MultiSearchPrompt $prompt): self
{
if ($prompt->searchValue() !== '') {
return $this;
}
$this->newLine(max(
0,
min($prompt->scroll, $prompt->terminal()->lines() - 7) - count($prompt->matches()),
));
if ($prompt->matches() === []) {
$this->newLine();
}
return $this;
}
/**
* Render the options.
*/
protected function renderOptions(MultiSearchPrompt $prompt): string
{
if ($prompt->searchValue() !== '' && empty($prompt->matches())) {
return $this->gray(' '.($prompt->state === 'searching' ? 'Searching...' : 'No results.'));
}
return implode(PHP_EOL, $this->scrollbar(
array_map(function ($label, $key) use ($prompt) {
$label = $this->truncate($label, $prompt->terminal()->cols() - 12);
$index = array_search($key, array_keys($prompt->matches()));
$active = $index === $prompt->highlighted;
$selected = $prompt->isList()
? in_array($label, $prompt->value())
: in_array($key, $prompt->value());
return match (true) {
$active && $selected => "{$this->cyan(' ◼')} {$label} ",
$active => "{$this->cyan('')}{$label} ",
$selected => " {$this->cyan('◼')} {$this->dim($label)} ",
default => " {$this->dim('◻')} {$this->dim($label)} ",
};
}, $prompt->visible(), array_keys($prompt->visible())),
$prompt->firstVisible,
$prompt->scroll,
count($prompt->matches()),
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6)
));
}
/**
* Render the selected options.
*/
protected function renderSelectedOptions(MultiSearchPrompt $prompt): string
{
if (count($prompt->labels()) === 0) {
return $this->gray('None');
}
return implode("\n", array_map(
fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 6),
$prompt->labels()
));
}
/**
* Render the info text.
*/
protected function getInfoText(MultiSearchPrompt $prompt): string
{
$info = count($prompt->value()).' selected';
$hiddenCount = count($prompt->value()) - count(array_filter(
$prompt->matches(),
fn ($label, $key) => in_array($prompt->isList() ? $label : $key, $prompt->value()),
ARRAY_FILTER_USE_BOTH
));
if ($hiddenCount > 0) {
$info .= " ($hiddenCount hidden)";
}
return $info;
}
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int
{
return 7;
}
}
@@ -0,0 +1,120 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\MultiSelectPrompt;
use Laravel\Prompts\Themes\Contracts\Scrolling;
class MultiSelectPromptRenderer extends Renderer implements Scrolling
{
use Concerns\DrawsBoxes;
use Concerns\DrawsScrollbars;
/**
* Render the multiselect prompt.
*/
public function __invoke(MultiSelectPrompt $prompt): string
{
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->renderSelectedOptions($prompt)
),
'cancel' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->renderOptions($prompt),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->renderOptions($prompt),
color: 'yellow',
info: count($prompt->options) > $prompt->scroll ? (count($prompt->value()).' selected') : '',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->renderOptions($prompt),
info: count($prompt->options) > $prompt->scroll ? (count($prompt->value()).' selected') : '',
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
),
};
}
/**
* Render the options.
*/
protected function renderOptions(MultiSelectPrompt $prompt): string
{
return implode(PHP_EOL, $this->scrollbar(
array_values(array_map(function ($label, $key) use ($prompt) {
$label = $this->truncate($label, $prompt->terminal()->cols() - 12);
$index = array_search($key, array_keys($prompt->options));
$active = $index === $prompt->highlighted;
if (array_is_list($prompt->options)) {
$value = $prompt->options[$index];
} else {
$value = array_keys($prompt->options)[$index];
}
$selected = in_array($value, $prompt->value());
if ($prompt->state === 'cancel') {
return $this->dim(match (true) {
$active && $selected => "{$this->strikethrough($label)} ",
$active => "{$this->strikethrough($label)} ",
$selected => "{$this->strikethrough($label)} ",
default => "{$this->strikethrough($label)} ",
});
}
return match (true) {
$active && $selected => "{$this->cyan(' ◼')} {$label} ",
$active => "{$this->cyan('')}{$label} ",
$selected => " {$this->cyan('◼')} {$this->dim($label)} ",
default => " {$this->dim('◻')} {$this->dim($label)} ",
};
}, $visible = $prompt->visible(), array_keys($visible))),
$prompt->firstVisible,
$prompt->scroll,
count($prompt->options),
min($this->longest($prompt->options, padding: 6), $prompt->terminal()->cols() - 6),
$prompt->state === 'cancel' ? 'dim' : 'cyan'
));
}
/**
* Render the selected options.
*/
protected function renderSelectedOptions(MultiSelectPrompt $prompt): string
{
if (count($prompt->labels()) === 0) {
return $this->gray('None');
}
return implode("\n", array_map(
fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 6),
$prompt->labels()
));
}
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int
{
return 5;
}
}
@@ -0,0 +1,65 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\Note;
class NoteRenderer extends Renderer
{
/**
* Render the note.
*/
public function __invoke(Note $note): string
{
$lines = explode(PHP_EOL, $note->message);
switch ($note->type) {
case 'intro':
case 'outro':
$lines = array_map(fn ($line) => " {$line} ", $lines);
$longest = max(array_map(fn ($line) => strlen($line), $lines));
foreach ($lines as $line) {
$line = str_pad($line, $longest, ' ');
$this->line(" {$this->bgCyan($this->black($line))}");
}
return $this;
case 'warning':
foreach ($lines as $line) {
$this->line($this->yellow(" {$line}"));
}
return $this;
case 'error':
foreach ($lines as $line) {
$this->line($this->red(" {$line}"));
}
return $this;
case 'alert':
foreach ($lines as $line) {
$this->line(" {$this->bgRed($this->white(" {$line} "))}");
}
return $this;
case 'info':
foreach ($lines as $line) {
$this->line($this->green(" {$line}"));
}
return $this;
default:
foreach ($lines as $line) {
$this->line(" {$line}");
}
return $this;
}
}
}
@@ -0,0 +1,53 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\PasswordPrompt;
class PasswordPromptRenderer extends Renderer
{
use Concerns\DrawsBoxes;
/**
* Render the password prompt.
*/
public function __invoke(PasswordPrompt $prompt): string
{
$maxWidth = $prompt->terminal()->cols() - 6;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($prompt->label),
$this->truncate($prompt->masked(), $maxWidth),
),
'cancel' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->strikethrough($this->dim($this->truncate($prompt->masked() ?: $prompt->placeholder, $maxWidth))),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$prompt->maskedWithCursor($maxWidth),
color: 'yellow',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$prompt->maskedWithCursor($maxWidth),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
),
};
}
}
@@ -0,0 +1,26 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\PausePrompt;
class PausePromptRenderer extends Renderer
{
use Concerns\DrawsBoxes;
/**
* Render the pause prompt.
*/
public function __invoke(PausePrompt $prompt): string
{
$lines = explode(PHP_EOL, $prompt->message);
$color = $prompt->state === 'submit' ? 'green' : 'gray';
foreach ($lines as $line) {
$this->line(" {$this->{$color}($line)}");
}
return $this;
}
}
@@ -0,0 +1,71 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\Progress;
class ProgressRenderer extends Renderer
{
use Concerns\DrawsBoxes;
/**
* The character to use for the progress bar.
*/
protected string $barCharacter = '█';
/**
* Render the progress bar.
*
* @param Progress<int|iterable<mixed>> $progress
*/
public function __invoke(Progress $progress): string
{
$filled = str_repeat($this->barCharacter, (int) ceil($progress->percentage() * min($this->minWidth, $progress->terminal()->cols() - 6)));
return match ($progress->state) {
'submit' => $this
->box(
$this->dim($this->truncate($progress->label, $progress->terminal()->cols() - 6)),
$this->dim($filled),
info: $this->fractionCompleted($progress),
),
'error' => $this
->box(
$this->truncate($progress->label, $progress->terminal()->cols() - 6),
$this->dim($filled),
color: 'red',
info: $this->fractionCompleted($progress),
),
'cancel' => $this
->box(
$this->truncate($progress->label, $progress->terminal()->cols() - 6),
$this->dim($filled),
color: 'red',
info: $this->fractionCompleted($progress),
)
->error($progress->cancelMessage),
default => $this
->box(
$this->cyan($this->truncate($progress->label, $progress->terminal()->cols() - 6)),
$this->dim($filled),
info: $this->fractionCompleted($progress),
)
->when(
$progress->hint,
fn () => $this->hint($progress->hint),
fn () => $this->newLine() // Space for errors
)
};
}
/**
* @param Progress<int|iterable<mixed>> $progress
*/
protected function fractionCompleted(Progress $progress): string
{
return number_format($progress->progress).' / '.number_format($progress->total);
}
}
+102
View File
@@ -0,0 +1,102 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\Concerns\Colors;
use Laravel\Prompts\Concerns\Truncation;
use Laravel\Prompts\Prompt;
abstract class Renderer
{
use Colors;
use Truncation;
/**
* The output to be rendered.
*/
protected string $output = '';
/**
* Create a new renderer instance.
*/
public function __construct(protected Prompt $prompt)
{
//
}
/**
* Render a line of output.
*/
protected function line(string $message): self
{
$this->output .= $message.PHP_EOL;
return $this;
}
/**
* Render a new line.
*/
protected function newLine(int $count = 1): self
{
$this->output .= str_repeat(PHP_EOL, $count);
return $this;
}
/**
* Render a warning message.
*/
protected function warning(string $message): self
{
return $this->line($this->yellow("{$message}"));
}
/**
* Render an error message.
*/
protected function error(string $message): self
{
return $this->line($this->red("{$message}"));
}
/**
* Render an hint message.
*/
protected function hint(string $message): self
{
if ($message === '') {
return $this;
}
$message = $this->truncate($message, $this->prompt->terminal()->cols() - 6);
return $this->line($this->gray(" {$message}"));
}
/**
* Apply the callback if the given "value" is truthy.
*
* @return $this
*/
protected function when(mixed $value, callable $callback, ?callable $default = null): self
{
if ($value) {
$callback($this);
} elseif ($default) {
$default($this);
}
return $this;
}
/**
* Render the output with a blank line above and below.
*/
public function __toString()
{
return str_repeat(PHP_EOL, max(2 - $this->prompt->newLinesWritten(), 0))
.$this->output
.(in_array($this->prompt->state, ['submit', 'cancel']) ? PHP_EOL : '');
}
}
@@ -0,0 +1,133 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\SearchPrompt;
use Laravel\Prompts\Themes\Contracts\Scrolling;
class SearchPromptRenderer extends Renderer implements Scrolling
{
use Concerns\DrawsBoxes;
use Concerns\DrawsScrollbars;
/**
* Render the suggest prompt.
*/
public function __invoke(SearchPrompt $prompt): string
{
$maxWidth = $prompt->terminal()->cols() - 6;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->truncate($prompt->label(), $maxWidth),
),
'cancel' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->strikethrough($this->dim($this->truncate($prompt->searchValue() ?: $prompt->placeholder, $maxWidth))),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$prompt->valueWithCursor($maxWidth),
$this->renderOptions($prompt),
color: 'yellow',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
'searching' => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->valueWithCursorAndSearchIcon($prompt, $maxWidth),
$this->renderOptions($prompt),
)
->hint($prompt->hint),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$prompt->valueWithCursor($maxWidth),
$this->renderOptions($prompt),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
)
->spaceForDropdown($prompt)
};
}
/**
* Render the value with the cursor and a search icon.
*/
protected function valueWithCursorAndSearchIcon(SearchPrompt $prompt, int $maxWidth): string
{
return preg_replace(
'/\s$/',
$this->cyan('…'),
$this->pad($prompt->valueWithCursor($maxWidth - 1).' ', min($this->longest($prompt->matches(), padding: 2), $maxWidth))
);
}
/**
* Render a spacer to prevent jumping when the suggestions are displayed.
*/
protected function spaceForDropdown(SearchPrompt $prompt): self
{
if ($prompt->searchValue() !== '') {
return $this;
}
$this->newLine(max(
0,
min($prompt->scroll, $prompt->terminal()->lines() - 7) - count($prompt->matches()),
));
if ($prompt->matches() === []) {
$this->newLine();
}
return $this;
}
/**
* Render the options.
*/
protected function renderOptions(SearchPrompt $prompt): string
{
if ($prompt->searchValue() !== '' && empty($prompt->matches())) {
return $this->gray(' '.($prompt->state === 'searching' ? 'Searching...' : 'No results.'));
}
return implode(PHP_EOL, $this->scrollbar(
array_values(array_map(function ($label, $key) use ($prompt) {
$label = $this->truncate($label, $prompt->terminal()->cols() - 10);
$index = array_search($key, array_keys($prompt->matches()));
return $prompt->highlighted === $index
? "{$this->cyan('')} {$label} "
: " {$this->dim($label)} ";
}, $visible = $prompt->visible(), array_keys($visible))),
$prompt->firstVisible,
$prompt->scroll,
count($prompt->matches()),
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6)
));
}
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int
{
return 7;
}
}
@@ -0,0 +1,93 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\SelectPrompt;
use Laravel\Prompts\Themes\Contracts\Scrolling;
class SelectPromptRenderer extends Renderer implements Scrolling
{
use Concerns\DrawsBoxes;
use Concerns\DrawsScrollbars;
/**
* Render the select prompt.
*/
public function __invoke(SelectPrompt $prompt): string
{
$maxWidth = $prompt->terminal()->cols() - 6;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->truncate($prompt->label(), $maxWidth),
),
'cancel' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->renderOptions($prompt),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->renderOptions($prompt),
color: 'yellow',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->renderOptions($prompt),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
),
};
}
/**
* Render the options.
*/
protected function renderOptions(SelectPrompt $prompt): string
{
return implode(PHP_EOL, $this->scrollbar(
array_values(array_map(function ($label, $key) use ($prompt) {
$label = $this->truncate($label, $prompt->terminal()->cols() - 12);
$index = array_search($key, array_keys($prompt->options));
if ($prompt->state === 'cancel') {
return $this->dim($prompt->highlighted === $index
? "{$this->strikethrough($label)} "
: "{$this->strikethrough($label)} "
);
}
return $prompt->highlighted === $index
? "{$this->cyan('')} {$this->cyan('●')} {$label} "
: " {$this->dim('○')} {$this->dim($label)} ";
}, $visible = $prompt->visible(), array_keys($visible))),
$prompt->firstVisible,
$prompt->scroll,
count($prompt->options),
min($this->longest($prompt->options, padding: 6), $prompt->terminal()->cols() - 6),
$prompt->state === 'cancel' ? 'dim' : 'cyan'
));
}
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int
{
return 5;
}
}
@@ -0,0 +1,41 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\Spinner;
class SpinnerRenderer extends Renderer
{
/**
* The frames of the spinner.
*
* @var array<string>
*/
protected array $frames = ['⠂', '⠒', '⠐', '⠰', '⠠', '⠤', '⠄', '⠆'];
/**
* The frame to render when the spinner is static.
*/
protected string $staticFrame = '⠶';
/**
* The interval between frames.
*/
protected int $interval = 75;
/**
* Render the spinner.
*/
public function __invoke(Spinner $spinner): string
{
if ($spinner->static) {
return $this->line(" {$this->cyan($this->staticFrame)} {$spinner->message}");
}
$spinner->interval = $this->interval;
$frame = $this->frames[$spinner->count % count($this->frames)];
return $this->line(" {$this->cyan($frame)} {$spinner->message}");
}
}
@@ -0,0 +1,123 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\SuggestPrompt;
use Laravel\Prompts\Themes\Contracts\Scrolling;
class SuggestPromptRenderer extends Renderer implements Scrolling
{
use Concerns\DrawsBoxes;
use Concerns\DrawsScrollbars;
/**
* Render the suggest prompt.
*/
public function __invoke(SuggestPrompt $prompt): string
{
$maxWidth = $prompt->terminal()->cols() - 6;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->truncate($prompt->value(), $maxWidth),
),
'cancel' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->strikethrough($this->dim($this->truncate($prompt->value() ?: $prompt->placeholder, $maxWidth))),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->valueWithCursorAndArrow($prompt, $maxWidth),
$this->renderOptions($prompt),
color: 'yellow',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->valueWithCursorAndArrow($prompt, $maxWidth),
$this->renderOptions($prompt),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
)
->spaceForDropdown($prompt),
};
}
/**
* Render the value with the cursor and an arrow.
*/
protected function valueWithCursorAndArrow(SuggestPrompt $prompt, int $maxWidth): string
{
if ($prompt->highlighted !== null || $prompt->value() !== '' || count($prompt->matches()) === 0) {
return $prompt->valueWithCursor($maxWidth);
}
return preg_replace(
'/\s$/',
$this->cyan('⌄'),
$this->pad($prompt->valueWithCursor($maxWidth - 1).' ', min($this->longest($prompt->matches(), padding: 2), $maxWidth))
);
}
/**
* Render a spacer to prevent jumping when the suggestions are displayed.
*/
protected function spaceForDropdown(SuggestPrompt $prompt): self
{
if ($prompt->value() === '' && $prompt->highlighted === null) {
$this->newLine(min(
count($prompt->matches()),
$prompt->scroll,
$prompt->terminal()->lines() - 7
) + 1);
}
return $this;
}
/**
* Render the options.
*/
protected function renderOptions(SuggestPrompt $prompt): string
{
if (empty($prompt->matches()) || ($prompt->value() === '' && $prompt->highlighted === null)) {
return '';
}
return implode(PHP_EOL, $this->scrollbar(
array_map(function ($label, $key) use ($prompt) {
$label = $this->truncate($label, $prompt->terminal()->cols() - 12);
return $prompt->highlighted === $key
? "{$this->cyan('')} {$label} "
: " {$this->dim($label)} ";
}, $visible = $prompt->visible(), array_keys($visible)),
$prompt->firstVisible,
$prompt->scroll,
count($prompt->matches()),
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6),
$prompt->state === 'cancel' ? 'dim' : 'cyan'
));
}
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int
{
return 7;
}
}
@@ -0,0 +1,43 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\Output\BufferedConsoleOutput;
use Laravel\Prompts\Table;
use Symfony\Component\Console\Helper\Table as SymfonyTable;
use Symfony\Component\Console\Helper\TableStyle;
class TableRenderer extends Renderer
{
/**
* Render the table.
*/
public function __invoke(Table $table): string
{
$tableStyle = (new TableStyle)
->setHorizontalBorderChars('─')
->setVerticalBorderChars('│', '│')
->setCellHeaderFormat($this->dim('<fg=default>%s</>'))
->setCellRowFormat('<fg=default>%s</>');
if (empty($table->headers)) {
$tableStyle->setCrossingChars('┼', '', '', '', '┤', '┘</>', '┴', '└', '├', '<fg=gray>┌', '┬', '┐');
} else {
$tableStyle->setCrossingChars('┼', '<fg=gray>┌', '┬', '┐', '┤', '┘</>', '┴', '└', '├');
}
$buffered = new BufferedConsoleOutput;
(new SymfonyTable($buffered))
->setHeaders($table->headers)
->setRows($table->rows)
->setStyle($tableStyle)
->render();
foreach (explode(PHP_EOL, trim($buffered->content(), PHP_EOL)) as $line) {
$this->line(' '.$line);
}
return $this;
}
}
@@ -0,0 +1,53 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\TextPrompt;
class TextPromptRenderer extends Renderer
{
use Concerns\DrawsBoxes;
/**
* Render the text prompt.
*/
public function __invoke(TextPrompt $prompt): string
{
$maxWidth = $prompt->terminal()->cols() - 6;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$this->truncate($prompt->value(), $maxWidth),
),
'cancel' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$this->strikethrough($this->dim($this->truncate($prompt->value() ?: $prompt->placeholder, $maxWidth))),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->terminal()->cols() - 6),
$prompt->valueWithCursor($maxWidth),
color: 'yellow',
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->terminal()->cols() - 6)),
$prompt->valueWithCursor($maxWidth),
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
)
};
}
}
@@ -0,0 +1,87 @@
<?php
namespace Laravel\Prompts\Themes\Default;
use Laravel\Prompts\TextareaPrompt;
use Laravel\Prompts\Themes\Contracts\Scrolling;
class TextareaPromptRenderer extends Renderer implements Scrolling
{
use Concerns\DrawsBoxes;
use Concerns\DrawsScrollbars;
/**
* Render the textarea prompt.
*/
public function __invoke(TextareaPrompt $prompt): string
{
$prompt->width = $prompt->terminal()->cols() - 8;
return match ($prompt->state) {
'submit' => $this
->box(
$this->dim($this->truncate($prompt->label, $prompt->width)),
implode(PHP_EOL, $prompt->lines()),
),
'cancel' => $this
->box(
$this->truncate($prompt->label, $prompt->width),
implode(PHP_EOL, array_map(fn ($line) => $this->strikethrough($this->dim($line)), $prompt->lines())),
color: 'red',
)
->error($prompt->cancelMessage),
'error' => $this
->box(
$this->truncate($prompt->label, $prompt->width),
$this->renderText($prompt),
color: 'yellow',
info: 'Ctrl+D to submit'
)
->warning($this->truncate($prompt->error, $prompt->terminal()->cols() - 5)),
default => $this
->box(
$this->cyan($this->truncate($prompt->label, $prompt->width)),
$this->renderText($prompt),
info: 'Ctrl+D to submit'
)
->when(
$prompt->hint,
fn () => $this->hint($prompt->hint),
fn () => $this->newLine() // Space for errors
)
};
}
/**
* Render the text in the prompt.
*/
protected function renderText(TextareaPrompt $prompt): string
{
$visible = $prompt->visible();
while (count($visible) < $prompt->scroll) {
$visible[] = '';
}
$longest = $this->longest($prompt->lines()) + 2;
return implode(PHP_EOL, $this->scrollbar(
$visible,
$prompt->firstVisible,
$prompt->scroll,
count($prompt->lines()),
min($longest, $prompt->width + 2),
));
}
/**
* The number of lines to reserve outside of the scrollable area.
*/
public function reservedLines(): int
{
return 5;
}
}