9
9
//
10
10
//===----------------------------------------------------------------------===//
11
11
12
- extension [ ParsableCommand . Type ] {
13
- /// Generates a Zsh completion script for the given command.
12
+ #if swift(>=6.0)
13
+ internal import ArgumentParserToolInfo
14
+ #else
15
+ import ArgumentParserToolInfo
16
+ #endif
17
+
18
+ extension ToolInfoV0 {
14
19
var zshCompletionScript : String {
20
+ command. zshCompletionScript
21
+ }
22
+ }
23
+
24
+ extension CommandInfoV0 {
25
+ fileprivate var zshCompletionScript : String {
15
26
// swift-format-ignore: NeverForceUnwrap
16
27
// Preconditions:
17
28
// - first must be non-empty for a zsh completion script to be of use.
18
29
// - first is guaranteed non-empty in the one place where this computed var is used.
19
- let commandName = first!. _commandName
20
- return """
21
- #compdef \( commandName)
30
+ """
31
+ #compdef \( commandName)
22
32
23
- \( completeFunctionName) () {
24
- local -ar non_empty_completions=( " ${@:#(|:*)} " )
25
- local -ar empty_completions=( " ${(M)@:#(|:*)} " )
26
- _describe -V '' non_empty_completions -- empty_completions -P $' \\ ' \\ ''
27
- }
33
+ \( completeFunctionName) () {
34
+ local -ar non_empty_completions=( " ${@:#(|:*)} " )
35
+ local -ar empty_completions=( " ${(M)@:#(|:*)} " )
36
+ _describe -V '' non_empty_completions -- empty_completions -P $' \\ ' \\ ''
37
+ }
28
38
29
- \( customCompleteFunctionName) () {
30
- local -a completions
31
- completions=( " ${(@f) " $( " ${command_name} " " ${@} " " ${command_line[@]} " ) " } " )
32
- if [[ " ${#completions[@]} " -gt 1 ]]; then
33
- \( completeFunctionName) " ${completions[@]:0:-1} "
34
- fi
35
- }
39
+ \( customCompleteFunctionName) () {
40
+ local -a completions
41
+ completions=( " ${(@f) " $( " ${command_name} " " ${@} " " ${command_line[@]} " ) " } " )
42
+ if [[ " ${#completions[@]} " -gt 1 ]]; then
43
+ \( completeFunctionName) " ${completions[@]:0:-1} "
44
+ fi
45
+ }
36
46
37
- \( cursorIndexInCurrentWordFunctionName) () {
38
- if [[ -z " ${QIPREFIX}${IPREFIX}${PREFIX} " ]]; then
39
- printf 0
40
- else
41
- printf %s " ${#${(z)LBUFFER}[-1]} "
42
- fi
43
- }
47
+ \( cursorIndexInCurrentWordFunctionName) () {
48
+ if [[ -z " ${QIPREFIX}${IPREFIX}${PREFIX} " ]]; then
49
+ printf 0
50
+ else
51
+ printf %s " ${#${(z)LBUFFER}[-1]} "
52
+ fi
53
+ }
44
54
45
- \( completionFunctions) \
46
- \( completionFunctionName ( ) )
47
- """
55
+ \( completionFunctions) \
56
+ \( completionFunctionName)
57
+ """
48
58
}
49
59
50
60
private var completionFunctions : String {
51
- guard let type = last else { return " " }
52
- let functionName = completionFunctionName ( )
53
- let isRootCommand = count == 1
61
+ let functionName = completionFunctionName
54
62
55
- let argumentSpecsAndSetupScripts = argumentsForHelp ( visibility: . default)
56
- . compactMap { argumentSpecAndSetupScript ( $0) }
63
+ let argumentSpecsAndSetupScripts = ( arguments ?? [ ] ) . compactMap {
64
+ argumentSpecAndSetupScript ( $0)
65
+ }
57
66
var argumentSpecs = argumentSpecsAndSetupScripts. map ( \. argumentSpec)
58
67
let setupScripts = argumentSpecsAndSetupScripts. compactMap ( \. setupScript)
59
68
60
- var subcommands = type. configuration. subcommands
61
- . filter { $0. configuration. shouldDisplay }
69
+ let subcommands = ( subcommands ?? [ ] ) . filter ( \. shouldDisplay)
62
70
63
71
let subcommandHandler : String
64
72
if subcommands. isEmpty {
@@ -67,17 +75,13 @@ extension [ParsableCommand.Type] {
67
75
argumentSpecs. append ( " '(-): :->command' " )
68
76
argumentSpecs. append ( " '(-)*:: :->arg' " )
69
77
70
- if isRootCommand {
71
- subcommands. addHelpSubcommandIfMissing ( )
72
- }
73
-
74
78
subcommandHandler = """
75
79
case " ${state} " in
76
80
command)
77
81
local -ar subcommands=(
78
82
\(
79
83
subcommands. map { """
80
- ' \( $0. _commandName . zshEscapeForSingleQuotedDescribeCompletion ( ) ) : \( $0. configuration . abstract. shellEscapeForSingleQuotedString ( ) ) '
84
+ ' \( $0. commandName . zshEscapeForSingleQuotedDescribeCompletion ( ) ) : \( $0. abstract? . shellEscapeForSingleQuotedString ( ) ?? " " ) '
81
85
"""
82
86
}
83
87
. joined ( separator: " \n " )
@@ -87,7 +91,7 @@ extension [ParsableCommand.Type] {
87
91
;;
88
92
arg)
89
93
case " ${words[1]} " in
90
- \( subcommands. map { $0 . _commandName } . joined ( separator: " | " ) ) )
94
+ \( subcommands. map ( \ . commandName ) . joined ( separator: " | " ) ) )
91
95
" \( functionName) _${words[1]} "
92
96
;;
93
97
esac
@@ -99,7 +103,7 @@ extension [ParsableCommand.Type] {
99
103
100
104
return """
101
105
\( functionName) () {
102
- \( isRootCommand
106
+ \( ( superCommands ?? [ ] ) . isEmpty
103
107
? """
104
108
emulate -RL zsh -G
105
109
setopt extendedglob nullglob numericglobsort
@@ -131,52 +135,55 @@ extension [ParsableCommand.Type] {
131
135
return " ${ret} "
132
136
}
133
137
134
- \( subcommands. map { ( self + [ $0 ] ) . completionFunctions } . joined ( ) )
138
+ \( subcommands. map ( \ . completionFunctions) . joined ( ) )
135
139
"""
136
140
}
137
141
138
142
private func argumentSpecAndSetupScript(
139
- _ arg: ArgumentDefinition
143
+ _ arg: ArgumentInfoV0
140
144
) -> ( argumentSpec: String , setupScript: String ? ) ? {
141
- guard arg. help . visibility . base == . default else { return nil }
145
+ guard arg. shouldDisplay else { return nil }
142
146
143
147
let line : String
144
- switch arg. names. count {
148
+ let names = arg. names ?? [ ]
149
+ switch names. count {
145
150
case 0 :
146
- line = arg. help . options . contains ( . isRepeating) ? " * " : " "
151
+ line = arg. isRepeating ? " * " : " "
147
152
case 1 :
153
+ // swift-format-ignore: NeverForceUnwrap
154
+ // Preconditions: names has exactly one element.
148
155
line = """
149
- \( arg. isRepeatingOption ? " * " : " " ) \( arg . names [ 0 ] . synopsisString . zshEscapeForSingleQuotedOptionSpec ( ) ) \( arg. zshCompletionAbstract )
156
+ \( arg. isRepeatingOption ? " * " : " " ) \( names. first! . commonCompletionSynopsisString ( ) . zshEscapeForSingleQuotedOptionSpec ( ) ) \( arg. completionAbstract )
150
157
"""
151
158
default :
152
- let synopses = arg . names. map {
153
- $0. synopsisString . zshEscapeForSingleQuotedOptionSpec ( )
159
+ let synopses = names. map {
160
+ $0. commonCompletionSynopsisString ( ) . zshEscapeForSingleQuotedOptionSpec ( )
154
161
}
155
162
line = """
156
163
\( arg. isRepeatingOption ? " * " : " ( \( synopses. joined ( separator: " " ) ) ) " ) ' \
157
164
{ \( synopses. joined ( separator: " , " ) ) } \
158
- ' \( arg. zshCompletionAbstract )
165
+ ' \( arg. completionAbstract )
159
166
"""
160
167
}
161
168
162
- switch arg. update {
163
- case . unary :
169
+ switch arg. kind {
170
+ case . option , . positional :
164
171
let ( argumentAction, setupScript) = argumentActionAndSetupScript ( arg)
165
172
return (
166
- " ' \( line) : \( arg. valueName. zshEscapeForSingleQuotedOptionSpec ( ) ) : \( argumentAction) ' " ,
173
+ " ' \( line) : \( arg. valueName? . zshEscapeForSingleQuotedOptionSpec ( ) ?? " " ) : \( argumentAction) ' " ,
167
174
setupScript
168
175
)
169
- case . nullary :
176
+ case . flag :
170
177
return ( " ' \( line) ' " , nil )
171
178
}
172
179
}
173
180
174
181
/// Returns the zsh "action" for an argument completion string.
175
182
private func argumentActionAndSetupScript(
176
- _ arg: ArgumentDefinition
183
+ _ arg: ArgumentInfoV0
177
184
) -> ( argumentAction: String , setupScript: String ? ) {
178
- switch arg. completion . kind {
179
- case . default :
185
+ switch arg. completionKind {
186
+ case . none :
180
187
return ( " " , nil )
181
188
182
189
case . file( let extensions) :
@@ -206,51 +213,46 @@ extension [ParsableCommand.Type] {
206
213
207
214
case . custom, . customAsync:
208
215
return (
209
- " { \( customCompleteFunctionName) \( arg. customCompletionCall ( self ) ) \" ${current_word_index} \" \" $( \( cursorIndexInCurrentWordFunctionName) ) \" } " ,
216
+ " { \( customCompleteFunctionName) \( arg. commonCustomCompletionCall ( command : self ) ) \" ${current_word_index} \" \" $( \( cursorIndexInCurrentWordFunctionName) ) \" } " ,
210
217
nil
211
218
)
212
219
213
220
case . customDeprecated:
214
221
return (
215
- " { \( customCompleteFunctionName) \( arg. customCompletionCall ( self ) ) } " ,
222
+ " { \( customCompleteFunctionName) \( arg. commonCustomCompletionCall ( command : self ) ) } " ,
216
223
nil
217
224
)
218
225
}
219
226
}
220
227
221
- private func variableName( _ arg: ArgumentDefinition ) -> String {
222
- guard let argName = arg. names. preferredName else {
223
- return
224
- " \( shellVariableNamePrefix) _ \( arg. valueName. shellEscapeForVariableName ( ) ) "
228
+ private func variableName( _ arg: ArgumentInfoV0 ) -> String {
229
+ guard let argName = arg. preferredName else {
230
+ return " _ \( arg. valueName? . shellEscapeForVariableName ( ) ?? " " ) "
225
231
}
226
232
return
227
- " \( argName. case == . long ? " __ " : " _ " ) \( shellVariableNamePrefix ) _ \( argName. valueString . shellEscapeForVariableName ( ) ) "
233
+ " \( argName. kind == . long ? " ___ " : " __ " ) \( argName. name . shellEscapeForVariableName ( ) ) "
228
234
}
229
235
230
236
private var completeFunctionName : String {
231
- // swift-format-ignore: NeverForceUnwrap
232
- // Precondition: first is guaranteed to be non-empty
233
- " __ \( first!. _commandName) _complete "
237
+ " \( completionFunctionPrefix) _complete "
234
238
}
235
239
236
240
private var customCompleteFunctionName : String {
237
- // swift-format-ignore: NeverForceUnwrap
238
- // Precondition: first is guaranteed to be non-empty
239
- " __ \( first!. _commandName) _custom_complete "
241
+ " \( completionFunctionPrefix) _custom_complete "
240
242
}
241
243
242
244
private var cursorIndexInCurrentWordFunctionName : String {
243
- " __ \( first ? . _commandName ?? " " ) _cursor_index_in_current_word "
245
+ " \( completionFunctionPrefix ) _cursor_index_in_current_word "
244
246
}
245
247
}
246
248
247
- extension ArgumentDefinition {
249
+ extension ArgumentInfoV0 {
248
250
/// - returns: `true` if `self` is a flag or an option and can be tab-completed multiple times in one command line.
249
251
/// For example, `ssh` allows the `-L` option to be given multiple times, to establish multiple port forwardings.
250
252
fileprivate var isRepeatingOption : Bool {
251
253
guard
252
- case . named ( _ ) = kind,
253
- help . options . contains ( . isRepeating)
254
+ [ . flag , . option ] . contains ( kind) ,
255
+ isRepeating
254
256
else { return false }
255
257
256
258
switch parsingStrategy {
@@ -259,10 +261,9 @@ extension ArgumentDefinition {
259
261
}
260
262
}
261
263
262
- fileprivate var zshCompletionAbstract : String {
263
- help. abstract. isEmpty
264
- ? " "
265
- : " [ \( help. abstract. zshEscapeForSingleQuotedOptionSpec ( ) ) ] "
264
+ fileprivate var completionAbstract : String {
265
+ guard let abstract, !abstract. isEmpty else { return " " }
266
+ return " [ \( abstract. zshEscapeForSingleQuotedOptionSpec ( ) ) ] "
266
267
}
267
268
}
268
269
0 commit comments