Skip to content

[Console][WIP][POC] Add support for arrow keys in QuestionHelper #36700

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
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
Add support for arrow keys in QuestionHelper
  • Loading branch information
pierredup committed May 5, 2020
commit e6050fc1b457e749ca4cd5d4d4368b527f70c38a
4 changes: 4 additions & 0 deletions src/Symfony/Component/Console/Cursor.php
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ public function getCurrentPosition(): array

sscanf($code, "\033[%d;%dR", $row, $col);

if (null === $row && null === $col) {
sscanf($code, "~\033[%d;%dR", $row, $col);
}

return [$col, $row];
}
}
78 changes: 75 additions & 3 deletions src/Symfony/Component/Console/Helper/QuestionHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ private function doAsk(OutputInterface $output, Question $question)
$inputStream = $this->inputStream ?: STDIN;
$autocomplete = $question->getAutocompleterCallback();

$cursor = new Cursor($output);

if (null === $autocomplete || !Terminal::hasSttyAvailable()) {
$ret = false;
if ($question->isHidden()) {
Expand All @@ -123,10 +125,80 @@ private function doAsk(OutputInterface $output, Question $question)
}

if (false === $ret) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new MissingInputException('Aborted.');
if (!Terminal::hasSttyAvailable()) {
$ret = fgets($inputStream, 4096);
if (false === $ret) {
throw new MissingInputException('Aborted.');
}
} else {
$sttyMode = shell_exec('stty -g');

shell_exec('stty -icanon -echo');

$k = null;
$string = '';

[$x] = $cursor->getCurrentPosition();

while (10 !== $k && 0 !== $k) {
[$pos] = $cursor->getCurrentPosition();
$pos -= $x;

$ret = fgetc($inputStream);
if (false === $ret && '' === $string) {
shell_exec(sprintf('stty %s', $sttyMode));
throw new MissingInputException('Aborted.');
}

$k = \ord($ret);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this code maintainable in the future, we could rename some variables. E.g. $k -> $pressedKey or $keyCode,etc.


if (27 === $k) {
fgetc($inputStream);
$k = \ord(fgetc($inputStream));

switch (true) {
case 67 === $k && $pos < self::strlen($string):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, let's define some constants to avoid these "magic numbers". E.g.:

case self::ARROW_RIGHT === $pressedKey:
    // ...

case self::ARROW_LEFT === $pressedKey:
    // ...

// ...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would highly appreciate that. I read the code and it took some time to dechipher what these numbers are.

$cursor->moveRight();
break;
case 68 === $k && $pos > 0:
$cursor->moveLeft();
break;
case 51 === $k && $pos >= 0:
$string = self::substr($string, 0, $pos).self::substr($string, $pos + 1);
$cursor->clearLineAfter();
$output->write(self::substr($string, $pos));
$cursor->moveToColumn($pos + $x);
break;
}
} elseif (127 === $k) {
if ($pos > 0) {
$string = self::substr($string, 0, $pos - 1).self::substr($string, $pos);

$cursor->moveToColumn($x);
$output->write($string);
$cursor->clearLineAfter()
->moveToColumn(($pos + $x) - 1);
}
} elseif ($k >= 32 && $k <= 126) {
if ($pos > 0 && $pos < \strlen($string)) {
$string = self::substr($string, 0, $pos).$ret.self::substr($string, $pos);
$output->write($ret.self::substr($string, $pos + 1));
$cursor->clearLineAfter()
->moveToColumn($pos + $x + 1);
} else {
$string .= $ret;
$output->write($ret);
}
} else {
$output->write($ret);
}
}

shell_exec(sprintf('stty %s', $sttyMode));

$ret = $string;
}

if ($question->isTrimmable()) {
$ret = trim($ret);
}
Expand Down