##// 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,28 +1173,36 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).
1173 tokens = _parse_tokens(expr)
1174 rev_tokens = reversed(tokens)
1175 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1176 name_turn = True
1177
1178 parts = []
1179 for token in rev_tokens:
1180 if token.type in skip_over:
1181 continue
1182 if token.type == tokenize.NAME and name_turn:
1183 parts.append(token.string)
1184 name_turn = False
1185 elif token.type == tokenize.OP and token.string == "." and not name_turn:
1186 parts.append(token.string)
1187 name_turn = True
1188 else:
1189 # short-circuit if not empty nor name token
1190 break
1176 if include_prefix:
1177 tokens = _parse_tokens(expr)
1178 rev_tokens = reversed(tokens)
1179 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1180 name_turn = True
1181
1182 parts = []
1183 for token in rev_tokens:
1184 if token.type in skip_over:
1185 continue
1186 if token.type == tokenize.NAME and name_turn:
1187 parts.append(token.string)
1188 name_turn = False
1189 elif (
1190 token.type == tokenize.OP and token.string == "." and not name_turn
1191 ):
1192 parts.append(token.string)
1193 name_turn = True
1194 else:
1195 # short-circuit if not empty nor name token
1196 break
1191 1197
1192 prefix_after_space = "".join(reversed(parts))
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))
@@ -697,7 +707,7 b' class TestCompleter(unittest.TestCase):'
697 707
698 708 def test_limit_to__all__False_ok(self):
699 709 """
700 Limit to all is deprecated, once we remove it this test can go away.
710 Limit to all is deprecated, once we remove it this test can go away.
701 711 """
702 712 ip = get_ipython()
703 713 c = ip.Completer
@@ -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