@@ -201,23 +201,11 @@ def complete(
201201
202202 # Positionals args that are left to parse
203203 remaining_positionals = deque (self ._positional_actions )
204-
205- # This gets set to True when flags will no longer be processed as argparse flags
206204 skip_remaining_flags = False
207-
208- # _ArgumentState of the current positional
209205 pos_arg_state : _ArgumentState | None = None
210-
211- # _ArgumentState of the current flag
212206 flag_arg_state : _ArgumentState | None = None
213-
214- # Non-reusable flags that we've parsed
215207 matched_flags : list [str ] = []
216-
217- # Keeps track of arguments we've seen and any tokens they consumed
218- consumed_arg_values : dict [str , list [str ]] = {} # dict(arg_name -> list[tokens])
219-
220- # Completed mutually exclusive groups
208+ consumed_arg_values : dict [str , list [str ]] = {}
221209 completed_mutex_groups : dict [argparse ._MutuallyExclusiveGroup , argparse .Action ] = {}
222210
223211 def consume_argument (arg_state : _ArgumentState , token : str ) -> None :
@@ -226,33 +214,6 @@ def consume_argument(arg_state: _ArgumentState, token: str) -> None:
226214 consumed_arg_values .setdefault (arg_state .action .dest , [])
227215 consumed_arg_values [arg_state .action .dest ].append (token )
228216
229- def update_mutex_groups (arg_action : argparse .Action ) -> None :
230- """Check if an argument belongs to a mutually exclusive group potenitally mark that group complete."""
231- # Check if this action is in a mutually exclusive group
232- for group in self ._parser ._mutually_exclusive_groups :
233- if arg_action in group ._group_actions :
234- # Check if the group this action belongs to has already been completed
235- if group in completed_mutex_groups :
236- completer_action = completed_mutex_groups [group ]
237- if arg_action != completer_action :
238- arg_str = f'{ argparse ._get_action_name (arg_action )} '
239- completer_str = f'{ argparse ._get_action_name (completer_action )} '
240- raise CompletionError (f"Error: argument { arg_str } : not allowed with argument { completer_str } " )
241- return
242-
243- # Mark that this action completed the group
244- completed_mutex_groups [group ] = arg_action
245-
246- # Don't tab complete any of the other args in the group
247- for group_action in group ._group_actions :
248- if group_action == arg_action :
249- continue
250- if group_action in self ._flag_to_action .values ():
251- matched_flags .extend (group_action .option_strings )
252- elif group_action in remaining_positionals :
253- remaining_positionals .remove (group_action )
254- break
255-
256217 #############################################################################################
257218 # Parse all but the last token
258219 #############################################################################################
@@ -287,19 +248,19 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
287248 if len (candidates ) == 1 :
288249 action = self ._flag_to_action [candidates [0 ]]
289250 if action :
290- update_mutex_groups (action )
251+ self . _update_mutex_groups (action , completed_mutex_groups , matched_flags , remaining_positionals )
291252 if isinstance (action , (argparse ._AppendAction , argparse ._AppendConstAction , argparse ._CountAction )):
292253 consumed_arg_values .setdefault (action .dest , [])
293254 else :
294255 matched_flags .extend (action .option_strings )
295256 consumed_arg_values [action .dest ] = []
296257 new_arg_state = _ArgumentState (action )
297- if new_arg_state .max > 0 : # type: ignore[operator]
258+ if cast ( float , new_arg_state .max ) > 0 :
298259 flag_arg_state = new_arg_state
299260 skip_remaining_flags = flag_arg_state .is_remainder
300261 elif flag_arg_state is not None :
301262 consume_argument (flag_arg_state , token )
302- if isinstance ( flag_arg_state .max , (float , int )) and flag_arg_state .count >= flag_arg_state . max :
263+ if flag_arg_state .count >= cast (float , flag_arg_state .max ) :
303264 flag_arg_state = None
304265 # Positional handling
305266 else :
@@ -317,57 +278,120 @@ def update_mutex_groups(arg_action: argparse.Action) -> None:
317278 return []
318279 pos_arg_state = _ArgumentState (action )
319280 if pos_arg_state is not None :
320- update_mutex_groups (pos_arg_state .action )
281+ self ._update_mutex_groups (
282+ pos_arg_state .action , completed_mutex_groups , matched_flags , remaining_positionals
283+ )
321284 consume_argument (pos_arg_state , token )
322285 if pos_arg_state .is_remainder :
323286 skip_remaining_flags = True
324- elif isinstance ( pos_arg_state .max , (float , int )) and pos_arg_state .count >= pos_arg_state . max :
287+ elif pos_arg_state .count >= cast (float , pos_arg_state .max ) :
325288 pos_arg_state = None
326289 if remaining_positionals and remaining_positionals [0 ].nargs == argparse .REMAINDER :
327290 skip_remaining_flags = True
328291
329- #############################################################################################
330- # Complete the last token
331- #############################################################################################
292+ return self ._handle_last_token (
293+ text ,
294+ line ,
295+ begidx ,
296+ endidx ,
297+ flag_arg_state ,
298+ pos_arg_state ,
299+ remaining_positionals ,
300+ consumed_arg_values ,
301+ matched_flags ,
302+ skip_remaining_flags ,
303+ cmd_set ,
304+ )
305+
306+ def _update_mutex_groups (
307+ self ,
308+ arg_action : argparse .Action ,
309+ completed_mutex_groups : dict [argparse ._MutuallyExclusiveGroup , argparse .Action ],
310+ matched_flags : list [str ],
311+ remaining_positionals : deque [argparse .Action ],
312+ ) -> None :
313+ """Update mutex groups state."""
314+ for group in self ._parser ._mutually_exclusive_groups :
315+ if arg_action in group ._group_actions :
316+ if group in completed_mutex_groups :
317+ completer_action = completed_mutex_groups [group ]
318+ if arg_action != completer_action :
319+ arg_str = f'{ argparse ._get_action_name (arg_action )} '
320+ completer_str = f'{ argparse ._get_action_name (completer_action )} '
321+ raise CompletionError (f"Error: argument { arg_str } : not allowed with argument { completer_str } " )
322+ return
323+ completed_mutex_groups [group ] = arg_action
324+ for group_action in group ._group_actions :
325+ if group_action == arg_action :
326+ continue
327+ if group_action in self ._flag_to_action .values ():
328+ matched_flags .extend (group_action .option_strings )
329+ elif group_action in remaining_positionals :
330+ remaining_positionals .remove (group_action )
331+ break
332+
333+ def _handle_last_token (
334+ self ,
335+ text : str ,
336+ line : str ,
337+ begidx : int ,
338+ endidx : int ,
339+ flag_arg_state : _ArgumentState | None ,
340+ pos_arg_state : _ArgumentState | None ,
341+ remaining_positionals : deque [argparse .Action ],
342+ consumed_arg_values : dict [str , list [str ]],
343+ matched_flags : list [str ],
344+ skip_remaining_flags : bool ,
345+ cmd_set : CommandSet | None ,
346+ ) -> list [str ]:
347+ """Perform final completion step handling positionals and flags."""
332348 if _looks_like_flag (text , self ._parser ) and not skip_remaining_flags :
333349 if flag_arg_state and isinstance (flag_arg_state .min , int ) and flag_arg_state .count < flag_arg_state .min :
334350 raise _UnfinishedFlagError (flag_arg_state )
335351 return cast (list [str ], self ._complete_flags (text , line , begidx , endidx , matched_flags ))
336352
337- completion_results : list [str ] = []
338353 if flag_arg_state is not None :
339- completion_results = self ._complete_arg (
340- text , line , begidx , endidx , flag_arg_state , consumed_arg_values , cmd_set = cmd_set
341- )
342- if completion_results :
354+ results = self ._complete_arg (text , line , begidx , endidx , flag_arg_state , consumed_arg_values , cmd_set = cmd_set )
355+ if results :
343356 if not self ._cmd2_app .completion_hint :
344357 self ._cmd2_app .completion_hint = _build_hint (self ._parser , flag_arg_state .action )
345- return completion_results
358+ return results
346359 if (
347360 (isinstance (flag_arg_state .min , int ) and flag_arg_state .count < flag_arg_state .min )
348361 or not _single_prefix_char (text , self ._parser )
349362 or skip_remaining_flags
350363 ):
351364 raise _NoResultsError (self ._parser , flag_arg_state .action )
352- elif pos_arg_state is not None or remaining_positionals :
353- if pos_arg_state is None :
354- pos_arg_state = _ArgumentState (remaining_positionals .popleft ())
355- completion_results = self ._complete_arg (
356- text , line , begidx , endidx , pos_arg_state , consumed_arg_values , cmd_set = cmd_set
357- )
358- if completion_results :
359- if not self ._cmd2_app .completion_hint and not isinstance (pos_arg_state .action , argparse ._SubParsersAction ):
360- self ._cmd2_app .completion_hint = _build_hint (self ._parser , pos_arg_state .action )
361- return completion_results
365+ return []
366+
367+ if pos_arg_state is None and remaining_positionals :
368+ pos_arg_state = _ArgumentState (remaining_positionals .popleft ())
369+
370+ if pos_arg_state is not None :
371+ results = self ._complete_arg (text , line , begidx , endidx , pos_arg_state , consumed_arg_values , cmd_set = cmd_set )
362372 # Fallback to flags if allowed
363- if not skip_remaining_flags and (
364- _looks_like_flag (text , self ._parser )
365- or _single_prefix_char (text , self ._parser )
366- or (isinstance (pos_arg_state .min , int ) and pos_arg_state .count >= pos_arg_state .min )
367- ):
368- flag_results = self ._complete_flags (text , line , begidx , endidx , matched_flags )
369- if flag_results :
370- return cast (list [str ], flag_results )
373+ if not skip_remaining_flags :
374+ if _looks_like_flag (text , self ._parser ) or _single_prefix_char (text , self ._parser ):
375+ flag_results = self ._complete_flags (text , line , begidx , endidx , matched_flags )
376+ results .extend (cast (list [str ], flag_results ))
377+ elif (
378+ not text
379+ and not results
380+ and (isinstance (pos_arg_state .min , int ) and pos_arg_state .count >= pos_arg_state .min )
381+ ):
382+ flag_results = self ._complete_flags (text , line , begidx , endidx , matched_flags )
383+ if flag_results :
384+ return cast (list [str ], flag_results )
385+
386+ if results :
387+ if (
388+ not self ._cmd2_app .completion_hint
389+ and not isinstance (pos_arg_state .action , argparse ._SubParsersAction )
390+ and not _looks_like_flag (text , self ._parser )
391+ and not _single_prefix_char (text , self ._parser )
392+ ):
393+ self ._cmd2_app .completion_hint = _build_hint (self ._parser , pos_arg_state .action )
394+ return results
371395 if not _single_prefix_char (text , self ._parser ) or skip_remaining_flags :
372396 raise _NoResultsError (self ._parser , pos_arg_state .action )
373397
0 commit comments