diff --git a/src/Concerns/Cursor.php b/src/Concerns/Cursor.php index 1a2f1e17..0dcb5f10 100644 --- a/src/Concerns/Cursor.php +++ b/src/Concerns/Cursor.php @@ -14,7 +14,7 @@ trait Cursor */ public function hideCursor(): void { - static::writeDirectly("\e[?25l"); + static::writer()->writeDirectly("\e[?25l"); static::$cursorHidden = true; } @@ -24,7 +24,7 @@ public function hideCursor(): void */ public function showCursor(): void { - static::writeDirectly("\e[?25h"); + static::writer()->writeDirectly("\e[?25h"); static::$cursorHidden = false; } @@ -58,6 +58,6 @@ public function moveCursor(int $x, int $y = 0): void $sequence .= "\e[{$y}B"; // Down } - static::writeDirectly($sequence); + static::writer()->writeDirectly($sequence); } } diff --git a/src/Concerns/Erase.php b/src/Concerns/Erase.php index 943dba6e..7de49fa2 100644 --- a/src/Concerns/Erase.php +++ b/src/Concerns/Erase.php @@ -18,7 +18,7 @@ public function eraseLines(int $count): void $clear .= "\e[G"; } - static::writeDirectly($clear); + static::writer()->writeDirectly($clear); } /** @@ -26,6 +26,6 @@ public function eraseLines(int $count): void */ public function eraseDown(): void { - static::writeDirectly("\e[J"); + static::writer()->writeDirectly("\e[J"); } } diff --git a/src/Concerns/FakesInputOutput.php b/src/Concerns/FakesInputOutput.php index 2df265f1..29745cc8 100644 --- a/src/Concerns/FakesInputOutput.php +++ b/src/Concerns/FakesInputOutput.php @@ -74,11 +74,12 @@ public static function assertStrippedOutputDoesntContain(string $string): void */ public static function content(): string { - if (! static::output() instanceof BufferedConsoleOutput) { + $output = static::writer()->getOutput(); + if (! $output instanceof BufferedConsoleOutput) { throw new RuntimeException('Prompt must be faked before accessing content.'); } - return static::output()->content(); + return $output->content(); } /** diff --git a/src/Note.php b/src/Note.php index b2239673..e01226d7 100644 --- a/src/Note.php +++ b/src/Note.php @@ -29,7 +29,7 @@ public function prompt(): bool $this->state = 'submit'; - static::output()->write($this->renderTheme()); + static::writer()->write($this->renderTheme()); return true; } diff --git a/src/Output/BufferedConsoleOutput.php b/src/Output/BufferedConsoleOutput.php index b5e725b4..68fdb353 100644 --- a/src/Output/BufferedConsoleOutput.php +++ b/src/Output/BufferedConsoleOutput.php @@ -2,6 +2,8 @@ namespace Laravel\Prompts\Output; +use Symfony\Component\Console\Output\ConsoleOutput; + class BufferedConsoleOutput extends ConsoleOutput { /** diff --git a/src/Output/ConsoleOutput.php b/src/Output/PromptWriter.php similarity index 56% rename from src/Output/ConsoleOutput.php rename to src/Output/PromptWriter.php index 60381d62..23963c0b 100644 --- a/src/Output/ConsoleOutput.php +++ b/src/Output/PromptWriter.php @@ -2,15 +2,20 @@ namespace Laravel\Prompts\Output; -use Symfony\Component\Console\Output\ConsoleOutput as SymfonyConsoleOutput; +use Symfony\Component\Console\Output\OutputInterface; -class ConsoleOutput extends SymfonyConsoleOutput +class PromptWriter { /** * How many new lines were written by the last output. */ protected int $newLinesWritten = 1; + public function __construct( + private OutputInterface $output + ) { + } + /** * How many new lines were written by the last output. */ @@ -22,15 +27,15 @@ public function newLinesWritten(): int /** * Write the output and capture the number of trailing new lines. */ - protected function doWrite(string $message, bool $newline): void + public function write(string $message, bool $newline = false): void { - parent::doWrite($message, $newline); + $this->output->write($message, $newline); if ($newline) { $message .= \PHP_EOL; } - $trailingNewLines = strlen($message) - strlen(rtrim($message, \PHP_EOL)); + $trailingNewLines = \strlen($message) - \strlen(rtrim($message, \PHP_EOL)); if (trim($message) === '') { $this->newLinesWritten += $trailingNewLines; @@ -42,8 +47,13 @@ protected function doWrite(string $message, bool $newline): void /** * Write output directly, bypassing newline capture. */ - public function writeDirectly(string $message): void + public function writeDirectly(string $message, bool $newline = false): void + { + $this->output->write($message, $newline); + } + + public function getOutput(): OutputInterface { - parent::doWrite($message, false); + return $this->output; } } diff --git a/src/Prompt.php b/src/Prompt.php index 1f50b102..dfd3e872 100644 --- a/src/Prompt.php +++ b/src/Prompt.php @@ -3,8 +3,10 @@ namespace Laravel\Prompts; use Closure; -use Laravel\Prompts\Output\ConsoleOutput; +use Laravel\Prompts\Output\PromptWriter; use RuntimeException; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\Console\Output\ConsoleOutputInterface; use Symfony\Component\Console\Output\OutputInterface; use Throwable; @@ -65,9 +67,9 @@ abstract class Prompt protected static ?Closure $validateUsing; /** - * The output instance. + * The writer instance. */ - protected static OutputInterface $output; + protected static PromptWriter $writer; /** * The terminal instance. @@ -91,7 +93,7 @@ public function prompt(): mixed return $this->fallback(); } - static::$interactive ??= stream_isatty(STDIN); + static::$interactive ??= stream_isatty(STDIN) && (stream_isatty(STDOUT) || stream_isatty(STDERR)); if (! static::$interactive) { return $this->default(); @@ -102,7 +104,7 @@ public function prompt(): mixed try { static::terminal()->setTty('-icanon -isig -echo'); } catch (Throwable $e) { - static::output()->writeln("<comment>{$e->getMessage()}</comment>"); + static::writer()->write("<comment>{$e->getMessage()}</comment>", true); static::fallbackWhen(true); return $this->fallback(); @@ -154,9 +156,7 @@ public function newLinesWritten(): int */ protected function capturePreviousNewLines(): void { - $this->newLinesWritten = method_exists(static::output(), 'newLinesWritten') - ? static::output()->newLinesWritten() - : 1; + $this->newLinesWritten = static::writer()->newLinesWritten(); } /** @@ -164,27 +164,22 @@ protected function capturePreviousNewLines(): void */ public static function setOutput(OutputInterface $output): void { - self::$output = $output; + if ($output instanceof ConsoleOutputInterface && stream_isatty(STDERR) && ! stream_isatty(STDOUT)) { + $output = $output->getErrorOutput(); + } + self::$writer = new PromptWriter($output); } /** - * Get the current output instance. + * Get the prompt writer. */ - protected static function output(): OutputInterface + protected static function writer(): PromptWriter { - return self::$output ??= new ConsoleOutput(); - } + if (! isset(self::$writer)) { + self::setOutput(new ConsoleOutput()); + } - /** - * Write output directly, bypassing newline capture. - */ - protected static function writeDirectly(string $message): void - { - match (true) { - method_exists(static::output(), 'writeDirectly') => static::output()->writeDirectly($message), - method_exists(static::output(), 'getOutput') => static::output()->getOutput()->write($message), - default => static::output()->write($message), - }; + return self::$writer; } /** @@ -215,7 +210,7 @@ protected function render(): void } if ($this->state === 'initial') { - static::output()->write($frame); + static::writer()->write($frame); $this->state = 'active'; $this->prevFrame = $frame; @@ -228,7 +223,7 @@ protected function render(): void // Ensure that the full frame is buffered so subsequent output can see how many trailing newlines were written. if ($this->state === 'submit') { $this->eraseDown(); - static::output()->write($frame); + static::writer()->write($frame); $this->prevFrame = ''; @@ -242,7 +237,7 @@ protected function render(): void $this->moveCursor(0, $diffLine); $this->eraseLines(1); $lines = explode(PHP_EOL, $frame); - static::output()->write($lines[$diffLine]); + static::writer()->write($lines[$diffLine]); $this->moveCursor(0, count($lines) - $diffLine - 1); } elseif (count($diff) > 1) { // Re-render everything past the first change $diffLine = $diff[0]; @@ -250,7 +245,7 @@ protected function render(): void $this->eraseDown(); $lines = explode(PHP_EOL, $frame); $newLines = array_slice($lines, $diffLine); - static::output()->write(implode(PHP_EOL, $newLines)); + static::writer()->write(implode(PHP_EOL, $newLines)); } $this->prevFrame = $frame; diff --git a/src/Table.php b/src/Table.php index a7de3700..40b6538a 100644 --- a/src/Table.php +++ b/src/Table.php @@ -56,7 +56,7 @@ public function prompt(): bool $this->state = 'submit'; - static::output()->write($this->renderTheme()); + static::writer()->write($this->renderTheme()); return true; }