@@ -72,6 +72,17 @@ def fnsignature(obj):
7272 return sig
7373
7474
75+ def _format_annotation (annotation ):
76+ if isinstance (annotation , str ):
77+ return annotation
78+ if isinstance (annotation , typing .ForwardRef ):
79+ return annotation .__forward_arg__
80+ try :
81+ return inspect .formatannotation (annotation )
82+ except Exception :
83+ return repr (annotation )
84+
85+
7586class Completer :
7687 def __init__ (self , namespace = None ):
7788 """Create a new completer for the command line.
@@ -101,9 +112,9 @@ def __init__(self, namespace=None):
101112 self .namespace = namespace
102113
103114 def _resolve_callable (self , callable_path : str ):
104- namespace = self .namespace
105115 if self .use_main_ns :
106116 self .namespace = __main__ .__dict__
117+ namespace = self .namespace
107118 parts = callable_path .split ("." )
108119
109120 if not parts :
@@ -184,6 +195,41 @@ def _is_keyword_unpack_argument(
184195 and argument_tokens [0 ].string == "**"
185196 )
186197
198+ @staticmethod
199+ def _is_iterable_unpack_argument (
200+ argument_tokens : typing .List [tokenize .TokenInfo ]
201+ ) -> bool :
202+ return (
203+ len (argument_tokens ) > 0
204+ and argument_tokens [0 ].type == tokenize .OP
205+ and argument_tokens [0 ].string == "*"
206+ )
207+
208+ @staticmethod
209+ def _var_positional_parameter_index (
210+ parameters : typing .List [inspect .Parameter ]
211+ ) -> Optional [int ]:
212+ return next (
213+ (
214+ index for index , parameter in enumerate (parameters )
215+ if parameter .kind == inspect .Parameter .VAR_POSITIONAL
216+ ),
217+ None ,
218+ )
219+
220+ @staticmethod
221+ def _keyword_only_parameter_index (
222+ parameters : typing .List [inspect .Parameter ], used_keyword_parameters : typing .Set [str ]
223+ ) -> Optional [int ]:
224+ return next (
225+ (
226+ index for index , parameter in enumerate (parameters )
227+ if parameter .kind == inspect .Parameter .KEYWORD_ONLY
228+ and parameter .name not in used_keyword_parameters
229+ ),
230+ None ,
231+ )
232+
187233 @staticmethod
188234 def _keyword_parameter_index (
189235 parameters : typing .List [inspect .Parameter ], keyword_name : str
@@ -241,13 +287,26 @@ def _current_argument_index(
241287 ) -> Optional [int ]:
242288 positional_argument_count = 0
243289 used_keyword_parameters = set ()
290+ var_keyword_index = next (
291+ (
292+ index for index , parameter in enumerate (parameters )
293+ if parameter .kind == inspect .Parameter .VAR_KEYWORD
294+ ),
295+ None ,
296+ )
297+ seen_var_keyword_argument = False
298+ seen_iterable_unpack_argument = False
244299
245300 for argument_tokens in arguments [:- 1 ]:
246301 keyword_name = cls ._keyword_argument_name (argument_tokens )
247302 if keyword_name is not None :
248303 parameter_index = cls ._keyword_parameter_index (parameters , keyword_name )
249304 if parameter_index is not None :
250305 used_keyword_parameters .add (parameters [parameter_index ].name )
306+ if parameter_index == var_keyword_index :
307+ seen_var_keyword_argument = True
308+ elif cls ._is_iterable_unpack_argument (argument_tokens ):
309+ seen_iterable_unpack_argument = True
251310 elif not cls ._is_keyword_unpack_argument (argument_tokens ):
252311 positional_argument_count += 1
253312
@@ -257,14 +316,22 @@ def _current_argument_index(
257316 return cls ._keyword_parameter_index (parameters , keyword_name )
258317
259318 if cls ._is_keyword_unpack_argument (current_argument ):
260- for index , parameter in enumerate (parameters ):
261- if parameter .kind == inspect .Parameter .VAR_KEYWORD :
262- return index
263- return None
319+ return var_keyword_index
320+ if cls ._is_iterable_unpack_argument (current_argument ):
321+ return cls ._var_positional_parameter_index (parameters )
322+
323+ if seen_var_keyword_argument :
324+ return var_keyword_index
325+ if seen_iterable_unpack_argument :
326+ return cls ._var_positional_parameter_index (parameters )
264327
265- return cls ._positional_parameter_index (
328+ parameter_index = cls ._positional_parameter_index (
266329 parameters , positional_argument_count , used_keyword_parameters
267330 )
331+ if parameter_index is not None :
332+ return parameter_index
333+
334+ return cls ._keyword_only_parameter_index (parameters , used_keyword_parameters )
268335
269336 def _get_argument_completion_context (
270337 self , text : str
@@ -276,9 +343,9 @@ def _get_argument_completion_context(
276343 for line in text .splitlines (keepends = True ):
277344 line_offsets .append (line_offsets [- 1 ] + len (line ))
278345
279- def absolute_index (position : typing .Tuple [int , int ]) -> int :
346+ def absolute_byte_index (position : typing .Tuple [int , int ]) -> int :
280347 line , column = position
281- return line_offsets [line - 1 ] + column
348+ return len ( text [: line_offsets [line - 1 ] + column ]. encode ( "utf-8" ))
282349
283350 reader = io .StringIO (text ).readline
284351 stream = tokenize .generate_tokens (reader )
@@ -360,16 +427,14 @@ def absolute_index(position: typing.Tuple[int, int]) -> int:
360427 return None
361428
362429 parameters = list (signature .parameters .values ())
363- if not parameters :
364- return None
365430
366431 arguments = self ._split_call_arguments (tokens , call_context ["token_index" ])
367432
368433 return {
369434 "signature" : signature ,
370435 "parameters" : parameters ,
371436 "current_argument_index" : self ._current_argument_index (parameters , arguments ),
372- "start_index" : absolute_index (tokens [call_context ["token_index" ]].end ),
437+ "start_index" : absolute_byte_index (tokens [call_context ["token_index" ]].end ),
373438 }
374439
375440 def can_complete_arguments (self , text : str ) -> bool :
@@ -395,23 +460,19 @@ def complete_arguments(self, text: str) -> typing.Tuple[Optional[str], int]:
395460 parameters = context ["parameters" ]
396461 current_argument_index = context ["current_argument_index" ]
397462
398-
399463 return_args = []
400464 positional_only_count = sum (
401465 1 for p in parameters if p .kind == inspect .Parameter .POSITIONAL_ONLY
402466 )
403467
404468 for i , parameter in enumerate (parameters ):
405- if positional_only_count > 0 and i == positional_only_count :
406- return_args .append ("/" )
407-
408469 if (
409- parameter .kind == inspect .Parameter .KEYWORD_ONLY
410- and "*" not in return_args
411- and not any (
412- p .kind == inspect .Parameter .VAR_POSITIONAL
413- for p in parameters [:i ]
414- )
470+ parameter .kind == inspect .Parameter .KEYWORD_ONLY
471+ and "*" not in return_args
472+ and not any (
473+ p .kind == inspect .Parameter .VAR_POSITIONAL
474+ for p in parameters [:i ]
475+ )
415476 ):
416477 return_args .append ("*" )
417478
@@ -425,10 +486,7 @@ def complete_arguments(self, text: str) -> typing.Tuple[Optional[str], int]:
425486 if i == current_argument_index :
426487 arg_postfix = ''
427488 if parameter .annotation is not inspect .Signature .empty :
428- try :
429- annotation = inspect .formatannotation (parameter .annotation )
430- except Exception :
431- annotation = repr (parameter .annotation )
489+ annotation = _format_annotation (parameter .annotation )
432490 arg_postfix += f": { annotation } "
433491
434492 if parameter .default is not inspect .Signature .empty :
@@ -441,13 +499,13 @@ def complete_arguments(self, text: str) -> typing.Tuple[Optional[str], int]:
441499
442500 return_args .append (arg )
443501
502+ if positional_only_count > 0 and i + 1 == positional_only_count :
503+ return_args .append ("/" )
504+
444505 result = ", " .join (return_args )
445506
446507 if signature .return_annotation is not inspect .Signature .empty :
447- try :
448- return_annotation = inspect .formatannotation (signature .return_annotation )
449- except Exception :
450- return_annotation = repr (signature .return_annotation )
508+ return_annotation = _format_annotation (signature .return_annotation )
451509 result += f'<span class="returnType"> -> { html .escape (return_annotation )} </span>'
452510
453511 return result , context ["start_index" ]
0 commit comments