@@ -318,6 +318,8 @@ def complete_command(self, tokens: List[str], text: str, line: str, begidx: int,
318318 flag_arg = AutoCompleter ._ArgumentState ()
319319 flag_action = None
320320
321+ remainder = {'arg' : None , 'action' : None }
322+
321323 matched_flags = []
322324 current_is_positional = False
323325 consumed_arg_values = {} # dict(arg_name -> [values, ...])
@@ -355,17 +357,76 @@ def consume_positional_argument() -> None:
355357 consumed_arg_values .setdefault (pos_action .dest , [])
356358 consumed_arg_values [pos_action .dest ].append (token )
357359
360+ def process_action_nargs (action : argparse .Action , arg_state : AutoCompleter ._ArgumentState ) -> None :
361+ """Process the current argparse Action and initialize the ArgumentState object used
362+ to track what arguments we have processed for this action"""
363+ if isinstance (action , _RangeAction ):
364+ arg_state .min = action .nargs_min
365+ arg_state .max = action .nargs_max
366+ arg_state .variable = True
367+ if arg_state .min is None or arg_state .max is None :
368+ if action .nargs is None :
369+ arg_state .min = 1
370+ arg_state .max = 1
371+ elif action .nargs == '+' :
372+ arg_state .min = 1
373+ arg_state .max = float ('inf' )
374+ arg_state .variable = True
375+ elif action .nargs == '*' or action .nargs == argparse .REMAINDER :
376+ arg_state .min = 0
377+ arg_state .max = float ('inf' )
378+ arg_state .variable = True
379+ if action .nargs == argparse .REMAINDER :
380+ print ("Setting remainder" )
381+ remainder ['action' ] = action
382+ remainder ['arg' ] = arg_state
383+ elif action .nargs == '?' :
384+ arg_state .min = 0
385+ arg_state .max = 1
386+ arg_state .variable = True
387+ else :
388+ arg_state .min = action .nargs
389+ arg_state .max = action .nargs
390+
391+
392+ # This next block of processing tries to parse all parameters before the last parameter.
393+ # We're trying to determine what specific argument the current cursor positition should be
394+ # matched with. When we finish parsing all of the arguments, we can determine whether the
395+ # last token is a positional or flag argument and which specific argument it is.
396+ #
397+ # We're also trying to save every flag that has been used as well as every value that
398+ # has been used for a positional or flag parameter. By saving this information we can exclude
399+ # it from the completion results we generate for the last token. For example, single-use flag
400+ # arguments will be hidden from the list of available flags. Also, arguments with a
401+ # defined list of possible values will exclude values that have already been used.
402+
403+ # notes when the last token has been reached
358404 is_last_token = False
405+
359406 for idx , token in enumerate (tokens ):
360407 is_last_token = idx >= len (tokens ) - 1
361408 # Only start at the start token index
362409 if idx >= self ._token_start_index :
410+ if remainder ['arg' ] is not None :
411+ print ("In Remainder mode" )
412+ if remainder ['action' ] == pos_action :
413+ consume_positional_argument ()
414+ continue
415+ elif remainder ['action' ] == flag_action :
416+ consume_flag_argument ()
417+ continue
418+ else :
419+ print ("!!" )
363420 current_is_positional = False
364421 # Are we consuming flag arguments?
365422 if not flag_arg .needed :
366- # we're not consuming flag arguments, is the current argument a potential flag?
423+ # At this point we're no longer consuming flag arguments. Is the current argument a potential flag?
424+ # If the argument is the start of a flag and this is the last token, we proceed forward to try
425+ # and match against our known flags.
426+ # If this argument is not the last token and the argument is exactly a flag prefix, then this
427+ # token should be consumed as an argument to a prior flag or positional argument.
367428 if len (token ) > 0 and token [0 ] in self ._parser .prefix_chars and \
368- (is_last_token or (not is_last_token and token != '-' )):
429+ (is_last_token or (not is_last_token and token not in self . _parser . prefix_chars )):
369430 # reset some tracking values
370431 flag_arg .reset ()
371432 # don't reset positional tracking because flags can be interspersed anywhere between positionals
@@ -381,7 +442,7 @@ def consume_positional_argument() -> None:
381442
382443 if flag_action is not None :
383444 # resolve argument counts
384- self . _process_action_nargs (flag_action , flag_arg )
445+ process_action_nargs (flag_action , flag_arg )
385446 if not is_last_token and not isinstance (flag_action , argparse ._AppendAction ):
386447 matched_flags .extend (flag_action .option_strings )
387448
@@ -418,7 +479,7 @@ def consume_positional_argument() -> None:
418479 return sub_completers [token ].complete_command (tokens , text , line ,
419480 begidx , endidx )
420481 pos_action = action
421- self . _process_action_nargs (pos_action , pos_arg )
482+ process_action_nargs (pos_action , pos_arg )
422483 consume_positional_argument ()
423484
424485 elif not is_last_token and pos_arg .max is not None :
@@ -435,10 +496,12 @@ def consume_positional_argument() -> None:
435496 if not is_last_token and flag_arg .min is not None :
436497 flag_arg .needed = flag_arg .count < flag_arg .min
437498
499+ # Here we're done parsing all of the prior arguments. We know what the next argument is.
500+
438501 # if we don't have a flag to populate with arguments and the last token starts with
439502 # a flag prefix then we'll complete the list of flag options
440503 completion_results = []
441- if not flag_arg .needed and len (tokens [- 1 ]) > 0 and tokens [- 1 ][0 ] in self ._parser .prefix_chars :
504+ if not flag_arg .needed and len (tokens [- 1 ]) > 0 and tokens [- 1 ][0 ] in self ._parser .prefix_chars and remainder [ 'arg' ] is None :
442505 return AutoCompleter .basic_complete (text , line , begidx , endidx ,
443506 [flag for flag in self ._flags if flag not in matched_flags ])
444507 # we're not at a positional argument, see if we're in a flag argument
@@ -522,32 +585,6 @@ def format_help(self, tokens: List[str]) -> str:
522585 return completers [token ].format_help (tokens )
523586 return self ._parser .format_help ()
524587
525- @staticmethod
526- def _process_action_nargs (action : argparse .Action , arg_state : _ArgumentState ) -> None :
527- if isinstance (action , _RangeAction ):
528- arg_state .min = action .nargs_min
529- arg_state .max = action .nargs_max
530- arg_state .variable = True
531- if arg_state .min is None or arg_state .max is None :
532- if action .nargs is None :
533- arg_state .min = 1
534- arg_state .max = 1
535- elif action .nargs == '+' :
536- arg_state .min = 1
537- arg_state .max = float ('inf' )
538- arg_state .variable = True
539- elif action .nargs == '*' :
540- arg_state .min = 0
541- arg_state .max = float ('inf' )
542- arg_state .variable = True
543- elif action .nargs == '?' :
544- arg_state .min = 0
545- arg_state .max = 1
546- arg_state .variable = True
547- else :
548- arg_state .min = action .nargs
549- arg_state .max = action .nargs
550-
551588 def _complete_for_arg (self , action : argparse .Action ,
552589 text : str ,
553590 line : str ,
0 commit comments