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 == |
|
|
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,6 +1139,9 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): | |
|
1142 | 1145 | m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) |
|
1143 | 1146 | if not m2: |
|
1144 | 1147 | return [] |
@@ -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 |
|
|
|
1175 | skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} | |
|
1176 | name_turn = True | |
|
1177 | ||
|
1178 | parts = [] | |
|
1179 | for token in rev_tokens: | |
|
1180 |
|
|
|
1181 |
|
|
|
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 |
|
|
|
1189 | # short-circuit if not empty nor name token | |
|
1190 |
|
|
|
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_matche |
|
|
1988 | self.python_matcher, | |
|
1979 | 1989 | self.file_matcher, |
|
1980 | 1990 | self.python_func_kw_matcher, |
|
1981 | 1991 | ] |
@@ -2316,9 +2326,41 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 | return _convert_matcher_v1_result_to_v2(matches, type="variable") | |
|
2357 | ||
|
2319 | 2358 | @completion_matcher(api_version=1) |
|
2320 | 2359 | def python_matches(self, text: str) -> Iterable[str]: |
|
2321 |
"""Match attributes or global python names |
|
|
2360 | """Match attributes or global python names. | |
|
2361 | ||
|
2362 | .. deprecated:: 8.27 | |
|
2363 | You can use :meth:`python_matcher` instead.""" | |
|
2322 | 2364 | if "." in text: |
|
2323 | 2365 | try: |
|
2324 | 2366 | 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 |
" |
|
|
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(" |
|
|
651 |
self.assertIn(" |
|
|
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(" |
|
|
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(" |
|
|
679 |
self.assertNotIn(" |
|
|
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(" |
|
|
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(" |
|
|
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