refactor: susun semula struktur folder — Laravel source ke src/
This commit is contained in:
60
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsBoxes.php
vendored
Normal file
60
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsBoxes.php
vendored
Normal file
@@ -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 = 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}('│')}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($info) {
|
||||
$info = $this->truncate($info, $width - 1);
|
||||
}
|
||||
|
||||
$this->line($this->{$color}(' └'.str_repeat(
|
||||
'─', $info ? ($width - mb_strwidth($this->stripEscapeSequences($info))) : ($width + 2)
|
||||
).($info ? " {$info} " : '').'┘'));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
58
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsScrollbars.php
vendored
Normal file
58
vendor/laravel/prompts/src/Themes/Default/Concerns/DrawsScrollbars.php
vendored
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
260
vendor/laravel/prompts/src/Themes/Default/Concerns/InteractsWithStrings.php
vendored
Normal file
260
vendor/laravel/prompts/src/Themes/Default/Concerns/InteractsWithStrings.php
vendored
Normal file
@@ -0,0 +1,260 @@
|
||||
<?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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-byte version of wordwrap.
|
||||
*
|
||||
* @param non-empty-string $break
|
||||
*/
|
||||
protected function mbWordwrap(
|
||||
string $string,
|
||||
int $width = 75,
|
||||
string $break = "\n",
|
||||
bool $cut_long_words = false
|
||||
): string {
|
||||
$lines = explode($break, $string);
|
||||
$result = [];
|
||||
|
||||
foreach ($lines as $originalLine) {
|
||||
if (mb_strwidth($originalLine) <= $width) {
|
||||
$result[] = $originalLine;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$words = explode(' ', $originalLine);
|
||||
$line = null;
|
||||
$lineWidth = 0;
|
||||
|
||||
if ($cut_long_words) {
|
||||
foreach ($words as $index => $word) {
|
||||
$characters = mb_str_split($word);
|
||||
$strings = [];
|
||||
$str = '';
|
||||
|
||||
foreach ($characters as $character) {
|
||||
$tmp = $str.$character;
|
||||
|
||||
if (mb_strwidth($tmp) > $width) {
|
||||
$strings[] = $str;
|
||||
$str = $character;
|
||||
} else {
|
||||
$str = $tmp;
|
||||
}
|
||||
}
|
||||
|
||||
if ($str !== '') {
|
||||
$strings[] = $str;
|
||||
}
|
||||
|
||||
$words[$index] = implode(' ', $strings);
|
||||
}
|
||||
|
||||
$words = explode(' ', implode(' ', $words));
|
||||
}
|
||||
|
||||
foreach ($words as $word) {
|
||||
$tmp = ($line === null) ? $word : $line.' '.$word;
|
||||
|
||||
// Look for zero-width joiner characters (combined emojis)
|
||||
preg_match('/\p{Cf}/u', $word, $joinerMatches);
|
||||
|
||||
$wordWidth = count($joinerMatches) > 0 ? 2 : mb_strwidth($word);
|
||||
|
||||
$lineWidth += $wordWidth;
|
||||
|
||||
if ($line !== null) {
|
||||
// Space between words
|
||||
$lineWidth += 1;
|
||||
}
|
||||
|
||||
if ($lineWidth <= $width) {
|
||||
$line = $tmp;
|
||||
} else {
|
||||
$result[] = $line;
|
||||
$line = $word;
|
||||
$lineWidth = $wordWidth;
|
||||
}
|
||||
}
|
||||
|
||||
if ($line !== '') {
|
||||
$result[] = $line;
|
||||
}
|
||||
|
||||
$line = null;
|
||||
}
|
||||
|
||||
return implode($break, $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Word wrap text while preserving ANSI escape sequences.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
protected function ansiWordwrap(string $text, int $width): array
|
||||
{
|
||||
// Parse segments and build character array with codes
|
||||
$segments = $this->parseAnsiText($text);
|
||||
$plainText = $this->stripEscapeSequences($text);
|
||||
$chars = [];
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
$segmentChars = mb_str_split($segment['text']);
|
||||
|
||||
foreach ($segmentChars as $char) {
|
||||
$chars[] = ['char' => $char, 'codes' => $segment['codes']];
|
||||
}
|
||||
}
|
||||
|
||||
// Word wrap the plain text
|
||||
$wrappedLines = $this->mbWordwrap($plainText, $width, "\n", false);
|
||||
$plainLines = explode("\n", $wrappedLines);
|
||||
|
||||
// Rebuild each wrapped line with ANSI codes
|
||||
$result = [];
|
||||
$charIndex = 0;
|
||||
|
||||
foreach ($plainLines as $plainLine) {
|
||||
$line = '';
|
||||
$lastCodes = '';
|
||||
$lineChars = mb_str_split($plainLine);
|
||||
|
||||
foreach ($lineChars as $lineChar) {
|
||||
// Find matching character in original (handling spaces removed by wordwrap)
|
||||
while ($charIndex < count($chars) && $chars[$charIndex]['char'] !== $lineChar) {
|
||||
// Skip spaces that wordwrap removed
|
||||
if ($chars[$charIndex]['char'] === ' ') {
|
||||
$charIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ($charIndex < count($chars)) {
|
||||
$codes = $chars[$charIndex]['codes'];
|
||||
|
||||
if ($codes !== $lastCodes) {
|
||||
if ($lastCodes !== '') {
|
||||
$line .= "\e[0m";
|
||||
}
|
||||
|
||||
if ($codes !== '') {
|
||||
$line .= $codes;
|
||||
}
|
||||
|
||||
$lastCodes = $codes;
|
||||
}
|
||||
|
||||
$line .= $lineChar;
|
||||
$charIndex++;
|
||||
} else {
|
||||
$line .= $lineChar;
|
||||
}
|
||||
}
|
||||
|
||||
// Close any open ANSI codes
|
||||
if ($lastCodes !== '' && ! str_ends_with($line, "\e[0m")) {
|
||||
$line .= "\e[0m";
|
||||
}
|
||||
|
||||
$result[] = $line;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse text into segments with their associated ANSI codes.
|
||||
*
|
||||
* @return array<int, array{text: string, codes: string}>
|
||||
*/
|
||||
protected function parseAnsiText(string $text): array
|
||||
{
|
||||
$segments = [];
|
||||
$currentCodes = '';
|
||||
$currentText = '';
|
||||
$i = 0;
|
||||
$textLength = strlen($text);
|
||||
|
||||
while ($i < $textLength) {
|
||||
if ($text[$i] === "\e" && ($i + 1 < $textLength) && $text[$i + 1] === '[') {
|
||||
// Save current segment if it has text
|
||||
if ($currentText !== '') {
|
||||
$segments[] = ['text' => $currentText, 'codes' => $currentCodes];
|
||||
$currentText = '';
|
||||
}
|
||||
|
||||
// Extract ANSI escape sequence
|
||||
$escapeSequence = '';
|
||||
while ($i < $textLength) {
|
||||
$escapeSequence .= $text[$i];
|
||||
$i++;
|
||||
|
||||
if (preg_match('/^\\e\\[[0-9;]*m$/', $escapeSequence)) {
|
||||
// Update current codes
|
||||
if ($escapeSequence === "\e[0m") {
|
||||
$currentCodes = '';
|
||||
} else {
|
||||
$currentCodes = $escapeSequence;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$currentText .= $text[$i];
|
||||
$i++;
|
||||
}
|
||||
|
||||
// Add final segment
|
||||
if ($currentText !== '') {
|
||||
$segments[] = ['text' => $currentText, 'codes' => $currentCodes];
|
||||
}
|
||||
|
||||
return $segments;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user