@@ -17,43 +17,39 @@ extension [ParsableCommand.Type] {
17
17
// - first is guaranteed non-empty in the one place where this computed var is used.
18
18
let commandName = first!. _commandName
19
19
return """
20
- # A function which filters options which starts with " - " from $argv.
21
- function \( commandsAndPositionalsFunctionName)
22
- set -l results
23
- for i in (seq (count $argv))
24
- switch (echo $argv[$i] | string sub -l 1)
25
- case '-'
26
- case '*'
27
- echo $argv[$i]
20
+ function \( commandsAndPositionalsFunctionName) -S
21
+ switch $POSITIONALS[1]
22
+ \( commandCases) \
28
23
end
24
+ case '*'
25
+ set COMMANDS $POSITIONALS[1]
26
+ set -e POSITIONALS[1]
29
27
end
30
28
end
31
29
32
- function \( usingCommandFunctionName)
33
- set -gx \( CompletionShell . shellEnvironmentVariableName) fish
34
- set -gx \( CompletionShell . shellVersionEnvironmentVariableName) " $FISH_VERSION "
35
- set -l commands_and_positionals ( \( commandsAndPositionalsFunctionName) (commandline -opc))
36
- set -l expected_commands (string split -- ' \( separator) ' $argv[1])
37
- set -l subcommands (string split -- ' \( separator) ' $argv[2])
38
- if [ (count $commands_and_positionals) -ge (count $expected_commands) ]
39
- for i in (seq (count $expected_commands))
40
- if [ $commands_and_positionals[$i] != $expected_commands[$i] ]
41
- return 1
42
- end
43
- end
44
- if [ (count $commands_and_positionals) -eq (count $expected_commands) ]
45
- return 0
46
- end
47
- if [ (count $subcommands) -gt 1 ]
48
- for i in (seq (count $subcommands))
49
- if [ $commands_and_positionals[(math (count $expected_commands) + 1)] = $subcommands[$i] ]
50
- return 1
51
- end
52
- end
53
- end
54
- return 0
30
+ function \( commandsAndPositionalsFunctionName) _helper -S -a argparse_options -a option_specs
31
+ set -a COMMANDS $POSITIONALS[1]
32
+ set -e POSITIONALS[1]
33
+ if test -z $argparse_options
34
+ argparse -n (string join -- ' \( separator) ' $COMMANDS) (string split -- ' \( separator) ' $option_specs) -- $POSITIONALS 2> /dev/null
35
+ set POSITIONALS $argv
36
+ else
37
+ argparse (string split -- ' \( separator) ' $argparse_options) -n (string join -- ' \( separator) ' $COMMANDS) (string split -- ' \( separator) ' $option_specs) -- $POSITIONALS 2> /dev/null
38
+ set POSITIONALS $argv
55
39
end
56
- return 1
40
+ end
41
+
42
+ function \( usingCommandFunctionName) -a expected_commands
43
+ set COMMANDS
44
+ set POSITIONALS (commandline -opc)
45
+ \( commandsAndPositionalsFunctionName)
46
+ test " $COMMANDS " = $expected_commands
47
+ end
48
+
49
+ function \( positionalIndexFunctionName)
50
+ set POSITIONALS (commandline -opc)
51
+ \( commandsAndPositionalsFunctionName)
52
+ math (count $POSITIONALS) + 1
57
53
end
58
54
59
55
function \( completeDirectoriesFunctionName)
@@ -63,38 +59,51 @@ extension [ParsableCommand.Type] {
63
59
printf '%s \\ n' $subdirs
64
60
end
65
61
62
+ function \( customCompletionFunctionName)
63
+ set -x \( CompletionShell . shellEnvironmentVariableName) fish
64
+ set -x \( CompletionShell . shellVersionEnvironmentVariableName) $FISH_VERSION
65
+
66
+ set tokens (commandline -op)
67
+ if test -z (commandline -ot)
68
+ set index (count (commandline -opc))
69
+ set tokens $tokens[..$index] \\ ' \\ ' $tokens[$(math $index + 1)..]
70
+ end
71
+ command $tokens[1] $argv $tokens
72
+ end
73
+
66
74
complete -c \( commandName) -f
67
75
\( completions. joined ( separator: " \n " ) )
68
76
"""
69
77
}
70
78
71
- private var completions : [ String ] {
72
- guard let type = last else {
73
- fatalError ( )
74
- }
75
- var subcommands = type. configuration. subcommands
76
- . filter { $0. configuration. shouldDisplay }
77
-
78
- if count == 1 {
79
- subcommands. addHelpSubcommandIfMissing ( )
80
- }
79
+ private var commandCases : String {
80
+ let subcommands = subcommands
81
+ // swift-format-ignore: NeverForceUnwrap
82
+ // Precondition: last is guaranteed to be non-empty
83
+ return """
84
+ case ' \( last!. _commandName) '
85
+ \( commandsAndPositionalsFunctionName) _helper ' \(
86
+ subcommands. isEmpty ? " " : " -s "
87
+ ) ' ' \( completableArguments. compactMap ( \. optionSpec) . map { " \( $0) " } . joined ( separator: separator) ) ' \(
88
+ subcommands. isEmpty ? " " : " \n switch $POSITIONALS[1] " )
89
+ \( subcommands. map { ( self + [ $0] ) . commandCases } . joined ( separator: " " ) )
90
+ """ . indentingEachLine ( by: 4 )
91
+ }
81
92
93
+ private var completions : [ String ] {
82
94
// swift-format-ignore: NeverForceUnwrap
83
95
// Precondition: first is guaranteed to be non-empty
84
96
let commandName = first!. _commandName
85
- var prefix = """
97
+ let prefix = """
86
98
complete -c \( commandName) \
87
99
-n ' \( usingCommandFunctionName) \
88
100
" \( map { $0. _commandName } . joined ( separator: separator) ) "
89
101
"""
90
- if !subcommands. isEmpty {
91
- prefix +=
92
- " \" \( subcommands. map { $0. _commandName } . joined ( separator: separator) ) \" "
93
- }
94
- prefix += " ' "
95
102
96
- func complete( suggestion: String ) -> String {
97
- " \( prefix) \( suggestion) "
103
+ let subcommands = subcommands
104
+
105
+ func complete( suggestion: String , extraTests: [ String ] = [ ] ) -> String {
106
+ " \( prefix) \( extraTests. map { " ; \( $0) " } . joined ( ) ) ' \( suggestion) "
98
107
}
99
108
100
109
let subcommandCompletions : [ String ] = subcommands. map { subcommand in
@@ -104,11 +113,26 @@ extension [ParsableCommand.Type] {
104
113
)
105
114
}
106
115
116
+ var positionalIndex = 0
117
+
107
118
let argumentCompletions =
108
- argumentsForHelp ( visibility: . default)
109
- . compactMap { argumentSegments ( $0) }
110
- . map { $0. joined ( separator: separator) }
111
- . map { complete ( suggestion: $0) }
119
+ completableArguments
120
+ . map { ( arg: ArgumentDefinition ) in
121
+ complete (
122
+ suggestion: argumentSegments ( arg) . joined ( separator: separator) ,
123
+ extraTests: arg. isPositional
124
+ ? [
125
+ """
126
+ and test ( \( positionalIndexFunctionName) ) \
127
+ -eq \( {
128
+ positionalIndex += 1
129
+ return positionalIndex
130
+ } ( ) )
131
+ """
132
+ ]
133
+ : [ ]
134
+ )
135
+ }
112
136
113
137
let completionsFromSubcommands = subcommands. flatMap { subcommand in
114
138
( self + [ subcommand] ) . completions
@@ -118,23 +142,49 @@ extension [ParsableCommand.Type] {
118
142
completionsFromSubcommands + argumentCompletions + subcommandCompletions
119
143
}
120
144
121
- private func argumentSegments( _ arg: ArgumentDefinition ) -> [ String ] ? {
122
- guard arg. help. visibility. base == . default
123
- else { return nil }
145
+ private var subcommands : Self {
146
+ guard
147
+ let command = last,
148
+ ArgumentSet ( command, visibility: . default, parent: nil )
149
+ . filter ( \. isPositional) . isEmpty
150
+ else {
151
+ return [ ]
152
+ }
153
+ var subcommands = command. configuration. subcommands
154
+ . filter { $0. configuration. shouldDisplay }
155
+ if count == 1 {
156
+ subcommands. addHelpSubcommandIfMissing ( )
157
+ }
158
+ return subcommands
159
+ }
160
+
161
+ private var completableArguments : [ ArgumentDefinition ] {
162
+ argumentsForHelp ( visibility: . default) . compactMap { arg in
163
+ switch arg. completion. kind {
164
+ case . default where arg. names. isEmpty:
165
+ return nil
166
+ default :
167
+ return
168
+ arg. help. visibility. base == . default
169
+ ? arg
170
+ : nil
171
+ }
172
+ }
173
+ }
124
174
175
+ private func argumentSegments( _ arg: ArgumentDefinition ) -> [ String ] {
125
176
var results : [ String ] = [ ]
126
177
127
178
if !arg. names. isEmpty {
128
179
results += arg. names. map { $0. asFishSuggestion }
129
- }
130
-
131
- if !arg. help. abstract. isEmpty {
132
- results += [ " -d ' \( arg. help. abstract. fishEscapeForSingleQuotedString ( ) ) ' " ]
180
+ if !arg. help. abstract. isEmpty {
181
+ results += [
182
+ " -d ' \( arg. help. abstract. fishEscapeForSingleQuotedString ( ) ) ' "
183
+ ]
184
+ }
133
185
}
134
186
135
187
switch arg. completion. kind {
136
- case . default where arg. names. isEmpty:
137
- return nil
138
188
case . default:
139
189
break
140
190
case . list( let list) :
@@ -170,9 +220,7 @@ extension [ParsableCommand.Type] {
170
220
case . custom:
171
221
results += [
172
222
"""
173
- -rfka '( \
174
- set command (commandline -op)[1];command $command \( arg. customCompletionCall ( self ) ) (commandline -op) \
175
- )'
223
+ -rfka '( \( customCompletionFunctionName) \( arg. customCompletionCall ( self ) ) )'
176
224
"""
177
225
]
178
226
}
@@ -192,11 +240,54 @@ extension [ParsableCommand.Type] {
192
240
" _swift_ \( first!. _commandName) _using_command "
193
241
}
194
242
243
+ private var positionalIndexFunctionName : String {
244
+ // swift-format-ignore: NeverForceUnwrap
245
+ // Precondition: first is guaranteed to be non-empty
246
+ " _swift_ \( first!. _commandName) _positional_index "
247
+ }
248
+
195
249
private var completeDirectoriesFunctionName : String {
196
250
// swift-format-ignore: NeverForceUnwrap
197
251
// Precondition: first is guaranteed to be non-empty
198
252
" _swift_ \( first!. _commandName) _complete_directories "
199
253
}
254
+
255
+ private var customCompletionFunctionName : String {
256
+ // swift-format-ignore: NeverForceUnwrap
257
+ // Precondition: first is guaranteed to be non-empty
258
+ " _swift_ \( first!. _commandName) _custom_completion "
259
+ }
260
+ }
261
+
262
+ extension ArgumentDefinition {
263
+ fileprivate var optionSpec : String ? {
264
+ guard let shortName = name ( . short) else {
265
+ guard let longName = name ( . long) else {
266
+ return nil
267
+ }
268
+ return optionSpecRequiresValue ( longName)
269
+ }
270
+ guard let longName = name ( . long) else {
271
+ return optionSpecRequiresValue ( shortName)
272
+ }
273
+ return optionSpecRequiresValue ( " \( shortName) / \( longName) " )
274
+ }
275
+
276
+ private func name( _ nameType: Name . Case ) -> String ? {
277
+ names. first ( where: {
278
+ $0. case == nameType
279
+ } ) ?
280
+ . valueString
281
+ }
282
+
283
+ private func optionSpecRequiresValue( _ optionSpec: String ) -> String {
284
+ switch update {
285
+ case . unary:
286
+ return " \( optionSpec) = "
287
+ default :
288
+ return optionSpec
289
+ }
290
+ }
200
291
}
201
292
202
293
extension Name {
0 commit comments