@@ -5,15 +5,15 @@ function sanitizeSummary(description?: string): string {
55 if ( description === undefined ) {
66 return ''
77 }
8- return description
9- . replace ( / ( [ ` " ] ) / g, '\\\\\\$1' ) // backticks and double-quotes require triple-backslashes
10- // eslint-disable-next-line no-useless-escape
11- . replace ( / ( [ \[ \] ] ) / g, '\\\\$1' ) // square brackets require double-backslashes
12- . split ( '\n' ) [ 0 ] // only use the first line
8+ return (
9+ description
10+ . replace ( / ( [ ` " ] ) / g, '\\\\\\$1' ) // backticks and double-quotes require triple-backslashes
11+ // eslint-disable-next-line no-useless-escape
12+ . replace ( / ( [ \[ \] ] ) / g, '\\\\$1' ) // square brackets require double-backslashes
13+ . split ( '\n' ) [ 0 ]
14+ ) // only use the first line
1315}
1416
15- const argTemplate = ' "%s")\n %s\n ;;\n'
16-
1717type CommandCompletion = {
1818 id : string ;
1919 summary : string ;
@@ -30,34 +30,37 @@ type Topic = {
3030}
3131
3232export default class ZshCompWithSpaces {
33- protected config : Config ;
33+ protected config : Config
3434
3535 private topics : Topic [ ]
3636
3737 private commands : CommandCompletion [ ]
3838
39- private _coTopics ? : string [ ]
39+ private coTopics : string [ ]
4040
4141 constructor ( config : Config ) {
4242 this . config = config
4343 this . topics = this . getTopics ( )
4444 this . commands = this . getCommands ( )
45+ this . coTopics = this . getCoTopics ( )
4546 }
4647
4748 public generate ( ) : string {
4849 const firstArgs : { id : string ; summary ?: string } [ ] = [ ]
4950
5051 this . topics . forEach ( t => {
51- if ( ! t . name . includes ( ':' ) ) firstArgs . push ( {
52- id : t . name ,
53- summary : t . description ,
54- } )
52+ if ( ! t . name . includes ( ':' ) )
53+ firstArgs . push ( {
54+ id : t . name ,
55+ summary : t . description ,
56+ } )
5557 } )
5658 this . commands . forEach ( c => {
57- if ( ! firstArgs . find ( a => a . id === c . id ) && ! c . id . includes ( ':' ) ) firstArgs . push ( {
58- id : c . id ,
59- summary : c . summary ,
60- } )
59+ if ( ! firstArgs . find ( a => a . id === c . id ) && ! c . id . includes ( ':' ) )
60+ firstArgs . push ( {
61+ id : c . id ,
62+ summary : c . summary ,
63+ } )
6164 } )
6265
6366 const mainArgsCaseBlock = ( ) => {
@@ -66,24 +69,24 @@ export default class ZshCompWithSpaces {
6669 for ( const arg of firstArgs ) {
6770 if ( this . coTopics . includes ( arg . id ) ) {
6871 // coTopics already have a completion function.
69- caseBlock += `${ arg . id } )\n _${ this . config . bin } _${ arg . id } \n ;;\n`
72+ caseBlock += ` ${ arg . id } )\n _${ this . config . bin } _${ arg . id } \n ;;\n`
7073 } else {
7174 const cmd = this . commands . find ( c => c . id === arg . id )
7275
7376 if ( cmd ) {
7477 // if it's a command and has flags, inline flag completion statement.
7578 // skip it from the args statement if it doesn't accept any flag.
7679 if ( Object . keys ( cmd . flags ) . length > 0 ) {
77- caseBlock += `${ arg . id } )\n${ this . genZshFlagArgumentsBlock ( cmd . flags ) } ;; \n`
80+ caseBlock += ` ${ arg . id } )\n ${ this . genZshFlagArgumentsBlock ( cmd . flags ) } ;; \n`
7881 }
7982 } else {
8083 // it's a topic, redirect to its completion function.
81- caseBlock += `${ arg . id } )\n _${ this . config . bin } _${ arg . id } \n ;;\n`
84+ caseBlock += ` ${ arg . id } )\n _${ this . config . bin } _${ arg . id } \n ;;\n`
8285 }
8386 }
8487 }
8588
86- caseBlock += 'esac\n '
89+ caseBlock += ' esac'
8790
8891 return caseBlock
8992 }
@@ -92,7 +95,6 @@ export default class ZshCompWithSpaces {
9295`#compdef ${ this . config . bin }
9396
9497${ this . topics . map ( t => this . genZshTopicCompFun ( t . name ) ) . join ( '\n' ) }
95-
9698_${ this . config . bin } () {
9799 local context state state_descr line
98100 typeset -A opt_args
@@ -101,11 +103,11 @@ _${this.config.bin}() {
101103
102104 case "$state" in
103105 cmds)
104- ${ this . genZshValuesBlock ( firstArgs ) }
105- ;;
106+ ${ this . genZshValuesBlock ( { subArgs : firstArgs } ) }
107+ ;;
106108 args)
107109 ${ mainArgsCaseBlock ( ) }
108- ;;
110+ ;;
109111 esac
110112}
111113
@@ -114,18 +116,18 @@ _${this.config.bin}
114116 return compFunc
115117 }
116118
117- private genZshFlagArgumentsBlock ( flags ?: CommandFlags ) : string {
119+ private genZshFlagArguments ( flags ?: CommandFlags ) : string {
118120 // if a command doesn't have flags make it only complete files
119121 // also add comp for the global `--help` flag.
120- if ( ! flags ) return '_arguments -S \\\n --help"[Show help for command]" "*: :_files'
122+ if ( ! flags ) return '--help"[Show help for command]" "*: :_files'
121123
122124 const flagNames = Object . keys ( flags )
123125
124126 // `-S`:
125127 // Do not complete flags after a ‘--’ appearing on the line, and ignore the ‘--’. For example, with -S, in the line:
126128 // foobar -x -- -y
127129 // the ‘-x’ is considered a flag, the ‘-y’ is considered an argument, and the ‘--’ is considered to be neither.
128- let argumentsBlock = '_arguments -S \\\n '
130+ let argumentsBlock = ''
129131
130132 for ( const flagName of flagNames ) {
131133 const f = flags [ flagName ]
@@ -145,27 +147,11 @@ _${this.config.bin}
145147 } else {
146148 flagSpec += `"(-${ f . char } --${ f . name } )"{-${ f . char } ,--${ f . name } }`
147149 }
148-
149- flagSpec += `"[${ f . summary } ]`
150-
151- if ( f . options ) {
152- flagSpec += `:${ f . name } options:(${ f . options ?. join ( ' ' ) } )"`
153- } else {
154- flagSpec += ':file:_files"'
155- }
150+ } else if ( f . multiple ) {
151+ // this flag can be present multiple times on the line
152+ flagSpec += `"*"--${ f . name } `
156153 } else {
157- if ( f . multiple ) {
158- // this flag can be present multiple times on the line
159- flagSpec += '"*"'
160- }
161-
162- flagSpec += `--${ f . name } "[${ f . summary } ]:`
163-
164- if ( f . options ) {
165- flagSpec += `${ f . name } options:(${ f . options . join ( ' ' ) } )"`
166- } else {
167- flagSpec += 'file:_files"'
168- }
154+ flagSpec += `--${ f . name } `
169155 }
170156 } else if ( f . char ) {
171157 // Flag.Boolean
@@ -175,6 +161,16 @@ _${this.config.bin}
175161 flagSpec += `--${ f . name } "[${ f . summary } ]"`
176162 }
177163
164+ if ( f . type === 'option' ) {
165+ flagSpec += `"[${ f . summary } ]`
166+
167+ if ( f . options ) {
168+ flagSpec += `:${ f . name } options:(${ f . options ?. join ( ' ' ) } )"`
169+ } else {
170+ flagSpec += ':file:_files"'
171+ }
172+ }
173+
178174 flagSpec += ' \\\n'
179175 argumentsBlock += flagSpec
180176 }
@@ -186,65 +182,78 @@ _${this.config.bin}
186182 return argumentsBlock
187183 }
188184
189- private genZshValuesBlock ( subArgs : { id : string ; summary ?: string } [ ] ) : string {
190- let valuesBlock = '_values "completions" \\\n'
191-
192- subArgs . forEach ( subArg => {
193- valuesBlock += `"${ subArg . id } [${ subArg . summary } ]" \\\n`
185+ private genZshFlagArgumentsBlock ( flags ?: CommandFlags ) : string {
186+ let argumentsBlock = '_arguments -S \\'
187+ this . genZshFlagArguments ( flags )
188+ . split ( '\n' )
189+ . forEach ( f => {
190+ argumentsBlock += `\n ${ f } `
194191 } )
195192
196- return valuesBlock
193+ return argumentsBlock
197194 }
198195
199- private genZshTopicCompFun ( id : string ) : string {
200- const coTopics : string [ ] = [ ]
196+ private genZshValuesBlock ( options : { id ?: string ; subArgs : Array < { id : string ; summary ?: string } > } ) : string {
197+ let valuesBlock = '_values "completions"'
198+ const { id, subArgs} = options
201199
202- for ( const topic of this . topics ) {
203- for ( const cmd of this . commands ) {
204- if ( topic . name === cmd . id ) {
205- coTopics . push ( topic . name )
206- }
200+ subArgs . forEach ( subArg => {
201+ valuesBlock += ` \\\n "${ subArg . id } [${ subArg . summary } ]"`
202+ } )
203+
204+ if ( id ) {
205+ const cflags = this . commands . find ( c => c . id === id ) ?. flags
206+
207+ if ( cflags ) {
208+ // eslint-disable-next-line no-template-curly-in-string
209+ valuesBlock += ' \\\n "${flags[@]}"'
207210 }
208211 }
209212
210- const flagArgsTemplate = ' "%s")\n %s\n ;;\n'
213+ return valuesBlock
214+ }
211215
216+ private genZshTopicCompFun ( id : string ) : string {
212217 const underscoreSepId = id . replace ( / : / g, '_' )
213218 const depth = id . split ( ':' ) . length
214219
215- const isCotopic = coTopics . includes ( id )
220+ const isCotopic = this . coTopics . includes ( id )
216221
217- if ( isCotopic ) {
218- const compFuncName = `${ this . config . bin } _${ underscoreSepId } `
222+ let flags = ''
219223
220- const coTopicCompFunc =
221- `_${ compFuncName } () {
222- _${ compFuncName } _flags() {
223- local context state state_descr line
224- typeset -A opt_args
224+ if ( id ) {
225+ const cflags = this . commands . find ( c => c . id === id ) ?. flags
225226
226- ${ this . genZshFlagArgumentsBlock ( this . commands . find ( c => c . id === id ) ?. flags ) }
227- }
227+ if ( cflags ) {
228+ flags += '\n'
229+ this . genZshFlagArguments ( cflags )
230+ . split ( '\n' )
231+ . forEach ( f => {
232+ flags += ` ${ f } \n`
233+ } )
234+ flags += ' '
235+ }
236+ }
228237
238+ if ( isCotopic ) {
239+ const coTopicCompFunc = `_${ this . config . bin } _${ underscoreSepId } () {
229240 local context state state_descr line
230241 typeset -A opt_args
231242
243+ local -a flags=(%s)
244+
232245 _arguments -C "1: :->cmds" "*: :->args"
233246
234247 case "$state" in
235248 cmds)
236- if [[ "\${words[CURRENT]}" == -* ]]; then
237- _${ compFuncName } _flags
238- else
239- %s
240- fi
249+ %s
241250 ;;
242251 args)
243- case $line[1] in
244- %s
245- *)
246- _ ${ compFuncName } _flags
247- ;;
252+ case $line[1] in%s
253+ *)
254+ _arguments -S \\
255+ "\${flags[@]}"
256+ ;;
248257 esac
249258 ;;
250259 esac
@@ -264,24 +273,24 @@ _${this.config.bin}
264273 summary : t . description ,
265274 } )
266275
267- argsBlock += util . format ( argTemplate , subArg , `_${ this . config . bin } _${ underscoreSepId } _${ subArg } ` )
276+ argsBlock += util . format ( '\n "%s")\n %s\n ;;' , subArg , `_${ this . config . bin } _${ underscoreSepId } _${ subArg } ` )
268277 } )
269278
270279 this . commands
271280 . filter ( c => c . id . startsWith ( id + ':' ) && c . id . split ( ':' ) . length === depth + 1 )
272281 . forEach ( c => {
273- if ( coTopics . includes ( c . id ) ) return
282+ if ( this . coTopics . includes ( c . id ) ) return
274283 const subArg = c . id . split ( ':' ) [ depth ]
275284
276285 subArgs . push ( {
277286 id : subArg ,
278287 summary : c . summary ,
279288 } )
280289
281- argsBlock += util . format ( flagArgsTemplate , subArg , this . genZshFlagArgumentsBlock ( c . flags ) )
290+ argsBlock += util . format ( '\n "%s")\n _arguments -C "*::arg:->args"\n %s\n ;;' , subArg , this . genZshFlagArgumentsBlock ( c . flags ) )
282291 } )
283292
284- return util . format ( coTopicCompFunc , this . genZshValuesBlock ( subArgs ) , argsBlock )
293+ return util . format ( coTopicCompFunc , flags , this . genZshValuesBlock ( { id , subArgs} ) , argsBlock )
285294 }
286295 let argsBlock = ''
287296
@@ -296,21 +305,21 @@ _${this.config.bin}
296305 summary : t . description ,
297306 } )
298307
299- argsBlock += util . format ( argTemplate , subArg , `_${ this . config . bin } _${ underscoreSepId } _${ subArg } ` )
308+ argsBlock += util . format ( '\n "%s")\n %s\n ;;' , subArg , `_${ this . config . bin } _${ underscoreSepId } _${ subArg } ` )
300309 } )
301310
302311 this . commands
303312 . filter ( c => c . id . startsWith ( id + ':' ) && c . id . split ( ':' ) . length === depth + 1 )
304313 . forEach ( c => {
305- if ( coTopics . includes ( c . id ) ) return
314+ if ( this . coTopics . includes ( c . id ) ) return
306315 const subArg = c . id . split ( ':' ) [ depth ]
307316
308317 subArgs . push ( {
309318 id : subArg ,
310319 summary : c . summary ,
311320 } )
312321
313- argsBlock += util . format ( flagArgsTemplate , subArg , this . genZshFlagArgumentsBlock ( c . flags ) )
322+ argsBlock += util . format ( `\n "%s") ${ flags ? '\n _arguments -C "*::arg:->args"' : '' } \n %s\n ;;` , subArg , this . genZshFlagArgumentsBlock ( c . flags ) )
314323 } )
315324
316325 const topicCompFunc =
@@ -322,22 +331,19 @@ _${this.config.bin}
322331
323332 case "$state" in
324333 cmds)
325- %s
334+ %s
326335 ;;
327336 args)
328- case $line[1] in
329- %s
337+ case $line[1] in%s
330338 esac
331339 ;;
332- esac
340+ esac
333341}
334342`
335- return util . format ( topicCompFunc , this . genZshValuesBlock ( subArgs ) , argsBlock )
343+ return util . format ( topicCompFunc , this . genZshValuesBlock ( { subArgs} ) , argsBlock )
336344 }
337345
338- private get coTopics ( ) : string [ ] {
339- if ( this . _coTopics ) return this . _coTopics
340-
346+ private getCoTopics ( ) : string [ ] {
341347 const coTopics : string [ ] = [ ]
342348
343349 for ( const topic of this . topics ) {
@@ -348,9 +354,7 @@ _${this.config.bin}
348354 }
349355 }
350356
351- this . _coTopics = coTopics
352-
353- return this . _coTopics
357+ return coTopics
354358 }
355359
356360 private getTopics ( ) : Topic [ ] {
0 commit comments