Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions packages/console/src/Components/Concerns/HasTextBuffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,18 @@ public function deletePreviousCharacter(): void
$this->buffer->deletePreviousCharacter();
}

#[HandlesKey(Key::CMD_BACKSPACE)]
public function deleteCurrentLine(): void
{
if (! $this->bufferEnabled) {
return;
}

$this->buffer->deleteCurrentLine();
}

#[HandlesKey(Key::CTRL_BACKSPACE)]
#[HandlesKey(Key::ALT_BACKSPACE)]
public function deletePreviousWord(): void
{
if (! $this->bufferEnabled) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ final class InteractiveComponentRenderer

private bool $shouldRerender = true;

/** @var list<string> */
private array $pendingKeys = [];
private string $pendingInput = '';

public function __construct(
private readonly Validator $validator,
) {}
Expand Down Expand Up @@ -87,7 +91,7 @@ private function applyKey(InteractiveConsoleComponent $component, Console $conso
}

usleep(50);
$key = $console->read(16);
$key = $this->readKey($console);

// If there's no keypress, continue.
if ($key === '') {
Expand Down Expand Up @@ -167,6 +171,84 @@ private function applyKey(InteractiveConsoleComponent $component, Console $conso
}
}

private function readKey(Console $console): string
{
while ($this->pendingKeys === []) {
$input = $console->read(16);

if ($input === '') {
return '';
}

$this->pendingInput .= $input;
$this->pendingKeys = $this->splitKeys($this->pendingInput);
}

return array_shift($this->pendingKeys);
}

/** @return list<string> */
private function splitKeys(string &$input): array
{
/** @var null|list<string> $knownKeys */
static $knownKeys = null;

if ($knownKeys === null) {
$knownKeys = array_map(
static fn (Key $key): string => $key->value,
Key::cases(),
);
usort($knownKeys, static fn (string $left, string $right): int => strlen($right) <=> strlen($left));
}

$keys = [];

while ($input !== '') {
foreach ($knownKeys as $knownKey) {
if (! str_starts_with($input, $knownKey)) {
continue;
}

$keys[] = $knownKey;
$input = substr($input, strlen($knownKey));

continue 2;
}

if (str_starts_with($input, "\e")) {
$matches = [];
preg_match('/^\e(?:\[[0-?]*[ -\/]*[@-~]|O.)/s', $input, $matches);
$key = $matches[0] ?? mb_substr($input, 0, 2);
} else {
$length = $this->getUtf8CharacterLength($input);

if (strlen($input) < $length) {
break;
}

$key = substr($input, 0, $length);
}

$keys[] = $key;
$input = substr($input, strlen($key));
}

return $keys;
}

private function getUtf8CharacterLength(string $input): int
{
$firstByte = ord($input[0]);

return match (true) {
($firstByte & 0x80) === 0 => 1,
($firstByte & 0xE0) === 0xC0 => 2,
($firstByte & 0xF0) === 0xE0 => 3,
($firstByte & 0xF8) === 0xF0 => 4,
default => 1,
};
}

private function renderFrames(InteractiveConsoleComponent $component, Terminal $terminal): mixed
{
while (true) {
Expand All @@ -180,6 +262,8 @@ private function renderFrames(InteractiveConsoleComponent $component, Terminal $
continue;
}

$this->shouldRerender = false;

// Rerender the frames, it could be one or more
$frames = $terminal->render(
component: $component,
Expand All @@ -195,9 +279,6 @@ private function renderFrames(InteractiveConsoleComponent $component, Terminal $

$return = $frames->getReturn();

// Everything's rerendered
$this->shouldRerender = false;

if ($return !== null) {
return $return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use Tempest\Console\Terminal\Terminal;
use Tempest\Support\Str\ImmutableString;

use function Tempest\Support\arr;
use function Tempest\Support\str;

final class TextInputRenderer
Expand All @@ -35,14 +36,11 @@ public function render(
$this->label($label);

if ($hint) {
$this->offsetY++;
$this->offsetY += substr_count($hint, "\n") + 1;
$this->line($this->style('fg-gray', $hint))->newLine();
}

// splits the text to an array so we can work with individual lines
$lines = str($buffer->text ?: ($placeholder ?: ''))
->explode("\n")
->flatMap(fn (string $line) => str($line)->chunk($this->maxLineCharacters)->toArray())
$lines = arr($buffer->getWrappedLines($this->maxLineCharacters, $placeholder))
->map(static fn (string $line) => str($line)->replaceEnd("\n", ' '));

// calculates scroll offset based on cursor position
Expand Down
Loading
Loading