Skip to content

Commit b54039d

Browse files
committed
Make kwargs explicit with proper type annotations
Also fix handling of complete_magic_methods.
1 parent 9861730 commit b54039d

File tree

2 files changed

+90
-43
lines changed

2 files changed

+90
-43
lines changed

bpython/autocomplete.py

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import __main__
2929
import abc
3030
import glob
31+
import itertools
3132
import keyword
3233
import logging
3334
import os
@@ -383,11 +384,15 @@ class AttrCompletion(BaseCompletionType):
383384
attr_matches_re = LazyReCompile(r"(\w+(\.\w+)*)\.(\w*)")
384385

385386
def matches(
386-
self, cursor_offset: int, line: str, **kwargs: Any
387+
self,
388+
cursor_offset: int,
389+
line: str,
390+
*,
391+
locals_: Optional[Dict[str, Any]] = None,
392+
**kwargs: Any,
387393
) -> Optional[Set]:
388-
if "locals_" not in kwargs:
394+
if locals_ is None:
389395
return None
390-
locals_ = cast(Dict[str, Any], kwargs["locals_"])
391396

392397
r = self.locate(cursor_offset, line)
393398
if r is None:
@@ -465,11 +470,15 @@ def list_attributes(self, obj: Any) -> List[str]:
465470

466471
class DictKeyCompletion(BaseCompletionType):
467472
def matches(
468-
self, cursor_offset: int, line: str, **kwargs: Any
473+
self,
474+
cursor_offset: int,
475+
line: str,
476+
*,
477+
locals_: Optional[Dict[str, Any]] = None,
478+
**kwargs: Any,
469479
) -> Optional[Set]:
470-
if "locals_" not in kwargs:
480+
if locals_ is None:
471481
return None
472-
locals_ = kwargs["locals_"]
473482

474483
r = self.locate(cursor_offset, line)
475484
if r is None:
@@ -500,11 +509,16 @@ def format(self, match: str) -> str:
500509

501510
class MagicMethodCompletion(BaseCompletionType):
502511
def matches(
503-
self, cursor_offset: int, line: str, **kwargs: Any
512+
self,
513+
cursor_offset: int,
514+
line: str,
515+
*,
516+
current_block: Optional[str] = None,
517+
complete_magic_methods: Optional[bool] = None,
518+
**kwargs: Any,
504519
) -> Optional[Set]:
505-
if "current_block" not in kwargs:
520+
if current_block is None or complete_magic_methods is None or not complete_magic_methods:
506521
return None
507-
current_block = kwargs["current_block"]
508522

509523
r = self.locate(cursor_offset, line)
510524
if r is None:
@@ -519,15 +533,19 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]:
519533

520534
class GlobalCompletion(BaseCompletionType):
521535
def matches(
522-
self, cursor_offset: int, line: str, **kwargs: Any
536+
self,
537+
cursor_offset: int,
538+
line: str,
539+
*,
540+
locals_: Optional[Dict[str, Any]] = None,
541+
**kwargs: Any,
523542
) -> Optional[Set]:
524543
"""Compute matches when text is a simple name.
525544
Return a list of all keywords, built-in functions and names currently
526545
defined in self.namespace that match.
527546
"""
528-
if "locals_" not in kwargs:
547+
if locals_ is None:
529548
return None
530-
locals_ = kwargs["locals_"]
531549

532550
r = self.locate(cursor_offset, line)
533551
if r is None:
@@ -556,25 +574,29 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]:
556574

557575
class ParameterNameCompletion(BaseCompletionType):
558576
def matches(
559-
self, cursor_offset: int, line: str, **kwargs: Any
577+
self,
578+
cursor_offset: int,
579+
line: str,
580+
*,
581+
funcprops: Optional[inspection.FuncProps] = None,
582+
**kwargs: Any,
560583
) -> Optional[Set]:
561-
if "argspec" not in kwargs:
584+
if funcprops is None:
562585
return None
563-
argspec = kwargs["argspec"]
564586

565-
if not argspec:
566-
return None
567587
r = self.locate(cursor_offset, line)
568588
if r is None:
569589
return None
570590

571591
matches = {
572592
f"{name}="
573-
for name in argspec.argspec.args
593+
for name in funcprops.argspec.args
574594
if isinstance(name, str) and name.startswith(r.word)
575595
}
576596
matches.update(
577-
name + "=" for name in argspec.argspec.kwonly if name.startswith(r.word)
597+
name + "="
598+
for name in funcprops.argspec.kwonly
599+
if name.startswith(r.word)
578600
)
579601
return matches if matches else None
580602

@@ -588,12 +610,13 @@ def locate(self, cursor_offset: int, line: str) -> Optional[LinePart]:
588610
return lineparts.current_expression_attribute(cursor_offset, line)
589611

590612
def matches(
591-
self, cursor_offset: int, line: str, **kwargs: Any
613+
self,
614+
cursor_offset: int,
615+
line: str,
616+
*,
617+
locals_: Optional[Dict[str, Any]] = None,
618+
**kwargs: Any,
592619
) -> Optional[Set]:
593-
if "locals_" not in kwargs:
594-
return None
595-
locals_ = kwargs["locals_"]
596-
597620
if locals_ is None:
598621
locals_ = __main__.__dict__
599622

@@ -629,20 +652,23 @@ class JediCompletion(BaseCompletionType):
629652
_orig_start: Optional[int]
630653

631654
def matches(
632-
self, cursor_offset: int, line: str, **kwargs: Any
655+
self,
656+
cursor_offset: int,
657+
line: str,
658+
*,
659+
history: Optional[List[str]] = None,
660+
**kwargs: Any,
633661
) -> Optional[Set]:
634-
if "history" not in kwargs:
662+
if history is None:
635663
return None
636-
history = kwargs["history"]
637-
638664
if not lineparts.current_word(cursor_offset, line):
639665
return None
640-
history = "\n".join(history) + "\n" + line
641666

667+
combined_history = "\n".join(itertools.chain(history, (line,)))
642668
try:
643-
script = jedi.Script(history, path="fake.py")
669+
script = jedi.Script(combined_history, path="fake.py")
644670
completions = script.complete(
645-
len(history.splitlines()), cursor_offset
671+
len(combined_history.splitlines()), cursor_offset
646672
)
647673
except (jedi.NotFoundError, IndexError, KeyError):
648674
# IndexError for #483
@@ -679,12 +705,16 @@ def locate(self, cursor_offset: int, line: str) -> LinePart:
679705

680706
class MultilineJediCompletion(JediCompletion): # type: ignore [no-redef]
681707
def matches(
682-
self, cursor_offset: int, line: str, **kwargs: Any
708+
self,
709+
cursor_offset: int,
710+
line: str,
711+
*,
712+
current_block: Optional[str] = None,
713+
history: Optional[List[str]] = None,
714+
**kwargs: Any,
683715
) -> Optional[Set]:
684-
if "current_block" not in kwargs or "history" not in kwargs:
716+
if current_block is None or history is None:
685717
return None
686-
current_block = kwargs["current_block"]
687-
history = kwargs["history"]
688718

689719
if "\n" in current_block:
690720
assert cursor_offset <= len(line), "{!r} {!r}".format(
@@ -701,7 +731,12 @@ def get_completer(
701731
completers: Sequence[BaseCompletionType],
702732
cursor_offset: int,
703733
line: str,
704-
**kwargs: Any,
734+
*,
735+
locals_: Optional[Dict[str, Any]] = None,
736+
argspec: Optional[inspection.FuncProps] = None,
737+
history: Optional[List[str]] = None,
738+
current_block: Optional[str] = None,
739+
complete_magic_methods: Optional[bool] = None,
705740
) -> Tuple[List[str], Optional[BaseCompletionType]]:
706741
"""Returns a list of matches and an applicable completer
707742
@@ -711,7 +746,7 @@ def get_completer(
711746
line is a string of the current line
712747
kwargs (all optional):
713748
locals_ is a dictionary of the environment
714-
argspec is an inspect.FuncProps instance for the current function where
749+
argspec is an inspection.FuncProps instance for the current function where
715750
the cursor is
716751
current_block is the possibly multiline not-yet-evaluated block of
717752
code which the current line is part of
@@ -721,7 +756,15 @@ def get_completer(
721756

722757
for completer in completers:
723758
try:
724-
matches = completer.matches(cursor_offset, line, **kwargs)
759+
matches = completer.matches(
760+
cursor_offset,
761+
line,
762+
locals_=locals_,
763+
funcprops=argspec,
764+
history=history,
765+
current_block=current_block,
766+
complete_magic_methods=complete_magic_methods,
767+
)
725768
except Exception as e:
726769
# Instead of crashing the UI, log exceptions from autocompleters.
727770
logger = logging.getLogger(__name__)

bpython/test/test_autocomplete.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ def test_magic_methods_complete_after_double_underscores(self):
324324
com = autocomplete.MagicMethodCompletion()
325325
block = "class Something(object)\n def __"
326326
self.assertSetEqual(
327-
com.matches(10, " def __", current_block=block),
327+
com.matches(10, " def __", current_block=block, complete_magic_methods=True),
328328
set(autocomplete.MAGIC_METHODS),
329329
)
330330

@@ -419,10 +419,14 @@ def func(apple, apricot, banana, carrot):
419419
pass
420420

421421
argspec = inspection.ArgSpec(*inspect.getfullargspec(func))
422-
argspec = inspection.FuncProps("func", argspec, False)
422+
funcspec = inspection.FuncProps("func", argspec, False)
423423
com = autocomplete.ParameterNameCompletion()
424424
self.assertSetEqual(
425-
com.matches(1, "a", argspec=argspec), {"apple=", "apricot="}
425+
com.matches(1, "a", funcprops=funcspec), {"apple=", "apricot="}
426+
)
427+
self.assertSetEqual(
428+
com.matches(2, "ba", funcprops=funcspec), {"banana="}
429+
)
430+
self.assertSetEqual(
431+
com.matches(3, "car", funcprops=funcspec), {"carrot="}
426432
)
427-
self.assertSetEqual(com.matches(2, "ba", argspec=argspec), {"banana="})
428-
self.assertSetEqual(com.matches(3, "car", argspec=argspec), {"carrot="})

0 commit comments

Comments
 (0)