@@ -174,12 +174,18 @@ def shtab_prepare_action(action, parser) -> None:
174174 if isinstance (action , ActionTypeHint ):
175175 skip = getattr (action , "sub_add_kwargs" , {}).get ("skip" , set ())
176176 prefix = action .option_strings [0 ] if action .option_strings else None
177- choices = get_typehint_choices (action ._typehint , prefix , parser , skip )
177+ choices , require_prefix = get_typehint_choices (action ._typehint , prefix , parser , skip )
178178 if shtab_shell .get () == "bash" :
179179 message = f"Expected type: { type_to_str (action ._typehint )} "
180180 if action .option_strings == []:
181181 message = f"Argument: { action .dest } ; " + message
182- add_bash_typehint_completion (parser , action , message , choices )
182+ add_bash_typehint_completion (
183+ parser ,
184+ action ,
185+ message ,
186+ choices ,
187+ require_prefix = require_prefix ,
188+ )
183189 choices = None
184190 elif isinstance (action , _ActionHelpClassPath ):
185191 choices = get_help_class_choices (action ._typehint )
@@ -197,15 +203,23 @@ def shtab_prepare_action(action, parser) -> None:
197203 fi
198204}
199205%(name)s() {
200- local MATCH=( $(IFS=" " compgen -W "$1" "$2") )
206+ local REQUIRE_PREFIX="$4"
207+ local MATCH=()
208+ if [ "$REQUIRE_PREFIX" = 1 ] && [ -z "$2" ]; then
209+ MATCH=()
210+ else
211+ MATCH=( $(IFS=" " compgen -W "$1" "$2") )
212+ fi
201213 if [ ${#MATCH[@]} = 0 ]; then
202214 if [ "$COMP_TYPE" = 63 ]; then
203215 MATCHED=$(_jsonargparse_%%s_matched_choices "$1" "${MATCH[*]}")
204216 printf "%(b)s\\ n$3$MATCHED\\ n%(n)s" >&2
205217 kill -WINCH $$
206218 fi
207219 else
208- IFS=" " compgen -W "$1" "$2"
220+ for match in "${MATCH[@]}"; do
221+ echo "$match"
222+ done
209223 if [ "$COMP_TYPE" = 63 ]; then
210224 MATCHED=$(_jsonargparse_%%s_matched_choices "$1" "${MATCH[*]}")
211225 printf "%(b)s\\ n$3$MATCHED%(n)s" >&2
@@ -219,53 +233,77 @@ def shtab_prepare_action(action, parser) -> None:
219233}
220234
221235
222- def add_bash_typehint_completion (parser , action , message , choices ) -> None :
236+ def add_bash_typehint_completion (parser , action , message , choices , require_prefix = False ) -> None :
223237 fn_typehint = norm_name (bash_compgen_typehint_name % shtab_prog .get ())
224238 fn_name = parser .prog .replace (" [options] " , "_" )
225239 fn_name = norm_name (f"_jsonargparse_{ fn_name } _{ action .dest } _typehint" )
226- fn = '{fn_name}(){{ {fn_typehint} "{choices}" "$1" "{message}"; }}' .format (
240+ fn = '{fn_name}(){{ {fn_typehint} "{choices}" "$1" "{message}" {require_prefix} ; }}' .format (
227241 fn_name = fn_name ,
228242 fn_typehint = fn_typehint ,
229243 choices = " " .join (choices ),
230244 message = message ,
245+ require_prefix = 1 if require_prefix else 0 ,
231246 )
232247 shtab_preambles .get ().append (fn )
233248 action .complete = {"bash" : fn_name }
234249
235250
236- def get_typehint_choices (typehint , prefix , parser , skip , choices = None , added_subclasses = None ) -> list [str ]:
237- if choices is None :
238- choices = []
251+ def get_typehint_choices (typehint , prefix , parser , skip , added_subclasses = None ) -> tuple [list [str ], bool ]:
239252 if not added_subclasses :
240253 added_subclasses = set ()
241- if typehint is bool :
242- choices .extend (["true" , "false" ])
243- elif typehint is NoneType :
244- choices .append ("null" )
245- elif is_subclass (typehint , Enum ):
246- choices .extend (list (typehint .__members__ ))
247- else :
254+
255+ def get_choices_state (typehint ) -> tuple [list [str ], bool , bool ]:
256+ if typehint is bool :
257+ return ["true" , "false" ], True , False
258+ if typehint is NoneType :
259+ return ["null" ], True , False
260+ if is_subclass (typehint , Enum ):
261+ return list (typehint .__members__ ), True , False
262+
248263 origin = get_typehint_origin (typehint )
249264 if origin == Literal :
250- choices .extend ([str (a ) for a in typehint .__args__ if isinstance (a , (str , int , float ))])
251- elif origin == Union :
265+ choices = []
266+ for arg in typehint .__args__ :
267+ if isinstance (arg , bool ):
268+ choices .append (str (arg ).lower ())
269+ elif arg is None :
270+ choices .append ("null" )
271+ elif isinstance (arg , (str , int , float )):
272+ choices .append (str (arg ))
273+ return choices , True , False
274+
275+ if origin == Union :
276+ choices = []
277+ has_explicit_choices = False
278+ has_open_values = False
252279 for subtype in typehint .__args__ :
253280 if subtype in added_subclasses or subtype is object :
254281 continue
255- get_typehint_choices (subtype , prefix , parser , skip , choices , added_subclasses )
256- elif ActionTypeHint .is_subclass_typehint (typehint ):
282+ subchoices , subexplicit , subopen = get_choices_state (subtype )
283+ choices .extend (subchoices )
284+ has_explicit_choices = has_explicit_choices or subexplicit
285+ has_open_values = has_open_values or subopen
286+ return choices , has_explicit_choices , has_open_values
287+
288+ if ActionTypeHint .is_subclass_typehint (typehint ):
257289 added_subclasses .add (typehint )
258- choices .extend (add_subactions_and_get_subclass_choices (typehint , prefix , parser , skip , added_subclasses ))
259- elif origin in callable_origin_types :
290+ choices = add_subactions_and_get_subclass_choices (typehint , prefix , parser , skip , added_subclasses )
291+ return choices , True , False
292+
293+ if origin in callable_origin_types :
260294 return_type = get_callable_return_type (typehint )
261295 if return_type and ActionTypeHint .is_subclass_typehint (return_type ):
262296 num_args = len (typehint .__args__ ) - 1
263297 skip .add (num_args )
264- choices .extend (
265- add_subactions_and_get_subclass_choices (return_type , prefix , parser , skip , added_subclasses )
266- )
298+ choices = add_subactions_and_get_subclass_choices (return_type , prefix , parser , skip , added_subclasses )
299+ return choices , True , False
300+ return [], False , return_type is None
301+
302+ return [], False , True
267303
268- return [] if choices == ["null" ] else choices
304+ choices , has_explicit_choices , has_open_values = get_choices_state (typehint )
305+ require_prefix = get_typehint_origin (typehint ) == Union and has_explicit_choices and has_open_values
306+ return choices , require_prefix
269307
270308
271309def add_subactions_and_get_subclass_choices (typehint , prefix , parser , skip , added_subclasses ) -> list [str ]:
@@ -295,11 +333,19 @@ def add_subactions_and_get_subclass_choices(typehint, prefix, parser, skip, adde
295333 if option_string not in parser ._option_string_actions :
296334 action = parser .add_argument (option_string )
297335 for subtype in unique (subtypes ):
298- subchoices = get_typehint_choices (subtype , option_string , parser , skip , None , added_subclasses )
336+ subchoices , require_prefix = get_typehint_choices (
337+ subtype , option_string , parser , skip , added_subclasses
338+ )
299339 if shtab_shell .get () == "bash" :
300340 message = f"Expected type: { type_to_str (subtype )} ; "
301341 message += f"Accepted by subclasses: { ', ' .join (subclasses [name ])} "
302- add_bash_typehint_completion (parser , action , message , subchoices )
342+ add_bash_typehint_completion (
343+ parser ,
344+ action ,
345+ message ,
346+ subchoices ,
347+ require_prefix = require_prefix ,
348+ )
303349 elif subchoices :
304350 action .choices = subchoices
305351
0 commit comments