diff --git a/playground/spin.php b/playground/spin.php index 331c6563..fdc4cb6e 100644 --- a/playground/spin.php +++ b/playground/spin.php @@ -4,7 +4,7 @@ require __DIR__.'/../vendor/autoload.php'; -$result = spin( +$result1 = spin( function () { sleep(4); @@ -13,8 +13,28 @@ function () { 'Installing dependencies...', ); +$result2 = spin( + function () { + sleep(4); + + return 'A-OK'; + }, + 'Checking system...', + 'System looks good!', +); + +$result3 = spin( + function () { + sleep(4); + + return '8.2'; + }, + 'Detecting PHP Version...', + fn ($result) => "PHP Version: {$result}", +); + echo PHP_EOL; -var_dump($result); +var_dump($result1, $result2, $result3); echo str_repeat(PHP_EOL, 6); diff --git a/src/Spinner.php b/src/Spinner.php index fce1facd..06f59294 100644 --- a/src/Spinner.php +++ b/src/Spinner.php @@ -27,12 +27,26 @@ class Spinner extends Prompt */ protected int $pid; + /** + * The final message to display. + */ + public string $finalMessage = ''; + + /** + * A callback to generate the final message. + * + * @var string|\Closure(mixed): string + */ + protected $finalMessageHandler; + /** * Create a new Spinner instance. + * + * @param string|\Closure(mixed): string $finalMessageHandler */ - public function __construct(public string $message = '') + public function __construct(public string $message = '', string|Closure $finalMessageHandler = '') { - // + $this->finalMessageHandler = $finalMessageHandler; } /** @@ -72,6 +86,8 @@ public function spin(Closure $callback): mixed } else { $result = $callback(); + $this->finalMessage = $this->getFinalMessage($result); + $this->resetTerminal($originalAsync); return $result; @@ -83,6 +99,22 @@ public function spin(Closure $callback): mixed } } + /** + * Get the final message to display. + */ + protected function getFinalMessage(mixed $result): string + { + if ($this->finalMessageHandler === '') { + return ''; + } + + if (is_callable($this->finalMessageHandler)) { + return ($this->finalMessageHandler)($result) ?? ''; + } + + return $this->finalMessageHandler; + } + /** * Reset the terminal. */ @@ -111,6 +143,8 @@ protected function renderStatically(Closure $callback): mixed $this->render(); $result = $callback(); + + $this->finalMessage = $this->getFinalMessage($result); } finally { $this->eraseRenderedLines(); } @@ -141,9 +175,13 @@ public function value(): bool */ protected function eraseRenderedLines(): void { - $lines = explode(PHP_EOL, $this->prevFrame); - $this->moveCursor(-999, -count($lines) + 1); - $this->eraseDown(); + if ($this->finalMessage !== '') { + $this->render(); + } else { + $lines = explode(PHP_EOL, $this->prevFrame); + $this->moveCursor(-999, -count($lines) + 1); + $this->eraseDown(); + } } /** diff --git a/src/Themes/Default/SpinnerRenderer.php b/src/Themes/Default/SpinnerRenderer.php index c68aef4f..b5256257 100644 --- a/src/Themes/Default/SpinnerRenderer.php +++ b/src/Themes/Default/SpinnerRenderer.php @@ -28,6 +28,17 @@ class SpinnerRenderer extends Renderer */ public function __invoke(Spinner $spinner): string { + if ($spinner->finalMessage !== '') { + $finalMessage = wordwrap($spinner->finalMessage, $spinner->terminal()->cols() - 6); + + collect(explode(PHP_EOL, $finalMessage))->each(fn ($line) => $this->line(' '.$line)); + + // Avoid partial line indicator on re-render + $this->line(''); + + return $this; + } + if ($spinner->static) { return $this->line(" {$this->cyan($this->staticFrame)} {$spinner->message}"); } diff --git a/src/helpers.php b/src/helpers.php index 178cb22f..99231252 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -90,11 +90,12 @@ function multisearch(string $label, Closure $options, string $placeholder = '', * @template TReturn of mixed * * @param \Closure(): TReturn $callback + * @param string|\Closure(TReturn): string $finalMessage * @return TReturn */ -function spin(Closure $callback, string $message = ''): mixed +function spin(Closure $callback, string $message = '', string|Closure $finalMessage = ''): mixed { - return (new Spinner($message))->spin($callback); + return (new Spinner($message, $finalMessage))->spin($callback); } /** diff --git a/tests/Feature/SpinnerTest.php b/tests/Feature/SpinnerTest.php index 92078d64..b897b0db 100644 --- a/tests/Feature/SpinnerTest.php +++ b/tests/Feature/SpinnerTest.php @@ -17,3 +17,21 @@ Prompt::assertOutputContains('Running...'); }); + +it('renders a spinner and displays a final message', function ($finalMessageHandler, $expectedMessage) { + Prompt::fake(); + + $result = spin(function () { + usleep(1000); + + return 'result!'; + }, 'Running...', $finalMessageHandler); + + expect($result)->toBe('result!'); + + Prompt::assertOutputContains('Running...'); + Prompt::assertOutputContains($expectedMessage); +})->with([ + 'string' => ['All done!', 'All done!'], + 'closure' => [fn ($result) => "All done: {$result}", 'All done: result!'], +]);