@@ -59,11 +59,24 @@ function stripFigCursor(insertion: string) {
59
59
return insertion . slice ( 0 , index ) + suffix + '\u001b[D' . repeat ( suffix . length )
60
60
}
61
61
62
+ function getFigSeparator ( spec : Fig . Option ) {
63
+ return spec . requiresSeparator
64
+ ? ( typeof spec . requiresSeparator === 'string' ? spec . requiresSeparator : '=' )
65
+ : ( spec . requiresEquals ? '=' : undefined )
66
+ }
67
+
62
68
function getFigValues ( spec : Fig . Subcommand | Fig . Suggestion | Fig . Option ) {
63
69
if ( spec . insertValue ) {
64
70
return [ stripFigCursor ( spec . insertValue ) ]
65
71
}
66
- return normalizeArray ( spec . name )
72
+ const names = normalizeArray ( spec . name )
73
+ if ( 'requiresSeparator' in spec || 'requiresEquals' in spec ) {
74
+ const separator = getFigSeparator ( spec )
75
+ if ( separator ) {
76
+ return names . map ( name => name + separator )
77
+ }
78
+ }
79
+ return names
67
80
}
68
81
69
82
function getFigArgsLabel ( spec : Fig . Subcommand | Fig . Option , name : string ) {
@@ -136,22 +149,34 @@ function transformFigSubcommand(spec: Fig.Subcommand, query: string) {
136
149
} ) )
137
150
}
138
151
152
+ function isMatchFigOption ( value : string , raw : string | Fig . Option ) {
153
+ const spec = typeof raw === 'string' ? { name : raw } : raw
154
+ const names = normalizeArray ( spec . name )
155
+ const separator = getFigSeparator ( spec )
156
+ return names . some ( name => {
157
+ if ( separator ) {
158
+ return value . startsWith ( name + separator )
159
+ } else {
160
+ if ( value === name ) return true
161
+ // e.g. `-aL` matches both `-a` and `-L`
162
+ if ( / ^ - \w $ / . test ( name ) ) {
163
+ return / ^ - \w / . test ( value ) && value . includes ( name [ 1 ] )
164
+ }
165
+ return false
166
+ }
167
+ } )
168
+ }
169
+
139
170
function transformFigOption ( spec : Fig . Option , query : string , args : string [ ] ) {
140
171
if ( spec . hidden ) return [ ]
141
- let values = getFigValues ( spec )
142
- const separator = spec . requiresSeparator
143
- ? ( typeof spec . requiresSeparator === 'string' ? spec . requiresSeparator : '=' )
144
- : ( spec . requiresEquals ? '=' : undefined )
145
- if ( separator ) {
146
- values = values . map ( value => value + separator )
147
- }
172
+ const values = getFigValues ( spec )
148
173
const max = spec . isRepeatable
149
174
? ( typeof spec . isRepeatable === 'number' ? spec . isRepeatable : Infinity )
150
175
: 1
151
- const times = args . filter ( arg => {
152
- return values . some ( value => ( separator ? arg . startsWith ( value ) : arg === value ) )
153
- } ) . length
176
+ const times = args . filter ( arg => isMatchFigOption ( arg , spec ) ) . length
154
177
if ( times > max ) return [ ]
178
+ const exclusiveOn = spec . exclusiveOn ?? [ ]
179
+ if ( exclusiveOn . some ( exclusive => args . some ( arg => isMatchFigOption ( arg , exclusive ) ) ) ) return [ ]
155
180
return values . map < CommandCompletion > ( value => ( {
156
181
type : 'command' ,
157
182
query,
@@ -166,15 +191,26 @@ function transformFigOption(spec: Fig.Option, query: string, args: string[]) {
166
191
167
192
function getFigArgCompletions ( spec : Fig . Subcommand | Fig . Option , query : string , context : FigContext ) {
168
193
const specArgs = normalizeArray ( spec . args )
169
- return flatAsync ( specArgs . map ( arg => {
194
+ if ( ! specArgs . length ) return [ ]
195
+ // When inputting `--foo=bar`, if spec has name `--foo` and separator `=`, just make query to be `bar`
196
+ const separator = getFigSeparator ( spec )
197
+ const names = normalizeArray ( spec . name )
198
+ if ( separator ) {
199
+ const usage = names . find ( name => query . startsWith ( name + separator ) )
200
+ if ( usage ) {
201
+ query = query . slice ( usage . length + separator . length )
202
+ }
203
+ }
204
+ return flatAsync ( specArgs . map ( async arg => {
170
205
const generators = [
171
206
...( arg . template ? [ { template : arg . template } ] : [ ] ) ,
172
207
...normalizeArray ( arg . generators ) ,
173
208
]
174
- return flatAsync ( generators . map ( async generator => {
175
- const suggestions = await generateFigSuggestions ( generator , context )
176
- return suggestions . flatMap ( suggestion => transformFigSuggestion ( suggestion , query ) )
177
- } ) )
209
+ const suggestions = await flatAsync ( [
210
+ arg . suggestions ?? [ ] ,
211
+ ...generators . map ( generator => generateFigSuggestions ( generator , context ) ) ,
212
+ ] )
213
+ return suggestions . flatMap ( suggestion => transformFigSuggestion ( suggestion , query ) )
178
214
} ) )
179
215
}
180
216
@@ -197,18 +233,17 @@ async function getFigCompletions(
197
233
suggestions . flatMap ( suggestion => transformFigSuggestion ( suggestion , query , true ) ) ,
198
234
)
199
235
// Option args
200
- if ( args . length > 1 ) {
201
- // Option args since option spec will not pass to `getFigCompletions`
202
- const previousArg = args [ args . length - 2 ]
203
- const inputtingOption = options . find ( item => {
204
- const names = normalizeArray ( item . name )
205
- return names . includes ( previousArg )
206
- } )
207
- if ( inputtingOption ) {
208
- asyncCompletions . push (
209
- getFigArgCompletions ( inputtingOption , query , context ) ,
210
- )
211
- }
236
+ // Option args since option spec will not pass to `getFigCompletions`
237
+ const previousArg = args . length > 1 ? args [ args . length - 2 ] : undefined
238
+ const inputtingOption = options . find ( item => {
239
+ const separator = getFigSeparator ( item )
240
+ const optionArg = separator ? query : previousArg
241
+ return optionArg === undefined ? false : isMatchFigOption ( optionArg , item )
242
+ } )
243
+ if ( inputtingOption ) {
244
+ asyncCompletions . push (
245
+ getFigArgCompletions ( inputtingOption , query , context ) ,
246
+ )
212
247
}
213
248
// Subcommands (suggest if no subcommand, call recursively otherwise)
214
249
const subcommands = spec . subcommands ?? [ ]
0 commit comments