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 == |
|
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 |
|
|
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 |
|
|
1183 | for token in rev_tokens: | |
1181 |
|
|
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 |
|
|
1191 | ): | |
1189 | # short-circuit if not empty nor name token |
|
1192 | parts.append(token.string) | |
1190 |
|
|
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_matche |
|
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 |
" |
|
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(" |
|
660 | self.assertIn(".__str__", matches) | |
651 |
self.assertIn(" |
|
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(" |
|
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(" |
|
688 | self.assertNotIn(".__str__", matches) | |
679 |
self.assertNotIn(" |
|
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(" |
|
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(" |
|
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