##// 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 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
898 new_text = text[new_start:c.start] + c.text + text[c.end:new_end]
899 if c._origin == 'jedi':
899 if c._origin == 'jedi':
900 seen_jedi.add(new_text)
900 seen_jedi.add(new_text)
901 elif c._origin == 'IPCompleter.python_matches':
901 elif c._origin == "IPCompleter.python_matcher":
902 seen_python_matches.add(new_text)
902 seen_python_matches.add(new_text)
903 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
903 yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature)
904 diff = seen_python_matches.difference(seen_jedi)
904 diff = seen_python_matches.difference(seen_jedi)
@@ -1139,15 +1139,18 b' class Completer(Configurable):'
1139 with a __getattr__ hook is evaluated.
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 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
1145 m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer)
1143 if not m2:
1146 if not m2:
1144 return []
1147 return [], ""
1145 expr, attr = m2.group(1, 2)
1148 expr, attr = m2.group(1, 2)
1146
1149
1147 obj = self._evaluate_expr(expr)
1150 obj = self._evaluate_expr(expr)
1148
1151
1149 if obj is not_found:
1152 if obj is not_found:
1150 return []
1153 return [], ""
1151
1154
1152 if self.limit_to__all__ and hasattr(obj, '__all__'):
1155 if self.limit_to__all__ and hasattr(obj, '__all__'):
1153 words = get__all__entries(obj)
1156 words = get__all__entries(obj)
@@ -1170,28 +1173,36 b' class Completer(Configurable):'
1170 # reconciliator would know that we intend to append to rather than
1173 # reconciliator would know that we intend to append to rather than
1171 # replace the input text; this requires refactoring to return range
1174 # replace the input text; this requires refactoring to return range
1172 # which ought to be replaced (as does jedi).
1175 # which ought to be replaced (as does jedi).
1173 tokens = _parse_tokens(expr)
1176 if include_prefix:
1174 rev_tokens = reversed(tokens)
1177 tokens = _parse_tokens(expr)
1175 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1178 rev_tokens = reversed(tokens)
1176 name_turn = True
1179 skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE}
1177
1180 name_turn = True
1178 parts = []
1181
1179 for token in rev_tokens:
1182 parts = []
1180 if token.type in skip_over:
1183 for token in rev_tokens:
1181 continue
1184 if token.type in skip_over:
1182 if token.type == tokenize.NAME and name_turn:
1185 continue
1183 parts.append(token.string)
1186 if token.type == tokenize.NAME and name_turn:
1184 name_turn = False
1187 parts.append(token.string)
1185 elif token.type == tokenize.OP and token.string == "." and not name_turn:
1188 name_turn = False
1186 parts.append(token.string)
1189 elif (
1187 name_turn = True
1190 token.type == tokenize.OP and token.string == "." and not name_turn
1188 else:
1191 ):
1189 # short-circuit if not empty nor name token
1192 parts.append(token.string)
1190 break
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 def _evaluate_expr(self, expr):
1207 def _evaluate_expr(self, expr):
1197 obj = not_found
1208 obj = not_found
@@ -1973,9 +1984,8 b' class IPCompleter(Completer):'
1973 *self.magic_arg_matchers,
1984 *self.magic_arg_matchers,
1974 self.custom_completer_matcher,
1985 self.custom_completer_matcher,
1975 self.dict_key_matcher,
1986 self.dict_key_matcher,
1976 # TODO: convert python_matches to v2 API
1977 self.magic_matcher,
1987 self.magic_matcher,
1978 self.python_matches,
1988 self.python_matcher,
1979 self.file_matcher,
1989 self.file_matcher,
1980 self.python_func_kw_matcher,
1990 self.python_func_kw_matcher,
1981 ]
1991 ]
@@ -2316,9 +2326,42 b' class IPCompleter(Completer):'
2316 else:
2326 else:
2317 return iter([])
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 @completion_matcher(api_version=1)
2359 @completion_matcher(api_version=1)
2320 def python_matches(self, text: str) -> Iterable[str]:
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 if "." in text:
2365 if "." in text:
2323 try:
2366 try:
2324 matches = self.attr_matches(text)
2367 matches = self.attr_matches(text)
@@ -462,7 +462,10 b' class TestCompleter(unittest.TestCase):'
462 matches = c.all_completions("TestClass.")
462 matches = c.all_completions("TestClass.")
463 assert len(matches) > 2, (jedi_status, matches)
463 assert len(matches) > 2, (jedi_status, matches)
464 matches = c.all_completions("TestClass.a")
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 @pytest.mark.xfail(
470 @pytest.mark.xfail(
468 sys.version_info.releaselevel in ("alpha",),
471 sys.version_info.releaselevel in ("alpha",),
@@ -594,7 +597,7 b' class TestCompleter(unittest.TestCase):'
594 ip.Completer.use_jedi = True
597 ip.Completer.use_jedi = True
595 with provisionalcompleter():
598 with provisionalcompleter():
596 completions = ip.Completer.completions(line, cursor_pos)
599 completions = ip.Completer.completions(line, cursor_pos)
597 self.assertIn(completion, completions)
600 self.assertIn(completion, list(completions))
598
601
599 with provisionalcompleter():
602 with provisionalcompleter():
600 _(
603 _(
@@ -622,7 +625,7 b' class TestCompleter(unittest.TestCase):'
622 _(
625 _(
623 "assert str.star",
626 "assert str.star",
624 14,
627 14,
625 "str.startswith",
628 ".startswith",
626 "Should have completed on `assert str.star`: %s",
629 "Should have completed on `assert str.star`: %s",
627 Completion(11, 14, "startswith"),
630 Completion(11, 14, "startswith"),
628 )
631 )
@@ -633,6 +636,13 b' class TestCompleter(unittest.TestCase):'
633 "Should have completed on `d['a b'].str`: %s",
636 "Should have completed on `d['a b'].str`: %s",
634 Completion(9, 12, "strip"),
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 def test_omit__names(self):
647 def test_omit__names(self):
638 # also happens to test IPCompleter as a configurable
648 # also happens to test IPCompleter as a configurable
@@ -647,8 +657,8 b' class TestCompleter(unittest.TestCase):'
647 with provisionalcompleter():
657 with provisionalcompleter():
648 c.use_jedi = False
658 c.use_jedi = False
649 s, matches = c.complete("ip.")
659 s, matches = c.complete("ip.")
650 self.assertIn("ip.__str__", matches)
660 self.assertIn(".__str__", matches)
651 self.assertIn("ip._hidden_attr", matches)
661 self.assertIn("._hidden_attr", matches)
652
662
653 # c.use_jedi = True
663 # c.use_jedi = True
654 # completions = set(c.completions('ip.', 3))
664 # completions = set(c.completions('ip.', 3))
@@ -661,7 +671,7 b' class TestCompleter(unittest.TestCase):'
661 with provisionalcompleter():
671 with provisionalcompleter():
662 c.use_jedi = False
672 c.use_jedi = False
663 s, matches = c.complete("ip.")
673 s, matches = c.complete("ip.")
664 self.assertNotIn("ip.__str__", matches)
674 self.assertNotIn(".__str__", matches)
665 # self.assertIn('ip._hidden_attr', matches)
675 # self.assertIn('ip._hidden_attr', matches)
666
676
667 # c.use_jedi = True
677 # c.use_jedi = True
@@ -675,8 +685,8 b' class TestCompleter(unittest.TestCase):'
675 with provisionalcompleter():
685 with provisionalcompleter():
676 c.use_jedi = False
686 c.use_jedi = False
677 s, matches = c.complete("ip.")
687 s, matches = c.complete("ip.")
678 self.assertNotIn("ip.__str__", matches)
688 self.assertNotIn(".__str__", matches)
679 self.assertNotIn("ip._hidden_attr", matches)
689 self.assertNotIn("._hidden_attr", matches)
680
690
681 # c.use_jedi = True
691 # c.use_jedi = True
682 # completions = set(c.completions('ip.', 3))
692 # completions = set(c.completions('ip.', 3))
@@ -686,7 +696,7 b' class TestCompleter(unittest.TestCase):'
686 with provisionalcompleter():
696 with provisionalcompleter():
687 c.use_jedi = False
697 c.use_jedi = False
688 s, matches = c.complete("ip._x.")
698 s, matches = c.complete("ip._x.")
689 self.assertIn("ip._x.keys", matches)
699 self.assertIn(".keys", matches)
690
700
691 # c.use_jedi = True
701 # c.use_jedi = True
692 # completions = set(c.completions('ip._x.', 6))
702 # completions = set(c.completions('ip._x.', 6))
@@ -697,7 +707,7 b' class TestCompleter(unittest.TestCase):'
697
707
698 def test_limit_to__all__False_ok(self):
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 ip = get_ipython()
712 ip = get_ipython()
703 c = ip.Completer
713 c = ip.Completer
@@ -708,7 +718,7 b' class TestCompleter(unittest.TestCase):'
708 cfg.IPCompleter.limit_to__all__ = False
718 cfg.IPCompleter.limit_to__all__ = False
709 c.update_config(cfg)
719 c.update_config(cfg)
710 s, matches = c.complete("d.")
720 s, matches = c.complete("d.")
711 self.assertIn("d.x", matches)
721 self.assertIn(".x", matches)
712
722
713 def test_get__all__entries_ok(self):
723 def test_get__all__entries_ok(self):
714 class A:
724 class A:
General Comments 0
You need to be logged in to leave comments. Login now