@@ -48,15 +48,17 @@ extension CommandInfoV0 {
4848 #
4949 # required variables:
5050 #
51- # - flags: the flags that the current (sub)command can accept
52- # - options: the options that the current (sub)command can accept
51+ # - repeating_flags: the repeating flags that the current (sub)command can accept
52+ # - non_repeating_flags: the non-repeating flags that the current (sub)command can accept
53+ # - repeating_options: the repeating options that the current (sub)command can accept
54+ # - non_repeating_options: the non-repeating options that the current (sub)command can accept
5355 # - positional_number: value ignored
5456 # - unparsed_words: unparsed words from the current command line
5557 #
5658 # modified variables:
5759 #
58- # - flags : remove flags for this (sub)command that are already on the command line
59- # - options : remove options for this (sub)command that are already on the command line
60+ # - non_repeating_flags : remove flags for this (sub)command that are already on the command line
61+ # - non_repeating_options : remove options for this (sub)command that are already on the command line
6062 # - positional_number: set to the current positional number
6163 # - unparsed_words: remove all flags, options, and option values for this (sub)command
6264 \( offerFlagsOptionsFunctionName) () {
@@ -93,26 +95,26 @@ extension CommandInfoV0 {
9395 # ${word} is a flag or an option
9496 # If ${word} is an option, mark that the next word to be parsed is an option value
9597 local option
96- for option in " ${options [@]} " ; do
98+ for option in " ${repeating_options[@]} " " ${non_repeating_options [@]}" ; do
9799 [[ " ${word} " = " ${option} " ]] && is_parsing_option_value=true && break
98100 done
99101
100- # Remove ${word} from ${flags } or ${options } so it isn't offered again
102+ # Remove ${word} from ${non_repeating_flags } or ${non_repeating_options } so it isn't offered again
101103 local not_found=true
102104 local -i index
103- for index in " ${!flags [@]} " ; do
104- if [[ " ${flags [${index}]} " = " ${word} " ]]; then
105- unset " flags [${index}]"
106- flags =( " ${flags [@]} " )
105+ for index in " ${!non_repeating_flags [@]} " ; do
106+ if [[ " ${non_repeating_flags [${index}]} " = " ${word} " ]]; then
107+ unset " non_repeating_flags [${index}]"
108+ non_repeating_flags =( " ${non_repeating_flags [@]} " )
107109 not_found=false
108110 break
109111 fi
110112 done
111113 if " ${not_found} " ; then
112- for index in " ${!options [@]} " ; do
113- if [[ " ${options [${index}]} " = " ${word} " ]]; then
114- unset " options [${index}]"
115- options =( " ${options [@]} " )
114+ for index in " ${!non_repeating_flags [@]} " ; do
115+ if [[ " ${non_repeating_flags [${index}]} " = " ${word} " ]]; then
116+ unset " non_repeating_flags [${index}]"
117+ non_repeating_flags =( " ${non_repeating_flags [@]} " )
116118 break
117119 fi
118120 done
@@ -124,7 +126,7 @@ extension CommandInfoV0 {
124126 fi
125127
126128 # ${word} is neither a flag, nor an option, nor an option value
127- if [[ " ${positional_number} " -lt " ${positional_count} " ]]; then
129+ if [[ " ${positional_number} " -lt " ${positional_count} " || " ${positional_count} " -lt 0 ]]; then
128130 # ${word} is a positional
129131 ((positional_number++))
130132 unset " unparsed_words[${word_index}] "
@@ -147,7 +149,7 @@ extension CommandInfoV0 {
147149 && ! " ${is_parsing_option_value} " \\
148150 && [[ ( " ${cur} " = -* && " ${positional_number} " -ge 0) || " ${positional_number} " -eq -1 ]]
149151 then
150- COMPREPLY+=($(compgen -W " ${flags [*]} ${options [*]} " -- " ${cur} " ))
152+ COMPREPLY+=($(compgen -W " ${repeating_flags [*]} ${non_repeating_flags[*]} ${repeating_options[*]} ${non_repeating_options [*]} " -- " ${cur} " ))
151153 fi
152154 }
153155
@@ -211,26 +213,33 @@ extension CommandInfoV0 {
211213
212214 let positionalArguments = positionalArguments
213215
214- let flagCompletions = flagCompletions
215- let optionCompletions = optionCompletions
216- if !flagCompletions. isEmpty || !optionCompletions. isEmpty {
216+ let arguments = arguments ?? [ ]
217+
218+ let flags = arguments. filter { $0. kind == . flag }
219+ let options = arguments. filter { $0. kind == . option }
220+ if !flags. flatMap ( \. completionWords) . isEmpty
221+ || !options. flatMap ( \. completionWords) . isEmpty
222+ {
217223 result += """
218- \( declareTopLevelArray) flags=( \( flagCompletions. joined ( separator: " " ) ) )
219- \( declareTopLevelArray) options=( \( optionCompletions. joined ( separator: " " ) ) )
220- \( offerFlagsOptionsFunctionName) \( positionalArguments. count)
224+ \( declareTopLevelArray) repeating_flags=( \( flags. filter ( \. isRepeating) . flatMap ( \. completionWords) . joined ( separator: " " ) ) )
225+ \( declareTopLevelArray) non_repeating_flags=( \( flags. filter { !$0. isRepeating } . flatMap ( \. completionWords) . joined ( separator: " " ) ) )
226+ \( declareTopLevelArray) repeating_options=( \( options. filter ( \. isRepeating) . flatMap ( \. completionWords) . joined ( separator: " " ) ) )
227+ \( declareTopLevelArray) non_repeating_options=( \( options. filter { !$0. isRepeating } . flatMap ( \. completionWords) . joined ( separator: " " ) ) )
228+ \( offerFlagsOptionsFunctionName) \
229+ \( positionalArguments. contains { $0. isRepeating } ? - 1 : positionalArguments. count)
221230
222231 """
223232 }
224233
225234 // Generate the case pattern-matching statements for option values.
226235 // If there aren't any, skip the case block altogether.
227236 let optionHandlers =
228- ( arguments ?? [ ] ) . compactMap { arg in
237+ options . compactMap { arg in
229238 guard arg. kind != . flag else { return nil }
230239 let words = arg. completionWords
231240 guard !words. isEmpty else { return nil }
232241 return """
233- \( arg . completionWords . map { " ' \( $0. shellEscapeForSingleQuotedString ( ) ) ' " } . joined ( separator: " | " ) ) )
242+ \( words . map { " ' \( $0. shellEscapeForSingleQuotedString ( ) ) ' " } . joined ( separator: " | " ) ) )
234243 \( valueCompletion ( arg) . indentingEachLine ( by: 8 ) ) \
235244 return
236245 ;;
@@ -248,14 +257,23 @@ extension CommandInfoV0 {
248257 """
249258 }
250259
260+ var encounteredRepeatingPositional = false
251261 let positionalCases =
252262 zip ( 1 ... , positionalArguments)
253263 . compactMap { position, arg in
264+ guard !encounteredRepeatingPositional else {
265+ return nil as String ?
266+ }
267+
268+ if arg. isRepeating {
269+ encounteredRepeatingPositional = true
270+ }
271+
254272 let completion = valueCompletion ( arg)
255273 return completion. isEmpty
256274 ? nil
257275 : """
258- \( position) )
276+ \( encounteredRepeatingPositional ? " * " : position. description ) )
259277 \( completion. indentingEachLine ( by: 8 ) ) \
260278 return
261279 ;;
@@ -310,30 +328,6 @@ extension CommandInfoV0 {
310328 """
311329 }
312330
313- /// Returns flag completions.
314- private var flagCompletions : [ String ] {
315- ( arguments ?? [ ] ) . flatMap {
316- switch $0. kind {
317- case . flag:
318- return $0. completionWords
319- default :
320- return [ ]
321- }
322- }
323- }
324-
325- /// Returns option completions.
326- private var optionCompletions : [ String ] {
327- ( arguments ?? [ ] ) . flatMap {
328- switch $0. kind {
329- case . option:
330- return $0. completionWords
331- default :
332- return [ ]
333- }
334- }
335- }
336-
337331 /// Returns the completions that can follow the given argument's `--name`.
338332 private func valueCompletion( _ arg: ArgumentInfoV0 ) -> String {
339333 switch arg. completionKind {
@@ -375,7 +369,6 @@ extension CommandInfoV0 {
375369 """
376370
377371 case . custom, . customAsync:
378- // Generate a call back into the command to retrieve a completions list
379372 return """
380373 \( addCompletionsFunctionName) -W \
381374 " $( \( customCompleteFunctionName) \( arg. commonCustomCompletionCall ( command: self ) ) \
@@ -385,7 +378,6 @@ extension CommandInfoV0 {
385378 """
386379
387380 case . customDeprecated:
388- // Generate a call back into the command to retrieve a completions list
389381 return """
390382 \( addCompletionsFunctionName) -W \
391383 " $( \( customCompleteFunctionName) \( arg. commonCustomCompletionCall ( command: self ) ) ) "
0 commit comments