vendor and env first commit
This commit is contained in:
@@ -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,60 @@
|
||||
<?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 = collect(explode(PHP_EOL, $body));
|
||||
$footerLines = collect(explode(PHP_EOL, $footer))->filter();
|
||||
$width = $this->longest(
|
||||
$bodyLines
|
||||
->merge($footerLines)
|
||||
->push($title)
|
||||
->toArray()
|
||||
);
|
||||
|
||||
$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.'┐')}");
|
||||
|
||||
$bodyLines->each(function ($line) use ($width, $color) {
|
||||
$this->line("{$this->{$color}(' │')} {$this->pad($line, $width)} {$this->{$color}('│')}");
|
||||
});
|
||||
|
||||
if ($footerLines->isNotEmpty()) {
|
||||
$this->line($this->{$color}(' ├'.str_repeat('─', $width + 2).'┤'));
|
||||
|
||||
$footerLines->each(function ($line) use ($width, $color) {
|
||||
$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,55 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default\Concerns;
|
||||
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
trait DrawsScrollbars
|
||||
{
|
||||
/**
|
||||
* Render a scrollbar beside the visible items.
|
||||
*
|
||||
* @param \Illuminate\Support\Collection<int, string> $visible
|
||||
* @return \Illuminate\Support\Collection<int, string>
|
||||
*/
|
||||
protected function scrollbar(Collection $visible, int $firstVisible, int $height, int $total, int $width, string $color = 'cyan'): Collection
|
||||
{
|
||||
if ($height >= $total) {
|
||||
return $visible;
|
||||
}
|
||||
|
||||
$scrollPosition = $this->scrollPosition($firstVisible, $height, $total);
|
||||
|
||||
return $visible // @phpstan-ignore return.type
|
||||
->values()
|
||||
->map(fn ($line) => $this->pad($line, $width))
|
||||
->map(fn ($line, $index) => match ($index) {
|
||||
$scrollPosition => preg_replace('/.$/', $this->{$color}('┃'), $line),
|
||||
default => preg_replace('/.$/', $this->gray('│'), $line),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,46 @@
|
||||
<?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,
|
||||
collect($lines)
|
||||
->map(fn ($line) => mb_strwidth($this->stripEscapeSequences($line)) + $padding)
|
||||
->max()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,176 @@
|
||||
<?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 $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 12))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$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->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->matches()),
|
||||
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6)
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()) - collect($prompt->matches())
|
||||
->filter(fn ($label, $key) => in_array($prompt->isList() ? $label : $key, $prompt->value()))
|
||||
->count();
|
||||
|
||||
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,121 @@
|
||||
<?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 $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 12))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$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)} ",
|
||||
};
|
||||
})
|
||||
->values(),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->options),
|
||||
min($this->longest($prompt->options, padding: 6), $prompt->terminal()->cols() - 6),
|
||||
$prompt->state === 'cancel' ? 'dim' : 'cyan'
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,56 @@
|
||||
<?php
|
||||
|
||||
namespace Laravel\Prompts\Themes\Default;
|
||||
|
||||
use Laravel\Prompts\Note;
|
||||
|
||||
class NoteRenderer extends Renderer
|
||||
{
|
||||
/**
|
||||
* Render the note.
|
||||
*/
|
||||
public function __invoke(Note $note): string
|
||||
{
|
||||
$lines = collect(explode(PHP_EOL, $note->message));
|
||||
|
||||
switch ($note->type) {
|
||||
case 'intro':
|
||||
case 'outro':
|
||||
$lines = $lines->map(fn ($line) => " {$line} ");
|
||||
$longest = $lines->map(fn ($line) => strlen($line))->max();
|
||||
|
||||
$lines
|
||||
->each(function ($line) use ($longest) {
|
||||
$line = str_pad($line, $longest, ' ');
|
||||
$this->line(" {$this->bgCyan($this->black($line))}");
|
||||
});
|
||||
|
||||
return $this;
|
||||
|
||||
case 'warning':
|
||||
$lines->each(fn ($line) => $this->line($this->yellow(" {$line}")));
|
||||
|
||||
return $this;
|
||||
|
||||
case 'error':
|
||||
$lines->each(fn ($line) => $this->line($this->red(" {$line}")));
|
||||
|
||||
return $this;
|
||||
|
||||
case 'alert':
|
||||
$lines->each(fn ($line) => $this->line(" {$this->bgRed($this->white(" {$line} "))}"));
|
||||
|
||||
return $this;
|
||||
|
||||
case 'info':
|
||||
$lines->each(fn ($line) => $this->line($this->green(" {$line}")));
|
||||
|
||||
return $this;
|
||||
|
||||
default:
|
||||
$lines->each(fn ($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,25 @@
|
||||
<?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
|
||||
{
|
||||
match ($prompt->state) {
|
||||
'submit' => collect(explode(PHP_EOL, $prompt->message))
|
||||
->each(fn ($line) => $this->line($this->gray(" {$line}"))),
|
||||
default => collect(explode(PHP_EOL, $prompt->message))
|
||||
->each(fn ($line) => $this->line($this->green(" {$line}")))
|
||||
};
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?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: $progress->progress.'/'.$progress->total,
|
||||
),
|
||||
|
||||
'error' => $this
|
||||
->box(
|
||||
$this->truncate($progress->label, $progress->terminal()->cols() - 6),
|
||||
$this->dim($filled),
|
||||
color: 'red',
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($progress->label, $progress->terminal()->cols() - 6),
|
||||
$this->dim($filled),
|
||||
color: 'red',
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
)
|
||||
->error($progress->cancelMessage),
|
||||
|
||||
default => $this
|
||||
->box(
|
||||
$this->cyan($this->truncate($progress->label, $progress->terminal()->cols() - 6)),
|
||||
$this->dim($filled),
|
||||
info: $progress->progress.'/'.$progress->total,
|
||||
)
|
||||
->when(
|
||||
$progress->hint,
|
||||
fn () => $this->hint($progress->hint),
|
||||
fn () => $this->newLine() // Space for errors
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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,134 @@
|
||||
<?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 $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 10))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$index = array_search($key, array_keys($prompt->matches()));
|
||||
|
||||
return $prompt->highlighted === $index
|
||||
? "{$this->cyan('›')} {$label} "
|
||||
: " {$this->dim($label)} ";
|
||||
})
|
||||
->values(),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->matches()),
|
||||
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6)
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?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 $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 12))
|
||||
->map(function ($label, $key) use ($prompt) {
|
||||
$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)} ";
|
||||
})
|
||||
->values(),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->options),
|
||||
min($this->longest($prompt->options, padding: 6), $prompt->terminal()->cols() - 6),
|
||||
$prompt->state === 'cancel' ? 'dim' : 'cyan'
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,122 @@
|
||||
<?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 $this->scrollbar(
|
||||
collect($prompt->visible())
|
||||
->map(fn ($label) => $this->truncate($label, $prompt->terminal()->cols() - 10))
|
||||
->map(fn ($label, $key) => $prompt->highlighted === $key
|
||||
? "{$this->cyan('›')} {$label} "
|
||||
: " {$this->dim($label)} "
|
||||
),
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->matches()),
|
||||
min($this->longest($prompt->matches(), padding: 4), $prompt->terminal()->cols() - 6),
|
||||
$prompt->state === 'cancel' ? 'dim' : 'cyan'
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 7;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?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();
|
||||
|
||||
collect(explode(PHP_EOL, trim($buffered->content(), PHP_EOL)))
|
||||
->each(fn ($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)),
|
||||
collect($prompt->lines())->implode(PHP_EOL),
|
||||
),
|
||||
|
||||
'cancel' => $this
|
||||
->box(
|
||||
$this->truncate($prompt->label, $prompt->width),
|
||||
collect($prompt->lines())->map(fn ($line) => $this->strikethrough($this->dim($line)))->implode(PHP_EOL),
|
||||
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 = collect($prompt->visible());
|
||||
|
||||
while ($visible->count() < $prompt->scroll) {
|
||||
$visible->push('');
|
||||
}
|
||||
|
||||
$longest = $this->longest($prompt->lines()) + 2;
|
||||
|
||||
return $this->scrollbar(
|
||||
$visible,
|
||||
$prompt->firstVisible,
|
||||
$prompt->scroll,
|
||||
count($prompt->lines()),
|
||||
min($longest, $prompt->width + 2),
|
||||
)->implode(PHP_EOL);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of lines to reserve outside of the scrollable area.
|
||||
*/
|
||||
public function reservedLines(): int
|
||||
{
|
||||
return 5;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user