##// END OF EJS Templates
Fix completion in indented lines dropping prefix when jedi is disabled (#14474)...
M Bussonnier -
r28832:5c8bc514 merge
parent child Browse files
Show More
@@ -898,7 +898,7 b' def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) ->'
898 898 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
899 899 if c._origin == 'jedi':
900 900 seen_jedi.add(new_text)
901 elif c._origin == 'IPCompleter.python_matches':
901 elif c._origin == "IPCompleter.python_matcher":
902 902 seen_python_matches.add(new_text)
903 903 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
904 904 diff = seen_python_matches.difference(seen_jedi)
@@ -1139,15 +1139,18 b' class Completer(Configurable):'
1139 1139 with a __getattr__ hook is evaluated.
1140 1140
1141 1141 """
1142 return self._attr_matches(text)[0]
1143
1144 def _attr_matches(self, text, include_prefix=True) -> Tuple[Sequence[str], str]:
1142 1145 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
1143 1146 if not m2:
1144 return []
1147 return [], ""
1145 1148 expr, attr = m2.group(1, 2)
1146 1149
1147 1150 obj = self._evaluate_expr(expr)
1148 1151
1149 1152 if obj is not_found:
1150 return []
1153 return [], ""
1151 1154
1152 1155 if self.limit_to__all__ and hasattr(obj, '__all__'):
1153 1156 words = get__all__entries(obj)
@@ -1170,6 +1173,7 b' class Completer(Configurable):'
1170 1173 # reconciliator would know that we intend to append to rather than
1171 1174 # replace the input text; this requires refactoring to return range
1172 1175 # which ought to be replaced (as does jedi).
1176 if include_prefix:
1173 1177 tokens = _parse_tokens(expr)
1174 1178 rev_tokens = reversed(tokens)
1175 1179 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
@@ -1182,7 +1186,9 b' class Completer(Configurable):'
1182 1186 if token.type == tokenize.NAME and name_turn:
1183 1187 parts.append(token.string)
1184 1188 name_turn = False
1185 elif token.type == tokenize.OP and token.string == "." and not name_turn:
1189 elif (
1190 token.type == tokenize.OP and token.string == "." and not name_turn
1191 ):
1186 1192 parts.append(token.string)
1187 1193 name_turn = True
1188 1194 else:
@@ -1190,8 +1196,13 b' class Completer(Configurable):'
1190 1196 break
1191 1197
1192 1198 prefix_after_space = "".join(reversed(parts))
1199 else:
1200 prefix_after_space = ""
1193 1201
1194 return ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr]
1202 return (
1203 ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr],
1204 "." + attr,
1205 )
1195 1206
1196 1207 def _evaluate_expr(self, expr):
1197 1208 obj = not_found
@@ -1973,9 +1984,8 b' class IPCompleter(Completer):'
1973 1984 *self.magic_arg_matchers,
1974 1985 self.custom_completer_matcher,
1975 1986 self.dict_key_matcher,
1976 # TODO: convert python_matches to v2 API
1977 1987 self.magic_matcher,
1978 self.python_matches,
1988 self.python_matcher,
1979 1989 self.file_matcher,
1980 1990 self.python_func_kw_matcher,
1981 1991 ]
@@ -2316,9 +2326,42 b' class IPCompleter(Completer):'
2316 2326 else:
2317 2327 return iter([])
2318 2328
2329 @context_matcher()
2330 def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2331 """Match attributes or global python names"""
2332 text = context.line_with_cursor
2333 if "." in text:
2334 try:
2335 matches, fragment = self._attr_matches(text, include_prefix=False)
2336 if text.endswith(".") and self.omit__names:
2337 if self.omit__names == 1:
2338 # true if txt is _not_ a __ name, false otherwise:
2339 no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None
2340 else:
2341 # true if txt is _not_ a _ name, false otherwise:
2342 no__name = (
2343 lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :])
2344 is None
2345 )
2346 matches = filter(no__name, matches)
2347 return _convert_matcher_v1_result_to_v2(
2348 matches, type="attribute", fragment=fragment
2349 )
2350 except NameError:
2351 # catches <undefined attributes>.<tab>
2352 matches = []
2353 return _convert_matcher_v1_result_to_v2(matches, type="attribute")
2354 else:
2355 matches = self.global_matches(context.token)
2356 # TODO: maybe distinguish between functions, modules and just "variables"
2357 return _convert_matcher_v1_result_to_v2(matches, type="variable")
2358
2319 2359 @completion_matcher(api_version=1)
2320 2360 def python_matches(self, text: str) -> Iterable[str]:
2321 """Match attributes or global python names"""
2361 """Match attributes or global python names.
2362
2363 .. deprecated:: 8.27
2364 You can use :meth:`python_matcher` instead."""
2322 2365 if "." in text:
2323 2366 try:
2324 2367 matches = self.attr_matches(text)
@@ -462,7 +462,10 b' class TestCompleter(unittest.TestCase):'
462 462 matches = c.all_completions("TestClass.")
463 463 assert len(matches) > 2, (jedi_status, matches)
464 464 matches = c.all_completions("TestClass.a")
465 assert matches == ['TestClass.a', 'TestClass.a1'], jedi_status
465 if jedi_status:
466 assert matches == ["TestClass.a", "TestClass.a1"], jedi_status
467 else:
468 assert matches == [".a", ".a1"], jedi_status
466 469
467 470 @pytest.mark.xfail(
468 471 sys.version_info.releaselevel in ("alpha",),
@@ -594,7 +597,7 b' class TestCompleter(unittest.TestCase):'
594 597 ip.Completer.use_jedi = True
595 598 with provisionalcompleter():
596 599 completions = ip.Completer.completions(line, cursor_pos)
597 self.assertIn(completion, completions)
600 self.assertIn(completion, list(completions))
598 601
599 602 with provisionalcompleter():
600 603 _(
@@ -622,7 +625,7 b' class TestCompleter(unittest.TestCase):'
622 625 _(
623 626 "assert str.star",
624 627 14,
625 "str.startswith",
628 ".startswith",
626 629 "Should have completed on `assert str.star`: %s",
627 630 Completion(11, 14, "startswith"),
628 631 )
@@ -633,6 +636,13 b' class TestCompleter(unittest.TestCase):'
633 636 "Should have completed on `d['a b'].str`: %s",
634 637 Completion(9, 12, "strip"),
635 638 )
639 _(
640 "a.app",
641 4,
642 ".append",
643 "Should have completed on `a.app`: %s",
644 Completion(2, 4, "append"),
645 )
636 646
637 647 def test_omit__names(self):
638 648 # also happens to test IPCompleter as a configurable
@@ -647,8 +657,8 b' class TestCompleter(unittest.TestCase):'
647 657 with provisionalcompleter():
648 658 c.use_jedi = False
649 659 s, matches = c.complete("ip.")
650 self.assertIn("ip.__str__", matches)
651 self.assertIn("ip._hidden_attr", matches)
660 self.assertIn(".__str__", matches)
661 self.assertIn("._hidden_attr", matches)
652 662
653 663 # c.use_jedi = True
654 664 # completions = set(c.completions('ip.', 3))
@@ -661,7 +671,7 b' class TestCompleter(unittest.TestCase):'
661 671 with provisionalcompleter():
662 672 c.use_jedi = False
663 673 s, matches = c.complete("ip.")
664 self.assertNotIn("ip.__str__", matches)
674 self.assertNotIn(".__str__", matches)
665 675 # self.assertIn('ip._hidden_attr', matches)
666 676
667 677 # c.use_jedi = True
@@ -675,8 +685,8 b' class TestCompleter(unittest.TestCase):'
675 685 with provisionalcompleter():
676 686 c.use_jedi = False
677 687 s, matches = c.complete("ip.")
678 self.assertNotIn("ip.__str__", matches)
679 self.assertNotIn("ip._hidden_attr", matches)
688 self.assertNotIn(".__str__", matches)
689 self.assertNotIn("._hidden_attr", matches)
680 690
681 691 # c.use_jedi = True
682 692 # completions = set(c.completions('ip.', 3))
@@ -686,7 +696,7 b' class TestCompleter(unittest.TestCase):'
686 696 with provisionalcompleter():
687 697 c.use_jedi = False
688 698 s, matches = c.complete("ip._x.")
689 self.assertIn("ip._x.keys", matches)
699 self.assertIn(".keys", matches)
690 700
691 701 # c.use_jedi = True
692 702 # completions = set(c.completions('ip._x.', 6))
@@ -708,7 +718,7 b' class TestCompleter(unittest.TestCase):'
708 718 cfg.IPCompleter.limit_to__all__ = False
709 719 c.update_config(cfg)
710 720 s, matches = c.complete("d.")
711 self.assertIn("d.x", matches)
721 self.assertIn(".x", matches)
712 722
713 723 def test_get__all__entries_ok(self):
714 724 class A:
General Comments 0
You need to be logged in to leave comments. Login now