Skip to content

Commit cb027ea

Browse files
committed
Rework bash completion script to use native bash functions
1 parent 55aa9b7 commit cb027ea

File tree

5 files changed

+35
-32
lines changed

5 files changed

+35
-32
lines changed

src/Symfony/Component/Console/Command/CompleteCommand.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
134134
$this->log(' <comment>No suggestions were provided</>');
135135
}
136136

137-
$completionOutput->write($suggestions, $completionInput, $output);
137+
$completionOutput->write($suggestions, $output);
138138
} catch (\Throwable $e) {
139139
$this->log([
140140
'<error>Error!</error>',

src/Symfony/Component/Console/Completion/CompletionInput.php

-6
Original file line numberDiff line numberDiff line change
@@ -109,10 +109,6 @@ public function bind(InputDefinition $definition): void
109109
// complete argument value
110110
$this->completionType = self::TYPE_ARGUMENT_VALUE;
111111

112-
// shell cannot interpret quotes
113-
$trimQuotes = function ($value): string {
114-
return trim($value, '\'"');
115-
};
116112
$arguments = $this->getArguments();
117113
foreach ($arguments as $argumentName => $argumentValue) {
118114
if (null === $argumentValue) {
@@ -121,10 +117,8 @@ public function bind(InputDefinition $definition): void
121117

122118
$this->completionName = $argumentName;
123119
if (\is_array($argumentValue)) {
124-
$this->setArgument($argumentName, array_map($trimQuotes, $argumentValue));
125120
$this->completionValue = $argumentValue ? $argumentValue[array_key_last($argumentValue)] : null;
126121
} else {
127-
$this->setArgument($argumentName, $trimQuotes($argumentValue));
128122
$this->completionValue = $argumentValue;
129123
}
130124
}

src/Symfony/Component/Console/Completion/Output/BashCompletionOutput.php

+3-21
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Console\Completion\Output;
1313

14-
use Symfony\Component\Console\Completion\CompletionInput;
1514
use Symfony\Component\Console\Completion\CompletionSuggestions;
1615
use Symfony\Component\Console\Output\OutputInterface;
1716

@@ -20,29 +19,12 @@
2019
*/
2120
class BashCompletionOutput implements CompletionOutputInterface
2221
{
23-
public function write(CompletionSuggestions $suggestions, CompletionInput $input, OutputInterface $output): void
22+
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void
2423
{
25-
$options = [];
24+
$options = $suggestions->getValueSuggestions();
2625
foreach ($suggestions->getOptionSuggestions() as $option) {
2726
$options[] = '--'.$option->getName();
2827
}
29-
$output->write(implode(' ', $options));
30-
31-
// add quotes to the suggestion if current value starts with a quote
32-
$completionValue = $input->getCompletionValue();
33-
$quote = in_array($completionValue[0] ?? null, ['"', '\''], true) ? $completionValue[0] : null;
34-
35-
$values = array_map(function ($value) use ($completionValue, $quote) {
36-
if (null !== $quote) {
37-
$value = $quote . $value . $quote;
38-
} elseif (str_contains($value, '\\')) {
39-
// without quote, more escaping is necessary
40-
$value = str_replace('\\', '\\\\', $value);
41-
}
42-
43-
return escapeshellarg($value);
44-
}, $suggestions->getValueSuggestions());
45-
46-
$output->writeln(implode(' ', $values));
28+
$output->writeln(implode("\n", $options));
4729
}
4830
}

src/Symfony/Component/Console/Completion/Output/CompletionOutputInterface.php

+1-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111

1212
namespace Symfony\Component\Console\Completion\Output;
1313

14-
use Symfony\Component\Console\Completion\CompletionInput;
1514
use Symfony\Component\Console\Completion\CompletionSuggestions;
1615
use Symfony\Component\Console\Output\OutputInterface;
1716

@@ -22,5 +21,5 @@
2221
*/
2322
interface CompletionOutputInterface
2423
{
25-
public function write(CompletionSuggestions $suggestions, CompletionInput $input, OutputInterface $output): void;
24+
public function write(CompletionSuggestions $suggestions, OutputInterface $output): void;
2625
}

src/Symfony/Component/Console/Resources/completion.bash

+30-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
# https://symfony.com/doc/current/contributing/code/license.html
77

88
_sf_{{ COMMAND_NAME }}() {
9+
# Use newline as only separator to allow space in completion values
10+
IFS=$'\n'
911
local sf_cmd="${COMP_WORDS[0]}"
1012
if [ ! -f "$sf_cmd" ]; then
1113
return 1
@@ -16,12 +18,38 @@ _sf_{{ COMMAND_NAME }}() {
1618

1719
local completecmd=("$sf_cmd" "_complete" "-sbash" "-c$cword" "-S{{ VERSION }}")
1820
for w in ${words[@]}; do
19-
completecmd+=($(printf -- "-i%b" "$w"))
21+
w=$(printf -- '%b' "$w")
22+
# remove quotes
23+
quote="${w:0:1}"
24+
if [ "$quote" == "'" ]; then
25+
w="${w%\'}"
26+
w="${w#\'}"
27+
elif [ "$quote" == '"' ]; then
28+
w="${w%\"}"
29+
w="${w#\"}"
30+
fi
31+
completecmd+=("-i$w")
2032
done
2133

2234
local sfcomplete
2335
if sfcomplete=$(${completecmd[@]} 2>&1); then
24-
COMPREPLY=($(compgen -W "$sfcomplete" -- $(printf -- "%q" "$cur")))
36+
local quote suggestions
37+
quote=${cur:0:1}
38+
if [ "$quote" == "'" ]; then
39+
# single quotes: no additional escaping (does not accept ' in values)
40+
suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
41+
elif [ "$quote" == '"' ]; then
42+
# double quotes: double escaping for \ $ ` "
43+
s=${s//\\/\\\\}
44+
s=${s//\$/\\\$}
45+
s=${s//\`/\\\`}
46+
s=${s//\"/\\\"}
47+
suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
48+
else
49+
# no quotes: double escaping
50+
suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done)
51+
fi
52+
COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur")))
2553
__ltrim_colon_completions "$cur"
2654
else
2755
if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then

0 commit comments

Comments
 (0)