Skip to content

Commit

Permalink
Remove terminal height requirement (#128)
Browse files Browse the repository at this point in the history
* Ensure a minimimum scroll height of 1

* Allow the terminal dimensions to be recalculated

* Remove exception for minimum terminal height

* Add additional helper methods for moving the cursor

* Only re-render within the visible terminal height
  • Loading branch information
jessarcher authored Apr 4, 2024
1 parent 3318556 commit 3b5e6b0
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 86 deletions.
16 changes: 16 additions & 0 deletions src/Concerns/Cursor.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,20 @@ public function moveCursor(int $x, int $y = 0): void

static::writeDirectly($sequence);
}

/**
* Move the cursor to the given column.
*/
public function moveCursorToColumn(int $column): void
{
static::writeDirectly("\e[{$column}G");
}

/**
* Move the cursor up by the given number of lines.
*/
public function moveCursorUp(int $lines): void
{
static::writeDirectly("\e[{$lines}A");
}
}
1 change: 1 addition & 0 deletions src/Concerns/FakesInputOutput.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static function fake(array $keys = []): void
$mock->shouldReceive('restoreTty')->byDefault();
$mock->shouldReceive('cols')->byDefault()->andReturn(80);
$mock->shouldReceive('lines')->byDefault()->andReturn(24);
$mock->shouldReceive('initDimensions')->byDefault();

foreach ($keys as $key) {
$mock->shouldReceive('read')->once()->andReturn($key);
Expand Down
2 changes: 1 addition & 1 deletion src/Concerns/Scrolling.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ protected function reduceScrollingToFitTerminal(): void
{
$reservedLines = ($renderer = $this->getRenderer()) instanceof ScrollingRenderer ? $renderer->reservedLines() : 0;

$this->scroll = min($this->scroll, $this->terminal()->lines() - $reservedLines);
$this->scroll = max(1, min($this->scroll, $this->terminal()->lines() - $reservedLines));
}

/**
Expand Down
71 changes: 9 additions & 62 deletions src/Prompt.php
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ public static function validateUsing(Closure $callback): void
*/
protected function render(): void
{
$this->terminal()->initDimensions();

$frame = $this->renderTheme();

if ($frame === $this->prevFrame) {
Expand All @@ -223,35 +225,14 @@ protected function render(): void
return;
}

$this->resetCursorPosition();

// 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);
$terminalHeight = $this->terminal()->lines();
$previousFrameHeight = count(explode(PHP_EOL, $this->prevFrame));
$renderableLines = array_slice(explode(PHP_EOL, $frame), abs(min(0, $terminalHeight - $previousFrameHeight)));

$this->prevFrame = '';

return;
}

$diff = $this->diffLines($this->prevFrame, $frame);

if (count($diff) === 1) { // Update the single line that changed.
$diffLine = $diff[0];
$this->moveCursor(0, $diffLine);
$this->eraseLines(1);
$lines = explode(PHP_EOL, $frame);
static::output()->write($lines[$diffLine]);
$this->moveCursor(0, count($lines) - $diffLine - 1);
} elseif (count($diff) > 1) { // Re-render everything past the first change
$diffLine = $diff[0];
$this->moveCursor(0, $diffLine);
$this->eraseDown();
$lines = explode(PHP_EOL, $frame);
$newLines = array_slice($lines, $diffLine);
static::output()->write(implode(PHP_EOL, $newLines));
}
$this->moveCursorToColumn(1);
$this->moveCursorUp(min($terminalHeight, $previousFrameHeight) - 1);
$this->eraseDown();
$this->output()->write(implode(PHP_EOL, $renderableLines));

$this->prevFrame = $frame;
}
Expand All @@ -268,40 +249,6 @@ protected function submit(): void
}
}

/**
* Reset the cursor position to the beginning of the previous frame.
*/
private function resetCursorPosition(): void
{
$lines = count(explode(PHP_EOL, $this->prevFrame)) - 1;

$this->moveCursor(-999, $lines * -1);
}

/**
* Get the difference between two strings.
*
* @return array<int>
*/
private function diffLines(string $a, string $b): array
{
if ($a === $b) {
return [];
}

$aLines = explode(PHP_EOL, $a);
$bLines = explode(PHP_EOL, $b);
$diff = [];

for ($i = 0; $i < max(count($aLines), count($bLines)); $i++) {
if (! isset($aLines[$i]) || ! isset($bLines[$i]) || $aLines[$i] !== $bLines[$i]) {
$diff[] = $i;
}
}

return $diff;
}

/**
* Handle a key press and determine whether to continue.
*/
Expand Down
26 changes: 20 additions & 6 deletions src/Terminal.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Laravel\Prompts;

use ReflectionClass;
use RuntimeException;
use Symfony\Component\Console\Terminal as SymfonyTerminal;

Expand All @@ -13,14 +14,17 @@ class Terminal
protected ?string $initialTtyMode;

/**
* The number of columns in the terminal.
* The Symfony Terminal instance.
*/
protected int $cols;
protected SymfonyTerminal $terminal;

/**
* The number of lines in the terminal.
* Create a new Terminal instance.
*/
protected int $lines;
public function __construct()
{
$this->terminal = new SymfonyTerminal();
}

/**
* Read a line from the terminal.
Expand Down Expand Up @@ -59,15 +63,25 @@ public function restoreTty(): void
*/
public function cols(): int
{
return $this->cols ??= (new SymfonyTerminal())->getWidth();
return $this->terminal->getWidth();
}

/**
* Get the number of lines in the terminal.
*/
public function lines(): int
{
return $this->lines ??= (new SymfonyTerminal())->getHeight();
return $this->terminal->getHeight();
}

/**
* (Re)initialize the terminal dimensions.
*/
public function initDimensions(): void
{
(new ReflectionClass($this->terminal))
->getMethod('initDimensions')
->invoke($this->terminal);
}

/**
Expand Down
18 changes: 1 addition & 17 deletions src/Themes/Default/Renderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
use Laravel\Prompts\Concerns\Colors;
use Laravel\Prompts\Concerns\Truncation;
use Laravel\Prompts\Prompt;
use RuntimeException;

abstract class Renderer
{
Expand All @@ -22,7 +21,7 @@ abstract class Renderer
*/
public function __construct(protected Prompt $prompt)
{
$this->checkTerminalSize($prompt);
//
}

/**
Expand Down Expand Up @@ -100,19 +99,4 @@ public function __toString()
.$this->output
.(in_array($this->prompt->state, ['submit', 'cancel']) ? PHP_EOL : '');
}

/**
* Check that the terminal is large enough to render the prompt.
*/
private function checkTerminalSize(Prompt $prompt): void
{
$required = 8;
$actual = $prompt->terminal()->lines();

if ($actual < $required) {
throw new RuntimeException(
"The terminal height must be at least [$required] lines but is currently [$actual]. Please increase the height or reduce the font size."
);
}
}
}

0 comments on commit 3b5e6b0

Please sign in to comment.