8000 Add character-by-character autocompletion · peterlcole/symfony@837c906 · GitHub
[go: up one dir, main page]

Skip to content

Commit 837c906

Browse files
lmcdfabpot
authored andcommitted
Add character-by-character autocompletion
Fix autocomplete code. This is proof of concept for now Move functions inside of DialogHelper class Fix syntax errors Fix half-completed text not being returned from DialogHelper::ask when return key is pressed Handle arrow keys (ignore for now) Fallback if lacking colour support User $output->write instead of echo/printf Lose escape character constant Cleanup/minor refactor
1 parent a1adef7 commit 837c906

File tree

2 files changed

+100
-11
lines changed

2 files changed

+100
-11
lines changed

src/Symfony/Component/Console/Helper/DialogHelper.php

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
namespace Symfony\Component\Console\Helper;
1313

1414
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Console\Output\StreamOutput;
1516

1617
/**
1718
* The Dialog class provides helpers to interact with the user.
@@ -31,15 +32,102 @@ class DialogHelper extends Helper
3132
*
3233
* @return string The user answer
3334
*/
34-
public function ask(OutputInterface $output, $question, $default = null)
35+
public function ask(OutputInterface $output, $question, $default = null, $autocomplete = null)
3536
{
3637
$output->write($question);
3738

38-
$ret = fgets($this->inputStream ?: STDIN, 4096);
39-
if (false === $ret) {
40-
throw new \RuntimeException('Aborted');
39+
$inputStream = (null === $this->inputStream ? STDIN : $this->inputStream);
40+
41+
if (null === $autocomplete || !($output instanceof StreamOutput && $output->hasColorSupport())) {
42+
$ret = fgets($inputStream);
43+
if (false === $ret) {
44+
throw new \RuntimeException('Aborted');
45+
}
46+
$ret = trim($ret);
47+
} else {
48+
$i = 0;
49+
$currentMatched = false;
50+
$ret = '';
51+
52+
// Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
53+
system("stty -icanon -echo");
54+
55+
while ($c = fread($inputStream, 3)) {
56+
// Did we read an escape character?
57+
if (strlen($c) > 1 && $c[0] == "\033") {
58+
// Escape sequences for arrow keys
59+
if ($c[2] == 'A' || $c[2] == 'B' || $c[2] == 'C' || $c[2] == 'D') {
60+
continue;
61+
}
62+
}
63+
64+
// Backspace Character
65+
if (ord($c) == 127) {
66+
if ($i == 0) {
67+
continue;
68+
}
69+
70+
// Move cursor backwards
71+
$output->write("\033[1D");
72+
// Erase characters from cursor to end of line
73+
$output->write("\033[K");
74+
$ret = substr($ret, 0, --$i);
75+
76+
continue;
77+
}
78+
79+
if ($c == "\t" | $c == "\n") {
80+
if (false !== $currentMatched) {
81+
// Echo out completed match
82+
$output->write(substr($autocomplete[$currentMatched], strlen($ret)));
83+
$ret = $autocomplete[$currentMatched];
84+
$i = strlen($ret);
85+
}
86+
87+
if ($c == "\n") {
88+
$output->write($c);
89+
break;
90+
}
91+
92+
continue;
93+
}
94+
95+
$output->write($c);
96+
$ret .= $c;
97+
$i++;
98+
99+
$lastMatch = $currentMatched;
100+
101+
// Erase characters from cursor to end of line
102+
$output->write("\033[K");
103+
104+
for ($j = 0; $j < count($autocomplete); $j++) {
105+
$matchTest = substr($autocomplete[$j], 0, strlen($ret));
106+
107+
if ($ret == $matchTest) {
108+
// Save cursor position
109+
$output->write("\0337");
110+
111+
// Set fore/background colour to make text appear highlighted
112+
$output->write("\033[47;30m");
113+
$output->write(substr($autocomplete[$j], strlen($ret)));
114+
// Reset text colour
115+
$output->write("\033[0m");
116+
117+
// Restore cursor position
118+
$output->write("\0338");
119+
120+
$currentMatched = $j;
121+
break;
122+
}
123+
124+
$currentMatched = false;
125+
}
126+
}
127+
128+
// Reset stty so it behaves normally again
129+
system("stty icanon echo");
41130
}
42-
$ret = trim($ret);
43131

44132
return strlen($ret) > 0 ? $ret : $default;
45133
}
@@ -78,23 +166,24 @@ public function askConfirmation(OutputInterface $output, $question, $default = t
78166
*
79167
* @param OutputInterface $output
80168
* @param string|array $question
81-
* @param callback $validator A PHP callback
82-
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
83-
* @param string $default The default answer if none is given by the user
169+
* @param callback $validator A PHP callback
170+
* @param integer $attempts Max number of times to ask before giving up (false by default, which means infinite)
171+
* @param string $default The default answer if none is given by the user
172+
* @param array $autoComplete
84173
*
85174
* @return mixed
86175
*
87176
* @throws \Exception When any of the validator returns an error
88177
*/
89-
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null)
178+
public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, $autocomplete = null)
90179
{
91180
$error = null;
92181
while (false === $attempts || $attempts--) {
93182
if (null !== $error) {
94183
$output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
95184
}
96185

97-
$value = $this->ask($output, $question, $default);
186+
$value = $this->ask($output, $question, $default, $autocomplete);
98187

99188
try {
100189
return call_user_func($validator, $value);

src/Symfony/Component/Console/Output/StreamOutput.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public function doWrite($message, $newline)
100100
*
101101
* @return Boolean true if the stream supports colorization, false otherwise
102102
*/
103-
protected function hasColorSupport()
103+
public function hasColorSupport()
104104
{
105105
// @codeCoverageIgnoreStart
106106
if (DIRECTORY_SEPARATOR == '\\') {

0 commit comments

Comments
 (0)
0