@@ -110,7 +110,7 @@ __flow_process_completion_results() {
110110
111111 if (( (directive & shellCompDirectiveFilterFileExt) != 0 )) ; then
112112 # File extension filtering
113- local fullFilter filter filteringCmd
113+ local fullFilter= " " filter filteringCmd
114114
115115 # Do not use quotes around the $completions variable or else newline
116116 # characters will be kept.
@@ -141,20 +141,71 @@ __flow_process_completion_results() {
141141 __flow_handle_special_char " $cur " =
142142
143143 # Print the activeHelp statements before we finish
144+ __flow_handle_activeHelp
145+ }
146+
147+ __flow_handle_activeHelp () {
148+ # Print the activeHelp statements
144149 if (( ${# activeHelp[*]} != 0 )) ; then
145- printf " \n" ;
146- printf " %s\n" " ${activeHelp[@]} "
147- printf " \n"
148-
149- # The prompt format is only available from bash 4.4.
150- # We test if it is available before using it.
151- if (x=${PS1@ P} ) 2> /dev/null; then
152- printf " %s" " ${PS1@ P}${COMP_LINE[@]} "
153- else
154- # Can't print the prompt. Just print the
155- # text the user had typed, it is workable enough.
156- printf " %s" " ${COMP_LINE[@]} "
150+ if [ -z $COMP_TYPE ]; then
151+ # Bash v3 does not set the COMP_TYPE variable.
152+ printf " \n" ;
153+ printf " %s\n" " ${activeHelp[@]} "
154+ printf " \n"
155+ __flow_reprint_commandLine
156+ return
157157 fi
158+
159+ # Only print ActiveHelp on the second TAB press
160+ if [ $COMP_TYPE -eq 63 ]; then
161+ printf " \n"
162+ printf " %s\n" " ${activeHelp[@]} "
163+
164+ if (( ${# COMPREPLY[*]} == 0 )) ; then
165+ # When there are no completion choices from the program, file completion
166+ # may kick in if the program has not disabled it; in such a case, we want
167+ # to know if any files will match what the user typed, so that we know if
168+ # there will be completions presented, so that we know how to handle ActiveHelp.
169+ # To find out, we actually trigger the file completion ourselves;
170+ # the call to _filedir will fill COMPREPLY if files match.
171+ if (( (directive & shellCompDirectiveNoFileComp) == 0 )) ; then
172+ __flow_debug " Listing files"
173+ _filedir
174+ fi
175+ fi
176+
177+ if (( ${# COMPREPLY[*]} != 0 )) ; then
178+ # If there are completion choices to be shown, print a delimiter.
179+ # Re-printing the command-line will automatically be done
180+ # by the shell when it prints the completion choices.
181+ printf -- " --"
182+ else
183+ # When there are no completion choices at all, we need
184+ # to re-print the command-line since the shell will
185+ # not be doing it itself.
186+ __flow_reprint_commandLine
187+ fi
188+ elif [ $COMP_TYPE -eq 37 ] || [ $COMP_TYPE -eq 42 ]; then
189+ # For completion type: menu-complete/menu-complete-backward and insert-completions
190+ # the completions are immediately inserted into the command-line, so we first
191+ # print the activeHelp message and reprint the command-line since the shell won't.
192+ printf " \n"
193+ printf " %s\n" " ${activeHelp[@]} "
194+
195+ __flow_reprint_commandLine
196+ fi
197+ fi
198+ }
199+
200+ __flow_reprint_commandLine () {
201+ # The prompt format is only available from bash 4.4.
202+ # We test if it is available before using it.
203+ if (x=${PS1@ P} ) 2> /dev/null; then
204+ printf " %s" " ${PS1@ P}${COMP_LINE[@]} "
205+ else
206+ # Can't print the prompt. Just print the
207+ # text the user had typed, it is workable enough.
208+ printf " %s" " ${COMP_LINE[@]} "
158209 fi
159210}
160211
@@ -165,6 +216,8 @@ __flow_extract_activeHelp() {
165216 local endIndex=${# activeHelpMarker}
166217
167218 while IFS=' ' read -r comp; do
219+ [[ -z $comp ]] && continue
220+
168221 if [[ ${comp: 0: endIndex} == $activeHelpMarker ]]; then
169222 comp=${comp: endIndex}
170223 __flow_debug " ActiveHelp found: $comp "
@@ -187,16 +240,21 @@ __flow_handle_completion_types() {
187240 # If the user requested inserting one completion at a time, or all
188241 # completions at once on the command-line we must remove the descriptions.
189242 # https://github.com/spf13/cobra/issues/1508
190- local tab=$' \t ' comp
191- while IFS=' ' read -r comp; do
192- [[ -z $comp ]] && continue
193- # Strip any description
194- comp=${comp%% $tab * }
195- # Only consider the completions that match
196- if [[ $comp == " $cur " * ]]; then
197- COMPREPLY+=(" $comp " )
198- fi
199- done < <( printf " %s\n" " ${completions[@]} " )
243+
244+ # If there are no completions, we don't need to do anything
245+ (( ${# completions[@]} == 0 )) && return 0
246+
247+ local tab=$' \t '
248+
249+ # Strip any description and escape the completion to handled special characters
250+ IFS=$' \n ' read -ra completions -d ' ' < <( printf " %q\n" " ${completions[@]%% $tab * } " )
251+
252+ # Only consider the completions that match
253+ IFS=$' \n ' read -ra COMPREPLY -d ' ' < <( IFS=$' \n ' ; compgen -W " ${completions[*]} " -- " ${cur} " )
254+
255+ # compgen looses the escaping so we need to escape all completions again since they will
256+ # all be inserted on the command-line.
257+ IFS=$' \n ' read -ra COMPREPLY -d ' ' < <( printf " %q\n" " ${COMPREPLY[@]} " )
200258 ;;
201259
202260 * )
@@ -207,11 +265,25 @@ __flow_handle_completion_types() {
207265}
208266
209267__flow_handle_standard_completion_case () {
210- local tab=$' \t ' comp
268+ local tab=$' \t '
269+
270+ # If there are no completions, we don't need to do anything
271+ (( ${# completions[@]} == 0 )) && return 0
211272
212273 # Short circuit to optimize if we don't have descriptions
213274 if [[ " ${completions[*]} " != * $tab * ]]; then
214- IFS=$' \n ' read -ra COMPREPLY -d ' ' < <( compgen -W " ${completions[*]} " -- " $cur " )
275+ # First, escape the completions to handle special characters
276+ IFS=$' \n ' read -ra completions -d ' ' < <( printf " %q\n" " ${completions[@]} " )
277+ # Only consider the completions that match what the user typed
278+ IFS=$' \n ' read -ra COMPREPLY -d ' ' < <( IFS=$' \n ' ; compgen -W " ${completions[*]} " -- " ${cur} " )
279+
280+ # compgen looses the escaping so, if there is only a single completion, we need to
281+ # escape it again because it will be inserted on the command-line. If there are multiple
282+ # completions, we don't want to escape them because they will be printed in a list
283+ # and we don't want to show escape characters in that list.
284+ if (( ${# COMPREPLY[@]} == 1 )) ; then
285+ COMPREPLY[0]=$( printf " %q" " ${COMPREPLY[0]} " )
286+ fi
215287 return 0
216288 fi
217289
@@ -220,23 +292,39 @@ __flow_handle_standard_completion_case() {
220292 # Look for the longest completion so that we can format things nicely
221293 while IFS=' ' read -r compline; do
222294 [[ -z $compline ]] && continue
223- # Strip any description before checking the length
224- comp=${compline%% $tab * }
295+
296+ # Before checking if the completion matches what the user typed,
297+ # we need to strip any description and escape the completion to handle special
298+ # characters because those escape characters are part of what the user typed.
299+ # Don't call "printf" in a sub-shell because it will be much slower
300+ # since we are in a loop.
301+ printf -v comp " %q" " ${compline%% $tab * } " & > /dev/null || comp=$( printf " %q" " ${compline%% $tab * } " )
302+
225303 # Only consider the completions that match
226304 [[ $comp == " $cur " * ]] || continue
305+
306+ # The completions matches. Add it to the list of full completions including
307+ # its description. We don't escape the completion because it may get printed
308+ # in a list if there are more than one and we don't want show escape characters
309+ # in that list.
227310 COMPREPLY+=(" $compline " )
311+
312+ # Strip any description before checking the length, and again, don't escape
313+ # the completion because this length is only used when printing the completions
314+ # in a list and we don't want show escape characters in that list.
315+ comp=${compline%% $tab * }
228316 if (( ${# comp} > longest)) ; then
229317 longest=${# comp}
230318 fi
231319 done < <( printf " %s\n" " ${completions[@]} " )
232320
233- # If there is a single completion left, remove the description text
321+ # If there is a single completion left, remove the description text and escape any special characters
234322 if (( ${# COMPREPLY[*]} == 1 )) ; then
235323 __flow_debug " COMPREPLY[0]: ${COMPREPLY[0]} "
236- comp= " ${COMPREPLY[0]%% $tab * } "
237- __flow_debug " Removed description from single completion, which is now: ${comp } "
238- COMPREPLY[0]= $comp
239- else # Format the descriptions
324+ COMPREPLY[0]= $( printf " %q " " ${COMPREPLY[0]%% $tab * } " )
325+ __flow_debug " Removed description from single completion, which is now: ${COMPREPLY[0] } "
326+ else
327+ # Format the descriptions
240328 __flow_format_comp_descriptions $longest
241329 fi
242330}
0 commit comments