Skip to content

Commit 64f37c8

Browse files
committed
Merge branch 'main' into changelog_1.7.0
2 parents 34bfcc7 + a836797 commit 64f37c8

10 files changed

Lines changed: 395 additions & 254 deletions

File tree

Sources/ArgumentParser/Completions/BashCompletionsGenerator.swift

Lines changed: 43 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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)))"

Sources/ArgumentParser/Completions/FishCompletionsGenerator.swift

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,36 @@ extension ToolInfoV0 {
2424
extension CommandInfoV0 {
2525
fileprivate var fishCompletionScript: String {
2626
"""
27-
function \(shouldOfferCompletionsForFunctionName) -a expected_commands -a expected_positional_index
28-
set -l unparsed_tokens (\(tokensFunctionName) -pc)
27+
function \(shouldOfferCompletionsForFlagsOrOptionsFunctionName) -a expected_commands
28+
set -l non_repeating_flags_or_options $argv[2..]
29+
30+
set -l non_repeating_flags_or_options_absent 0
2931
set -l positional_index 0
3032
set -l commands
33+
\(parseTokensFunctionName)
34+
test "$commands" = "$expected_commands"; and return $non_repeating_flags_or_options_absent
35+
end
36+
37+
function \(shouldOfferCompletionsForPositionalFunctionName) -a expected_commands expected_positional_index positional_index_comparison
38+
if test -z $positional_index_comparison
39+
set positional_index_comparison -eq
40+
end
41+
42+
set -l non_repeating_flags_or_options
43+
set -l non_repeating_flags_or_options_absent 0
44+
set -l positional_index 0
45+
set -l commands
46+
\(parseTokensFunctionName)
47+
test "$commands" = "$expected_commands" -a \\( "$positional_index" "$positional_index_comparison" "$expected_positional_index" \\)
48+
end
49+
50+
function \(parseTokensFunctionName) -S
51+
set -l unparsed_tokens (\(tokensFunctionName) -pc)
52+
set -l present_flags_and_options
3153
3254
switch $unparsed_tokens[1]
3355
\(commandCases)
3456
end
35-
36-
test "$commands" = "$expected_commands" -a \\( -z "$expected_positional_index" -o "$expected_positional_index" -eq "$positional_index" \\)
3757
end
3858
3959
function \(tokensFunctionName)
@@ -44,9 +64,8 @@ extension CommandInfoV0 {
4464
end
4565
end
4666
47-
function \(parseSubcommandFunctionName) -S
67+
function \(parseSubcommandFunctionName) -S -a positional_count
4868
argparse -s r -- $argv
49-
set -l positional_count $argv[1]
5069
set -l option_specs $argv[2..]
5170
5271
set -a commands $unparsed_tokens[1]
@@ -58,8 +77,16 @@ extension CommandInfoV0 {
5877
argparse -sn "$commands" $option_specs -- $unparsed_tokens 2> /dev/null
5978
set unparsed_tokens $argv
6079
set positional_index (math $positional_index + 1)
80+
81+
for non_repeating_flag_or_option in $non_repeating_flags_or_options
82+
if set -ql _flag_$non_repeating_flag_or_option
83+
set non_repeating_flags_or_options_absent 1
84+
break
85+
end
86+
end
87+
6188
if test (count $unparsed_tokens) -eq 0 -o \\( -z "$_flag_r" -a "$positional_index" -gt "$positional_count" \\)
62-
return 0
89+
break
6390
end
6491
set -e unparsed_tokens[1]
6592
end
@@ -113,39 +140,52 @@ extension CommandInfoV0 {
113140
}
114141

115142
private var completions: [String] {
116-
let prefix = """
117-
complete -c '\(initialCommand)'\
118-
-n '\(shouldOfferCompletionsForFunctionName)\
119-
"\(commandContext.joined(separator: separator))"
120-
"""
143+
let prefix = "complete -c '\(initialCommand)' -n '"
121144

122145
let subcommands = (subcommands ?? []).filter(\.shouldDisplay)
123146

124147
var positionalIndex = 0
125148

149+
var repeatingPositionalComparison = ""
126150
let argumentCompletions =
127151
completableArguments
128-
.map { arg in
129-
"""
130-
\(prefix)\(
131-
arg.kind == .positional
132-
? """
133-
\({
134-
positionalIndex += 1
135-
return " \(positionalIndex)"
136-
}())
152+
.compactMap { arg in
153+
if arg.kind == .positional {
154+
guard repeatingPositionalComparison.isEmpty else {
155+
return nil as String?
156+
}
157+
158+
if arg.isRepeating {
159+
repeatingPositionalComparison = " -ge"
160+
}
161+
}
162+
163+
return """
164+
\(prefix)\(
165+
arg.kind == .positional
166+
? """
167+
\(shouldOfferCompletionsForPositionalFunctionName) "\(commandContext.joined(separator: separator))" \({
168+
positionalIndex += 1
169+
return "\(positionalIndex)\(repeatingPositionalComparison)"
170+
}())
171+
"""
172+
: """
173+
\(shouldOfferCompletionsForFlagsOrOptionsFunctionName) "\(commandContext.joined(separator: separator))"\
174+
\((arg.isRepeating ? [] : arg.names ?? []).map { " \($0.name)" }.sorted().joined())
175+
"""
176+
)' \(argumentSegments(arg).joined(separator: separator))
137177
"""
138-
: ""
139-
)' \(argumentSegments(arg).joined(separator: separator))
140-
"""
141178
}
142179

143180
positionalIndex += 1
144181

145182
return
146183
argumentCompletions
147184
+ subcommands.map {
148-
"\(prefix) \(positionalIndex)' -fa '\($0.commandName)' -d '\($0.abstract?.fishEscapeForSingleQuotedString() ?? "")'"
185+
"""
186+
\(prefix)\(shouldOfferCompletionsForPositionalFunctionName) "\(commandContext.joined(separator: separator))"\
187+
\(positionalIndex)' -fa '\($0.commandName)' -d '\($0.abstract?.fishEscapeForSingleQuotedString() ?? "")'
188+
"""
149189
}
150190
+ subcommands.flatMap(\.completions)
151191
}
@@ -247,8 +287,16 @@ extension CommandInfoV0 {
247287
"""
248288
}
249289

250-
private var shouldOfferCompletionsForFunctionName: String {
251-
"\(completionFunctionPrefix)_should_offer_completions_for"
290+
private var shouldOfferCompletionsForFlagsOrOptionsFunctionName: String {
291+
"\(completionFunctionPrefix)_should_offer_completions_for_flags_or_options"
292+
}
293+
294+
private var shouldOfferCompletionsForPositionalFunctionName: String {
295+
"\(completionFunctionPrefix)_should_offer_completions_for_positional"
296+
}
297+
298+
private var parseTokensFunctionName: String {
299+
"\(completionFunctionPrefix)_parse_tokens"
252300
}
253301

254302
private var tokensFunctionName: String {
@@ -289,7 +337,7 @@ extension ArgumentInfoV0 {
289337
private func optionSpecRequiresValue(_ optionSpec: String) -> String {
290338
switch kind {
291339
case .option:
292-
return "\(optionSpec)="
340+
return "\(optionSpec)=\(isRepeating ? "+" : "")"
293341
default:
294342
return optionSpec
295343
}

0 commit comments

Comments
 (0)