Show More
This diff has been collapsed as it changes many lines, (767 lines changed) Show them Hide them | |||||
@@ -100,6 +100,30 option. | |||||
100 |
|
100 | |||
101 | Be sure to update :any:`jedi` to the latest stable version or to try the |
|
101 | Be sure to update :any:`jedi` to the latest stable version or to try the | |
102 | current development version to get better completions. |
|
102 | current development version to get better completions. | |
|
103 | ||||
|
104 | Matchers | |||
|
105 | ======== | |||
|
106 | ||||
|
107 | All completions routines are implemented using unified ``matchers`` API. | |||
|
108 | The matchers API is provisional and subject to change without notice. | |||
|
109 | ||||
|
110 | The built-in matchers include: | |||
|
111 | ||||
|
112 | - ``IPCompleter.dict_key_matcher``: dictionary key completions, | |||
|
113 | - ``IPCompleter.magic_matcher``: completions for magics, | |||
|
114 | - ``IPCompleter.unicode_name_matcher``, ``IPCompleter.fwd_unicode_matcher`` and ``IPCompleter.latex_matcher``: see `Forward latex/unicode completion`_, | |||
|
115 | - ``back_unicode_name_matcher`` and ``back_latex_name_matcher``: see `Backward latex completion`_, | |||
|
116 | - ``IPCompleter.file_matcher``: paths to files and directories, | |||
|
117 | - ``IPCompleter.python_func_kw_matcher`` - function keywords, | |||
|
118 | - ``IPCompleter.python_matches`` - globals and attributes (v1 API), | |||
|
119 | - ``IPCompleter.jedi_matcher`` - static analysis with Jedi, | |||
|
120 | - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in ``core.InteractiveShell`` | |||
|
121 | which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions). | |||
|
122 | Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match | |||
|
123 | behaviour in earlier IPython versions. | |||
|
124 | ||||
|
125 | Adding custom matchers is possible by appending to `IPCompleter.custom_matchers` list, | |||
|
126 | but please be aware that this API is subject to change. | |||
103 | """ |
|
127 | """ | |
104 |
|
128 | |||
105 |
|
129 | |||
@@ -124,9 +148,26 import unicodedata | |||||
124 | import uuid |
|
148 | import uuid | |
125 | import warnings |
|
149 | import warnings | |
126 | from contextlib import contextmanager |
|
150 | from contextlib import contextmanager | |
|
151 | from functools import lru_cache, partial | |||
127 | from importlib import import_module |
|
152 | from importlib import import_module | |
128 | from types import SimpleNamespace |
|
153 | from types import SimpleNamespace | |
129 | from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional |
|
154 | from typing import ( | |
|
155 | Iterable, | |||
|
156 | Iterator, | |||
|
157 | List, | |||
|
158 | Tuple, | |||
|
159 | Union, | |||
|
160 | Any, | |||
|
161 | Sequence, | |||
|
162 | Dict, | |||
|
163 | NamedTuple, | |||
|
164 | Pattern, | |||
|
165 | Optional, | |||
|
166 | Callable, | |||
|
167 | TYPE_CHECKING, | |||
|
168 | Set, | |||
|
169 | ) | |||
|
170 | from typing_extensions import TypedDict, NotRequired | |||
130 |
|
171 | |||
131 | from IPython.core.error import TryNext |
|
172 | from IPython.core.error import TryNext | |
132 | from IPython.core.inputtransformer2 import ESC_MAGIC |
|
173 | from IPython.core.inputtransformer2 import ESC_MAGIC | |
@@ -137,7 +178,17 from IPython.utils import generics | |||||
137 | from IPython.utils.dir2 import dir2, get_real_method |
|
178 | from IPython.utils.dir2 import dir2, get_real_method | |
138 | from IPython.utils.path import ensure_dir_exists |
|
179 | from IPython.utils.path import ensure_dir_exists | |
139 | from IPython.utils.process import arg_split |
|
180 | from IPython.utils.process import arg_split | |
140 | from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe |
|
181 | from traitlets import ( | |
|
182 | Bool, | |||
|
183 | Enum, | |||
|
184 | Int, | |||
|
185 | List as ListTrait, | |||
|
186 | Unicode, | |||
|
187 | Dict as DictTrait, | |||
|
188 | Union as UnionTrait, | |||
|
189 | default, | |||
|
190 | observe, | |||
|
191 | ) | |||
141 | from traitlets.config.configurable import Configurable |
|
192 | from traitlets.config.configurable import Configurable | |
142 |
|
193 | |||
143 | import __main__ |
|
194 | import __main__ | |
@@ -145,6 +196,7 import __main__ | |||||
145 | # skip module docstests |
|
196 | # skip module docstests | |
146 | __skip_doctest__ = True |
|
197 | __skip_doctest__ = True | |
147 |
|
198 | |||
|
199 | ||||
148 | try: |
|
200 | try: | |
149 | import jedi |
|
201 | import jedi | |
150 | jedi.settings.case_insensitive_completion = False |
|
202 | jedi.settings.case_insensitive_completion = False | |
@@ -153,7 +205,16 try: | |||||
153 | JEDI_INSTALLED = True |
|
205 | JEDI_INSTALLED = True | |
154 | except ImportError: |
|
206 | except ImportError: | |
155 | JEDI_INSTALLED = False |
|
207 | JEDI_INSTALLED = False | |
156 | #----------------------------------------------------------------------------- |
|
208 | ||
|
209 | if TYPE_CHECKING: | |||
|
210 | from typing import cast | |||
|
211 | else: | |||
|
212 | ||||
|
213 | def cast(obj, _type): | |||
|
214 | return obj | |||
|
215 | ||||
|
216 | ||||
|
217 | # ----------------------------------------------------------------------------- | |||
157 | # Globals |
|
218 | # Globals | |
158 | #----------------------------------------------------------------------------- |
|
219 | #----------------------------------------------------------------------------- | |
159 |
|
220 | |||
@@ -177,6 +238,8 else: | |||||
177 | # may have trouble processing. |
|
238 | # may have trouble processing. | |
178 | MATCHES_LIMIT = 500 |
|
239 | MATCHES_LIMIT = 500 | |
179 |
|
240 | |||
|
241 | # Completion type reported when no type can be inferred. | |||
|
242 | _UNKNOWN_TYPE = "<unknown>" | |||
180 |
|
243 | |||
181 | class ProvisionalCompleterWarning(FutureWarning): |
|
244 | class ProvisionalCompleterWarning(FutureWarning): | |
182 | """ |
|
245 | """ | |
@@ -355,6 +418,9 class _FakeJediCompletion: | |||||
355 | return '<Fake completion object jedi has crashed>' |
|
418 | return '<Fake completion object jedi has crashed>' | |
356 |
|
419 | |||
357 |
|
420 | |||
|
421 | _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion] | |||
|
422 | ||||
|
423 | ||||
358 | class Completion: |
|
424 | class Completion: | |
359 | """ |
|
425 | """ | |
360 | Completion object used and return by IPython completers. |
|
426 | Completion object used and return by IPython completers. | |
@@ -417,6 +483,131 class Completion: | |||||
417 | return hash((self.start, self.end, self.text)) |
|
483 | return hash((self.start, self.end, self.text)) | |
418 |
|
484 | |||
419 |
|
485 | |||
|
486 | class SimpleCompletion: | |||
|
487 | # TODO: decide whether we should keep the ``SimpleCompletion`` separate from ``Completion`` | |||
|
488 | # there are two advantages of keeping them separate: | |||
|
489 | # - compatibility with old readline `Completer.complete` interface (less important) | |||
|
490 | # - ease of use for third parties (just return matched text and don't worry about coordinates) | |||
|
491 | # the disadvantage is that we need to loop over the completions again to transform them into | |||
|
492 | # `Completion` objects (but it was done like that before the refactor into `SimpleCompletion` too). | |||
|
493 | __slots__ = ["text", "type"] | |||
|
494 | ||||
|
495 | def __init__(self, text: str, *, type: str = None): | |||
|
496 | self.text = text | |||
|
497 | self.type = type | |||
|
498 | ||||
|
499 | def __repr__(self): | |||
|
500 | return f"<SimpleCompletion text={self.text!r} type={self.type!r}>" | |||
|
501 | ||||
|
502 | ||||
|
503 | class _MatcherResultBase(TypedDict): | |||
|
504 | ||||
|
505 | #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token. | |||
|
506 | matched_fragment: NotRequired[str] | |||
|
507 | ||||
|
508 | #: whether to suppress results from other matchers; default is False. | |||
|
509 | suppress_others: NotRequired[bool] | |||
|
510 | ||||
|
511 | #: are completions already ordered and should be left as-is? default is False. | |||
|
512 | ordered: NotRequired[bool] | |||
|
513 | ||||
|
514 | # TODO: should we used a relevance score for ordering? | |||
|
515 | #: value between 0 (likely not relevant) and 100 (likely relevant); default is 50. | |||
|
516 | # relevance: NotRequired[float] | |||
|
517 | ||||
|
518 | ||||
|
519 | class SimpleMatcherResult(_MatcherResultBase): | |||
|
520 | """Result of new-style completion matcher.""" | |||
|
521 | ||||
|
522 | #: list of candidate completions | |||
|
523 | completions: Sequence[SimpleCompletion] | |||
|
524 | ||||
|
525 | ||||
|
526 | class _JediMatcherResult(_MatcherResultBase): | |||
|
527 | """Matching result returned by Jedi (will be processed differently)""" | |||
|
528 | ||||
|
529 | #: list of candidate completions | |||
|
530 | completions: Iterable[_JediCompletionLike] | |||
|
531 | ||||
|
532 | ||||
|
533 | class CompletionContext(NamedTuple): | |||
|
534 | # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`) | |||
|
535 | # which was not explicitly visible as an argument of the matcher, making any refactor | |||
|
536 | # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers | |||
|
537 | # from the completer, and make substituting them in sub-classes easier. | |||
|
538 | ||||
|
539 | #: Relevant fragment of code directly preceding the cursor. | |||
|
540 | #: The extraction of token is implemented via splitter heuristic | |||
|
541 | #: (following readline behaviour for legacy reasons), which is user configurable | |||
|
542 | #: (by switching the greedy mode). | |||
|
543 | token: str | |||
|
544 | ||||
|
545 | full_text: str | |||
|
546 | ||||
|
547 | #: Cursor position in the line (the same for ``full_text`` and `text``). | |||
|
548 | cursor_position: int | |||
|
549 | ||||
|
550 | #: Cursor line in ``full_text``. | |||
|
551 | cursor_line: int | |||
|
552 | ||||
|
553 | @property | |||
|
554 | @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7 | |||
|
555 | def text_until_cursor(self) -> str: | |||
|
556 | return self.line_with_cursor[: self.cursor_position] | |||
|
557 | ||||
|
558 | @property | |||
|
559 | @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7 | |||
|
560 | def line_with_cursor(self) -> str: | |||
|
561 | return self.full_text.split("\n")[self.cursor_line] | |||
|
562 | ||||
|
563 | ||||
|
564 | MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult] | |||
|
565 | ||||
|
566 | MatcherAPIv1 = Callable[[str], List[str]] | |||
|
567 | MatcherAPIv2 = Callable[[CompletionContext], MatcherResult] | |||
|
568 | Matcher = Union[MatcherAPIv1, MatcherAPIv2] | |||
|
569 | ||||
|
570 | ||||
|
571 | def completion_matcher( | |||
|
572 | *, priority: float = None, identifier: str = None, api_version=1 | |||
|
573 | ): | |||
|
574 | """Adds attributes describing the matcher. | |||
|
575 | ||||
|
576 | Parameters | |||
|
577 | ---------- | |||
|
578 | priority : Optional[float] | |||
|
579 | The priority of the matcher, determines the order of execution of matchers. | |||
|
580 | Higher priority means that the matcher will be executed first. Defaults to 50. | |||
|
581 | identifier : Optional[str] | |||
|
582 | identifier of the matcher allowing users to modify the behaviour via traitlets, | |||
|
583 | and also used to for debugging (will be passed as ``origin`` with the completions). | |||
|
584 | Defaults to matcher function ``__qualname__``. | |||
|
585 | api_version: Optional[int] | |||
|
586 | version of the Matcher API used by this matcher. | |||
|
587 | Currently supported values are 1 and 2. | |||
|
588 | Defaults to 1. | |||
|
589 | """ | |||
|
590 | ||||
|
591 | def wrapper(func: Matcher): | |||
|
592 | func.matcher_priority = priority | |||
|
593 | func.matcher_identifier = identifier or func.__qualname__ | |||
|
594 | func.matcher_api_version = api_version | |||
|
595 | return func | |||
|
596 | ||||
|
597 | return wrapper | |||
|
598 | ||||
|
599 | ||||
|
600 | def _get_matcher_id(matcher: Matcher): | |||
|
601 | return getattr(matcher, "matcher_identifier", matcher.__qualname__) | |||
|
602 | ||||
|
603 | ||||
|
604 | def _get_matcher_api_version(matcher): | |||
|
605 | return getattr(matcher, "matcher_api_version", 1) | |||
|
606 | ||||
|
607 | ||||
|
608 | context_matcher = partial(completion_matcher, api_version=2) | |||
|
609 | ||||
|
610 | ||||
420 | _IC = Iterable[Completion] |
|
611 | _IC = Iterable[Completion] | |
421 |
|
612 | |||
422 |
|
613 | |||
@@ -920,7 +1111,16 def _safe_isinstance(obj, module, class_name): | |||||
920 | return (module in sys.modules and |
|
1111 | return (module in sys.modules and | |
921 | isinstance(obj, getattr(import_module(module), class_name))) |
|
1112 | isinstance(obj, getattr(import_module(module), class_name))) | |
922 |
|
1113 | |||
923 | def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: |
|
1114 | ||
|
1115 | @context_matcher() | |||
|
1116 | def back_unicode_name_matcher(context): | |||
|
1117 | fragment, matches = back_unicode_name_matches(context.token) | |||
|
1118 | return _convert_matcher_v1_result_to_v2( | |||
|
1119 | matches, type="unicode", fragment=fragment, suppress_if_matches=True | |||
|
1120 | ) | |||
|
1121 | ||||
|
1122 | ||||
|
1123 | def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]: | |||
924 | """Match Unicode characters back to Unicode name |
|
1124 | """Match Unicode characters back to Unicode name | |
925 |
|
1125 | |||
926 | This does ``☃`` -> ``\\snowman`` |
|
1126 | This does ``☃`` -> ``\\snowman`` | |
@@ -959,7 +1159,16 def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: | |||||
959 | pass |
|
1159 | pass | |
960 | return '', () |
|
1160 | return '', () | |
961 |
|
1161 | |||
962 | def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : |
|
1162 | ||
|
1163 | @context_matcher() | |||
|
1164 | def back_latex_name_matcher(context): | |||
|
1165 | fragment, matches = back_latex_name_matches(context.token) | |||
|
1166 | return _convert_matcher_v1_result_to_v2( | |||
|
1167 | matches, type="latex", fragment=fragment, suppress_if_matches=True | |||
|
1168 | ) | |||
|
1169 | ||||
|
1170 | ||||
|
1171 | def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]: | |||
963 | """Match latex characters back to unicode name |
|
1172 | """Match latex characters back to unicode name | |
964 |
|
1173 | |||
965 | This does ``\\ℵ`` -> ``\\aleph`` |
|
1174 | This does ``\\ℵ`` -> ``\\aleph`` | |
@@ -1038,11 +1247,25 def _make_signature(completion)-> str: | |||||
1038 | for p in signature.defined_names()) if f]) |
|
1247 | for p in signature.defined_names()) if f]) | |
1039 |
|
1248 | |||
1040 |
|
1249 | |||
1041 | class _CompleteResult(NamedTuple): |
|
1250 | _CompleteResult = Dict[str, MatcherResult] | |
1042 | matched_text : str |
|
1251 | ||
1043 | matches: Sequence[str] |
|
1252 | ||
1044 | matches_origin: Sequence[str] |
|
1253 | def _convert_matcher_v1_result_to_v2( | |
1045 | jedi_matches: Any |
|
1254 | matches: Sequence[str], | |
|
1255 | type: str, | |||
|
1256 | fragment: str = None, | |||
|
1257 | suppress_if_matches: bool = False, | |||
|
1258 | ) -> SimpleMatcherResult: | |||
|
1259 | """Utility to help with transition""" | |||
|
1260 | result = { | |||
|
1261 | "completions": [SimpleCompletion(text=match, type=type) for match in matches], | |||
|
1262 | "suppress_others": (True if matches else False) | |||
|
1263 | if suppress_if_matches | |||
|
1264 | else False, | |||
|
1265 | } | |||
|
1266 | if fragment is not None: | |||
|
1267 | result["matched_fragment"] = fragment | |||
|
1268 | return result | |||
1046 |
|
1269 | |||
1047 |
|
1270 | |||
1048 | class IPCompleter(Completer): |
|
1271 | class IPCompleter(Completer): | |
@@ -1058,17 +1281,58 class IPCompleter(Completer): | |||||
1058 | else: |
|
1281 | else: | |
1059 | self.splitter.delims = DELIMS |
|
1282 | self.splitter.delims = DELIMS | |
1060 |
|
1283 | |||
1061 |
dict_keys_only = Bool( |
|
1284 | dict_keys_only = Bool( | |
1062 | help="""Whether to show dict key matches only""") |
|
1285 | False, | |
|
1286 | help=""" | |||
|
1287 | Whether to show dict key matches only. | |||
|
1288 | ||||
|
1289 | (disables all matchers except for `IPCompleter.dict_key_matcher`). | |||
|
1290 | """, | |||
|
1291 | ) | |||
|
1292 | ||||
|
1293 | suppress_competing_matchers = UnionTrait( | |||
|
1294 | [Bool(), DictTrait(Bool(None, allow_none=True))], | |||
|
1295 | help=""" | |||
|
1296 | Whether to suppress completions from other `Matchers`_. | |||
|
1297 | ||||
|
1298 | When set to ``None`` (default) the matchers will attempt to auto-detect | |||
|
1299 | whether suppression of other matchers is desirable. For example, at | |||
|
1300 | the beginning of a line followed by `%` we expect a magic completion | |||
|
1301 | to be the only applicable option, and after ``my_dict['`` we usually | |||
|
1302 | expect a completion with an existing dictionary key. | |||
|
1303 | ||||
|
1304 | If you want to disable this heuristic and see completions from all matchers, | |||
|
1305 | set ``IPCompleter.suppress_competing_matchers = False``. | |||
|
1306 | To disable the heuristic for specific matchers provide a dictionary mapping: | |||
|
1307 | ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``. | |||
|
1308 | ||||
|
1309 | Set ``IPCompleter.suppress_competing_matchers = True`` to limit | |||
|
1310 | completions to the set of matchers with the highest priority; | |||
|
1311 | this is equivalent to ``IPCompleter.merge_completions`` and | |||
|
1312 | can be beneficial for performance, but will sometimes omit relevant | |||
|
1313 | candidates from matchers further down the priority list. | |||
|
1314 | """, | |||
|
1315 | ).tag(config=True) | |||
1063 |
|
1316 | |||
1064 |
merge_completions = Bool( |
|
1317 | merge_completions = Bool( | |
|
1318 | True, | |||
1065 | help="""Whether to merge completion results into a single list |
|
1319 | help="""Whether to merge completion results into a single list | |
1066 |
|
1320 | |||
1067 | If False, only the completion results from the first non-empty |
|
1321 | If False, only the completion results from the first non-empty | |
1068 | completer will be returned. |
|
1322 | completer will be returned. | |
1069 |
|
|
1323 | ||
|
1324 | As of version 8.5.0, setting the value to ``False`` is an alias for: | |||
|
1325 | ``IPCompleter.suppress_competing_matchers = True.``. | |||
|
1326 | """, | |||
|
1327 | ).tag(config=True) | |||
|
1328 | ||||
|
1329 | disable_matchers = ListTrait( | |||
|
1330 | Unicode(), help="""List of matchers to disable.""" | |||
1070 | ).tag(config=True) |
|
1331 | ).tag(config=True) | |
1071 | omit__names = Enum((0,1,2), default_value=2, |
|
1332 | ||
|
1333 | omit__names = Enum( | |||
|
1334 | (0, 1, 2), | |||
|
1335 | default_value=2, | |||
1072 | help="""Instruct the completer to omit private method names |
|
1336 | help="""Instruct the completer to omit private method names | |
1073 |
|
1337 | |||
1074 | Specifically, when completing on ``object.<tab>``. |
|
1338 | Specifically, when completing on ``object.<tab>``. | |
@@ -1144,7 +1408,7 class IPCompleter(Completer): | |||||
1144 | namespace=namespace, |
|
1408 | namespace=namespace, | |
1145 | global_namespace=global_namespace, |
|
1409 | global_namespace=global_namespace, | |
1146 | config=config, |
|
1410 | config=config, | |
1147 | **kwargs |
|
1411 | **kwargs, | |
1148 | ) |
|
1412 | ) | |
1149 |
|
1413 | |||
1150 | # List where completion matches will be stored |
|
1414 | # List where completion matches will be stored | |
@@ -1173,8 +1437,8 class IPCompleter(Completer): | |||||
1173 | #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') |
|
1437 | #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') | |
1174 |
|
1438 | |||
1175 | self.magic_arg_matchers = [ |
|
1439 | self.magic_arg_matchers = [ | |
1176 |
self.magic_config_matche |
|
1440 | self.magic_config_matcher, | |
1177 |
self.magic_color_matche |
|
1441 | self.magic_color_matcher, | |
1178 | ] |
|
1442 | ] | |
1179 |
|
1443 | |||
1180 | # This is set externally by InteractiveShell |
|
1444 | # This is set externally by InteractiveShell | |
@@ -1186,27 +1450,53 class IPCompleter(Completer): | |||||
1186 | # attribute through the `@unicode_names` property. |
|
1450 | # attribute through the `@unicode_names` property. | |
1187 | self._unicode_names = None |
|
1451 | self._unicode_names = None | |
1188 |
|
1452 | |||
|
1453 | self._backslash_combining_matchers = [ | |||
|
1454 | self.latex_name_matcher, | |||
|
1455 | self.unicode_name_matcher, | |||
|
1456 | back_latex_name_matcher, | |||
|
1457 | back_unicode_name_matcher, | |||
|
1458 | self.fwd_unicode_matcher, | |||
|
1459 | ] | |||
|
1460 | ||||
|
1461 | if not self.backslash_combining_completions: | |||
|
1462 | for matcher in self._backslash_combining_matchers: | |||
|
1463 | self.disable_matchers.append(matcher.matcher_identifier) | |||
|
1464 | ||||
|
1465 | if not self.merge_completions: | |||
|
1466 | self.suppress_competing_matchers = True | |||
|
1467 | ||||
|
1468 | if self.dict_keys_only: | |||
|
1469 | self.disable_matchers.append(self.dict_key_matcher.matcher_identifier) | |||
|
1470 | ||||
1189 | @property |
|
1471 | @property | |
1190 |
def matchers(self) -> List[ |
|
1472 | def matchers(self) -> List[Matcher]: | |
1191 | """All active matcher routines for completion""" |
|
1473 | """All active matcher routines for completion""" | |
1192 | if self.dict_keys_only: |
|
1474 | if self.dict_keys_only: | |
1193 |
return [self.dict_key_matche |
|
1475 | return [self.dict_key_matcher] | |
1194 |
|
1476 | |||
1195 | if self.use_jedi: |
|
1477 | if self.use_jedi: | |
1196 | return [ |
|
1478 | return [ | |
1197 | *self.custom_matchers, |
|
1479 | *self.custom_matchers, | |
1198 |
self. |
|
1480 | *self._backslash_combining_matchers, | |
1199 |
self. |
|
1481 | *self.magic_arg_matchers, | |
1200 |
self. |
|
1482 | self.custom_completer_matcher, | |
|
1483 | self.magic_matcher, | |||
|
1484 | self._jedi_matcher, | |||
|
1485 | self.dict_key_matcher, | |||
|
1486 | self.file_matcher, | |||
1201 | ] |
|
1487 | ] | |
1202 | else: |
|
1488 | else: | |
1203 | return [ |
|
1489 | return [ | |
1204 | *self.custom_matchers, |
|
1490 | *self.custom_matchers, | |
1205 |
self. |
|
1491 | *self._backslash_combining_matchers, | |
|
1492 | *self.magic_arg_matchers, | |||
|
1493 | self.custom_completer_matcher, | |||
|
1494 | self.dict_key_matcher, | |||
|
1495 | # TODO: convert python_matches to v2 API | |||
|
1496 | self.magic_matcher, | |||
1206 | self.python_matches, |
|
1497 | self.python_matches, | |
1207 |
self.file_matche |
|
1498 | self.file_matcher, | |
1208 |
self. |
|
1499 | self.python_func_kw_matcher, | |
1209 | self.python_func_kw_matches, |
|
|||
1210 | ] |
|
1500 | ] | |
1211 |
|
1501 | |||
1212 | def all_completions(self, text:str) -> List[str]: |
|
1502 | def all_completions(self, text:str) -> List[str]: | |
@@ -1227,7 +1517,14 class IPCompleter(Completer): | |||||
1227 | return [f.replace("\\","/") |
|
1517 | return [f.replace("\\","/") | |
1228 | for f in self.glob("%s*" % text)] |
|
1518 | for f in self.glob("%s*" % text)] | |
1229 |
|
1519 | |||
1230 | def file_matches(self, text:str)->List[str]: |
|
1520 | @context_matcher() | |
|
1521 | def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1522 | matches = self.file_matches(context.token) | |||
|
1523 | # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter, | |||
|
1524 | # starts with `/home/`, `C:\`, etc) | |||
|
1525 | return _convert_matcher_v1_result_to_v2(matches, type="path") | |||
|
1526 | ||||
|
1527 | def file_matches(self, text: str) -> List[str]: | |||
1231 | """Match filenames, expanding ~USER type strings. |
|
1528 | """Match filenames, expanding ~USER type strings. | |
1232 |
|
1529 | |||
1233 | Most of the seemingly convoluted logic in this completer is an |
|
1530 | Most of the seemingly convoluted logic in this completer is an | |
@@ -1309,7 +1606,16 class IPCompleter(Completer): | |||||
1309 | # Mark directories in input list by appending '/' to their names. |
|
1606 | # Mark directories in input list by appending '/' to their names. | |
1310 | return [x+'/' if os.path.isdir(x) else x for x in matches] |
|
1607 | return [x+'/' if os.path.isdir(x) else x for x in matches] | |
1311 |
|
1608 | |||
1312 | def magic_matches(self, text:str): |
|
1609 | @context_matcher() | |
|
1610 | def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1611 | text = context.token | |||
|
1612 | matches = self.magic_matches(text) | |||
|
1613 | result = _convert_matcher_v1_result_to_v2(matches, type="magic") | |||
|
1614 | is_magic_prefix = len(text) > 0 and text[0] == "%" | |||
|
1615 | result["suppress_others"] = is_magic_prefix and bool(result["completions"]) | |||
|
1616 | return result | |||
|
1617 | ||||
|
1618 | def magic_matches(self, text: str): | |||
1313 | """Match magics""" |
|
1619 | """Match magics""" | |
1314 | # Get all shell magics now rather than statically, so magics loaded at |
|
1620 | # Get all shell magics now rather than statically, so magics loaded at | |
1315 | # runtime show up too. |
|
1621 | # runtime show up too. | |
@@ -1351,8 +1657,14 class IPCompleter(Completer): | |||||
1351 |
|
1657 | |||
1352 | return comp |
|
1658 | return comp | |
1353 |
|
1659 | |||
1354 | def magic_config_matches(self, text:str) -> List[str]: |
|
1660 | @context_matcher() | |
1355 | """ Match class names and attributes for %config magic """ |
|
1661 | def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |
|
1662 | # NOTE: uses `line_buffer` equivalent for compatibility | |||
|
1663 | matches = self.magic_config_matches(context.line_with_cursor) | |||
|
1664 | return _convert_matcher_v1_result_to_v2(matches, type="param") | |||
|
1665 | ||||
|
1666 | def magic_config_matches(self, text: str) -> List[str]: | |||
|
1667 | """Match class names and attributes for %config magic""" | |||
1356 | texts = text.strip().split() |
|
1668 | texts = text.strip().split() | |
1357 |
|
1669 | |||
1358 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): |
|
1670 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): | |
@@ -1386,8 +1698,14 class IPCompleter(Completer): | |||||
1386 | if attr.startswith(texts[1]) ] |
|
1698 | if attr.startswith(texts[1]) ] | |
1387 | return [] |
|
1699 | return [] | |
1388 |
|
1700 | |||
1389 | def magic_color_matches(self, text:str) -> List[str] : |
|
1701 | @context_matcher() | |
1390 | """ Match color schemes for %colors magic""" |
|
1702 | def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |
|
1703 | # NOTE: uses `line_buffer` equivalent for compatibility | |||
|
1704 | matches = self.magic_color_matches(context.line_with_cursor) | |||
|
1705 | return _convert_matcher_v1_result_to_v2(matches, type="param") | |||
|
1706 | ||||
|
1707 | def magic_color_matches(self, text: str) -> List[str]: | |||
|
1708 | """Match color schemes for %colors magic""" | |||
1391 | texts = text.split() |
|
1709 | texts = text.split() | |
1392 | if text.endswith(' '): |
|
1710 | if text.endswith(' '): | |
1393 | # .split() strips off the trailing whitespace. Add '' back |
|
1711 | # .split() strips off the trailing whitespace. Add '' back | |
@@ -1400,9 +1718,24 class IPCompleter(Completer): | |||||
1400 | if color.startswith(prefix) ] |
|
1718 | if color.startswith(prefix) ] | |
1401 | return [] |
|
1719 | return [] | |
1402 |
|
1720 | |||
1403 | def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]: |
|
1721 | @context_matcher(identifier="IPCompleter.jedi_matcher") | |
|
1722 | def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult: | |||
|
1723 | matches = self._jedi_matches( | |||
|
1724 | cursor_column=context.cursor_position, | |||
|
1725 | cursor_line=context.cursor_line, | |||
|
1726 | text=context.full_text, | |||
|
1727 | ) | |||
|
1728 | return { | |||
|
1729 | "completions": matches, | |||
|
1730 | # statis analysis should not suppress other matchers | |||
|
1731 | "suppress_others": False, | |||
|
1732 | } | |||
|
1733 | ||||
|
1734 | def _jedi_matches( | |||
|
1735 | self, cursor_column: int, cursor_line: int, text: str | |||
|
1736 | ) -> Iterable[_JediCompletionLike]: | |||
1404 | """ |
|
1737 | """ | |
1405 |
Return a list of :any:`jedi.api.Completion |
|
1738 | Return a list of :any:`jedi.api.Completion`s object from a ``text`` and | |
1406 | cursor position. |
|
1739 | cursor position. | |
1407 |
|
1740 | |||
1408 | Parameters |
|
1741 | Parameters | |
@@ -1554,6 +1887,11 class IPCompleter(Completer): | |||||
1554 |
|
1887 | |||
1555 | return list(set(ret)) |
|
1888 | return list(set(ret)) | |
1556 |
|
1889 | |||
|
1890 | @context_matcher() | |||
|
1891 | def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |||
|
1892 | matches = self.python_func_kw_matches(context.token) | |||
|
1893 | return _convert_matcher_v1_result_to_v2(matches, type="param") | |||
|
1894 | ||||
1557 | def python_func_kw_matches(self, text): |
|
1895 | def python_func_kw_matches(self, text): | |
1558 | """Match named parameters (kwargs) of the last open function""" |
|
1896 | """Match named parameters (kwargs) of the last open function""" | |
1559 |
|
1897 | |||
@@ -1650,9 +1988,18 class IPCompleter(Completer): | |||||
1650 | return obj.dtype.names or [] |
|
1988 | return obj.dtype.names or [] | |
1651 | return [] |
|
1989 | return [] | |
1652 |
|
1990 | |||
1653 | def dict_key_matches(self, text:str) -> List[str]: |
|
1991 | @context_matcher() | |
1654 | "Match string keys in a dictionary, after e.g. 'foo[' " |
|
1992 | def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult: | |
|
1993 | matches = self.dict_key_matches(context.token) | |||
|
1994 | return _convert_matcher_v1_result_to_v2( | |||
|
1995 | matches, type="dict key", suppress_if_matches=True | |||
|
1996 | ) | |||
|
1997 | ||||
|
1998 | def dict_key_matches(self, text: str) -> List[str]: | |||
|
1999 | """Match string keys in a dictionary, after e.g. ``foo[``. | |||
1655 |
|
|
2000 | ||
|
2001 | DEPRECATED: Deprecated since 8.5. Use ``dict_key_matcher`` instead. | |||
|
2002 | """ | |||
1656 |
|
2003 | |||
1657 | if self.__dict_key_regexps is not None: |
|
2004 | if self.__dict_key_regexps is not None: | |
1658 | regexps = self.__dict_key_regexps |
|
2005 | regexps = self.__dict_key_regexps | |
@@ -1754,8 +2101,15 class IPCompleter(Completer): | |||||
1754 |
|
2101 | |||
1755 | return [leading + k + suf for k in matches] |
|
2102 | return [leading + k + suf for k in matches] | |
1756 |
|
2103 | |||
|
2104 | @context_matcher() | |||
|
2105 | def unicode_name_matcher(self, context): | |||
|
2106 | fragment, matches = self.unicode_name_matches(context.token) | |||
|
2107 | return _convert_matcher_v1_result_to_v2( | |||
|
2108 | matches, type="unicode", fragment=fragment, suppress_if_matches=True | |||
|
2109 | ) | |||
|
2110 | ||||
1757 | @staticmethod |
|
2111 | @staticmethod | |
1758 |
def unicode_name_matches(text:str) -> Tuple[str, List[str]] |
|
2112 | def unicode_name_matches(text: str) -> Tuple[str, List[str]]: | |
1759 | """Match Latex-like syntax for unicode characters base |
|
2113 | """Match Latex-like syntax for unicode characters base | |
1760 | on the name of the character. |
|
2114 | on the name of the character. | |
1761 |
|
2115 | |||
@@ -1776,8 +2130,14 class IPCompleter(Completer): | |||||
1776 | pass |
|
2130 | pass | |
1777 | return '', [] |
|
2131 | return '', [] | |
1778 |
|
2132 | |||
|
2133 | @context_matcher() | |||
|
2134 | def latex_name_matcher(self, context): | |||
|
2135 | fragment, matches = self.latex_matches(context.token) | |||
|
2136 | return _convert_matcher_v1_result_to_v2( | |||
|
2137 | matches, type="latex", fragment=fragment, suppress_if_matches=True | |||
|
2138 | ) | |||
1779 |
|
2139 | |||
1780 | def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: |
|
2140 | def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]: | |
1781 | """Match Latex syntax for unicode characters. |
|
2141 | """Match Latex syntax for unicode characters. | |
1782 |
|
2142 | |||
1783 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` |
|
2143 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` | |
@@ -1797,6 +2157,15 class IPCompleter(Completer): | |||||
1797 | return s, matches |
|
2157 | return s, matches | |
1798 | return '', () |
|
2158 | return '', () | |
1799 |
|
2159 | |||
|
2160 | @context_matcher() | |||
|
2161 | def custom_completer_matcher(self, context): | |||
|
2162 | matches = self.dispatch_custom_completer(context.token) or [] | |||
|
2163 | result = _convert_matcher_v1_result_to_v2( | |||
|
2164 | matches, type="<unknown>", suppress_if_matches=True | |||
|
2165 | ) | |||
|
2166 | result["ordered"] = True | |||
|
2167 | return result | |||
|
2168 | ||||
1800 | def dispatch_custom_completer(self, text): |
|
2169 | def dispatch_custom_completer(self, text): | |
1801 | if not self.custom_completers: |
|
2170 | if not self.custom_completers: | |
1802 | return |
|
2171 | return | |
@@ -1951,12 +2320,25 class IPCompleter(Completer): | |||||
1951 | """ |
|
2320 | """ | |
1952 | deadline = time.monotonic() + _timeout |
|
2321 | deadline = time.monotonic() + _timeout | |
1953 |
|
2322 | |||
1954 |
|
||||
1955 | before = full_text[:offset] |
|
2323 | before = full_text[:offset] | |
1956 | cursor_line, cursor_column = position_to_cursor(full_text, offset) |
|
2324 | cursor_line, cursor_column = position_to_cursor(full_text, offset) | |
1957 |
|
2325 | |||
1958 | matched_text, matches, matches_origin, jedi_matches = self._complete( |
|
2326 | jedi_matcher_id = _get_matcher_id(self._jedi_matcher) | |
1959 | full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) |
|
2327 | ||
|
2328 | results = self._complete( | |||
|
2329 | full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column | |||
|
2330 | ) | |||
|
2331 | non_jedi_results: Dict[str, SimpleMatcherResult] = { | |||
|
2332 | identifier: result | |||
|
2333 | for identifier, result in results.items() | |||
|
2334 | if identifier != jedi_matcher_id | |||
|
2335 | } | |||
|
2336 | ||||
|
2337 | jedi_matches = ( | |||
|
2338 | cast(results[jedi_matcher_id], _JediMatcherResult)["completions"] | |||
|
2339 | if jedi_matcher_id in results | |||
|
2340 | else () | |||
|
2341 | ) | |||
1960 |
|
2342 | |||
1961 | iter_jm = iter(jedi_matches) |
|
2343 | iter_jm = iter(jedi_matches) | |
1962 | if _timeout: |
|
2344 | if _timeout: | |
@@ -1984,27 +2366,55 class IPCompleter(Completer): | |||||
1984 |
|
2366 | |||
1985 | for jm in iter_jm: |
|
2367 | for jm in iter_jm: | |
1986 | delta = len(jm.name_with_symbols) - len(jm.complete) |
|
2368 | delta = len(jm.name_with_symbols) - len(jm.complete) | |
1987 |
yield Completion( |
|
2369 | yield Completion( | |
1988 |
|
|
2370 | start=offset - delta, | |
1989 | text=jm.name_with_symbols, |
|
2371 | end=offset, | |
1990 | type='<unknown>', # don't compute type for speed |
|
2372 | text=jm.name_with_symbols, | |
1991 | _origin='jedi', |
|
2373 | type=_UNKNOWN_TYPE, # don't compute type for speed | |
1992 | signature='') |
|
2374 | _origin="jedi", | |
1993 |
|
2375 | signature="", | ||
1994 |
|
2376 | ) | ||
1995 | start_offset = before.rfind(matched_text) |
|
|||
1996 |
|
2377 | |||
1997 | # TODO: |
|
2378 | # TODO: | |
1998 | # Suppress this, right now just for debug. |
|
2379 | # Suppress this, right now just for debug. | |
1999 |
if jedi_matches and |
|
2380 | if jedi_matches and non_jedi_results and self.debug: | |
2000 | yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', |
|
2381 | some_start_offset = before.rfind( | |
2001 | _origin='debug', type='none', signature='') |
|
2382 | next(iter(non_jedi_results.values()))["matched_fragment"] | |
|
2383 | ) | |||
|
2384 | yield Completion( | |||
|
2385 | start=some_start_offset, | |||
|
2386 | end=offset, | |||
|
2387 | text="--jedi/ipython--", | |||
|
2388 | _origin="debug", | |||
|
2389 | type="none", | |||
|
2390 | signature="", | |||
|
2391 | ) | |||
2002 |
|
2392 | |||
2003 | # I'm unsure if this is always true, so let's assert and see if it |
|
2393 | ordered = [] | |
2004 | # crash |
|
2394 | sortable = [] | |
2005 | assert before.endswith(matched_text) |
|
2395 | ||
2006 | for m, t in zip(matches, matches_origin): |
|
2396 | for origin, result in non_jedi_results.items(): | |
2007 | yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>') |
|
2397 | matched_text = result["matched_fragment"] | |
|
2398 | start_offset = before.rfind(matched_text) | |||
|
2399 | is_ordered = result.get("ordered", False) | |||
|
2400 | container = ordered if is_ordered else sortable | |||
|
2401 | ||||
|
2402 | # I'm unsure if this is always true, so let's assert and see if it | |||
|
2403 | # crash | |||
|
2404 | assert before.endswith(matched_text) | |||
|
2405 | ||||
|
2406 | for simple_completion in result["completions"]: | |||
|
2407 | completion = Completion( | |||
|
2408 | start=start_offset, | |||
|
2409 | end=offset, | |||
|
2410 | text=simple_completion.text, | |||
|
2411 | _origin=origin, | |||
|
2412 | signature="", | |||
|
2413 | type=simple_completion.type or _UNKNOWN_TYPE, | |||
|
2414 | ) | |||
|
2415 | container.append(completion) | |||
|
2416 | ||||
|
2417 | yield from self._deduplicate(ordered + self._sort(sortable)) | |||
2008 |
|
2418 | |||
2009 |
|
2419 | |||
2010 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: |
|
2420 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: | |
@@ -2046,7 +2456,54 class IPCompleter(Completer): | |||||
2046 | PendingDeprecationWarning) |
|
2456 | PendingDeprecationWarning) | |
2047 | # potential todo, FOLD the 3rd throw away argument of _complete |
|
2457 | # potential todo, FOLD the 3rd throw away argument of _complete | |
2048 | # into the first 2 one. |
|
2458 | # into the first 2 one. | |
2049 | return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] |
|
2459 | # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?) | |
|
2460 | # TODO: should we deprecate now, or does it stay? | |||
|
2461 | ||||
|
2462 | results = self._complete( | |||
|
2463 | line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0 | |||
|
2464 | ) | |||
|
2465 | ||||
|
2466 | jedi_matcher_id = _get_matcher_id(self._jedi_matcher) | |||
|
2467 | ||||
|
2468 | return self._arrange_and_extract( | |||
|
2469 | results, | |||
|
2470 | # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version? | |||
|
2471 | skip_matchers={jedi_matcher_id}, | |||
|
2472 | # this API does not support different start/end positions (fragments of token). | |||
|
2473 | abort_if_offset_changes=True, | |||
|
2474 | ) | |||
|
2475 | ||||
|
2476 | def _arrange_and_extract( | |||
|
2477 | self, | |||
|
2478 | results: Dict[str, MatcherResult], | |||
|
2479 | skip_matchers: Set[str], | |||
|
2480 | abort_if_offset_changes: bool, | |||
|
2481 | ): | |||
|
2482 | ||||
|
2483 | sortable = [] | |||
|
2484 | ordered = [] | |||
|
2485 | most_recent_fragment = None | |||
|
2486 | for identifier, result in results.items(): | |||
|
2487 | if identifier in skip_matchers: | |||
|
2488 | continue | |||
|
2489 | if not most_recent_fragment: | |||
|
2490 | most_recent_fragment = result["matched_fragment"] | |||
|
2491 | if ( | |||
|
2492 | abort_if_offset_changes | |||
|
2493 | and result["matched_fragment"] != most_recent_fragment | |||
|
2494 | ): | |||
|
2495 | break | |||
|
2496 | if result.get("ordered", False): | |||
|
2497 | ordered.extend(result["completions"]) | |||
|
2498 | else: | |||
|
2499 | sortable.extend(result["completions"]) | |||
|
2500 | ||||
|
2501 | if not most_recent_fragment: | |||
|
2502 | most_recent_fragment = "" # to satisfy typechecker (and just in case) | |||
|
2503 | ||||
|
2504 | return most_recent_fragment, [ | |||
|
2505 | m.text for m in self._deduplicate(ordered + self._sort(sortable)) | |||
|
2506 | ] | |||
2050 |
|
2507 | |||
2051 | def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, |
|
2508 | def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, | |
2052 | full_text=None) -> _CompleteResult: |
|
2509 | full_text=None) -> _CompleteResult: | |
@@ -2081,14 +2538,10 class IPCompleter(Completer): | |||||
2081 |
|
2538 | |||
2082 | Returns |
|
2539 | Returns | |
2083 | ------- |
|
2540 | ------- | |
2084 | A tuple of N elements which are (likely): |
|
2541 | An ordered dictionary where keys are identifiers of completion | |
2085 | matched_text: ? the text that the complete matched |
|
2542 | matchers and values are ``MatcherResult``s. | |
2086 | matches: list of completions ? |
|
|||
2087 | matches_origin: ? list same length as matches, and where each completion came from |
|
|||
2088 | jedi_matches: list of Jedi matches, have it's own structure. |
|
|||
2089 | """ |
|
2543 | """ | |
2090 |
|
2544 | |||
2091 |
|
||||
2092 | # if the cursor position isn't given, the only sane assumption we can |
|
2545 | # if the cursor position isn't given, the only sane assumption we can | |
2093 | # make is that it's at the end of the line (the common case) |
|
2546 | # make is that it's at the end of the line (the common case) | |
2094 | if cursor_pos is None: |
|
2547 | if cursor_pos is None: | |
@@ -2100,93 +2553,131 class IPCompleter(Completer): | |||||
2100 | # if text is either None or an empty string, rely on the line buffer |
|
2553 | # if text is either None or an empty string, rely on the line buffer | |
2101 | if (not line_buffer) and full_text: |
|
2554 | if (not line_buffer) and full_text: | |
2102 | line_buffer = full_text.split('\n')[cursor_line] |
|
2555 | line_buffer = full_text.split('\n')[cursor_line] | |
2103 | if not text: # issue #11508: check line_buffer before calling split_line |
|
2556 | if not text: # issue #11508: check line_buffer before calling split_line | |
2104 | text = self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else '' |
|
2557 | text = ( | |
2105 |
|
2558 | self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else "" | ||
2106 | if self.backslash_combining_completions: |
|
2559 | ) | |
2107 | # allow deactivation of these on windows. |
|
|||
2108 | base_text = text if not line_buffer else line_buffer[:cursor_pos] |
|
|||
2109 |
|
||||
2110 | for meth in (self.latex_matches, |
|
|||
2111 | self.unicode_name_matches, |
|
|||
2112 | back_latex_name_matches, |
|
|||
2113 | back_unicode_name_matches, |
|
|||
2114 | self.fwd_unicode_match): |
|
|||
2115 | name_text, name_matches = meth(base_text) |
|
|||
2116 | if name_text: |
|
|||
2117 | return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \ |
|
|||
2118 | [meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()) |
|
|||
2119 |
|
||||
2120 |
|
2560 | |||
2121 | # If no line buffer is given, assume the input text is all there was |
|
2561 | # If no line buffer is given, assume the input text is all there was | |
2122 | if line_buffer is None: |
|
2562 | if line_buffer is None: | |
2123 | line_buffer = text |
|
2563 | line_buffer = text | |
2124 |
|
2564 | |||
|
2565 | # deprecated - do not use `line_buffer` in new code. | |||
2125 | self.line_buffer = line_buffer |
|
2566 | self.line_buffer = line_buffer | |
2126 | self.text_until_cursor = self.line_buffer[:cursor_pos] |
|
2567 | self.text_until_cursor = self.line_buffer[:cursor_pos] | |
2127 |
|
2568 | |||
2128 | # Do magic arg matches |
|
2569 | if not full_text: | |
2129 | for matcher in self.magic_arg_matchers: |
|
2570 | full_text = line_buffer | |
2130 | matches = list(matcher(line_buffer))[:MATCHES_LIMIT] |
|
2571 | ||
2131 | if matches: |
|
2572 | context = CompletionContext( | |
2132 | origins = [matcher.__qualname__] * len(matches) |
|
2573 | full_text=full_text, | |
2133 | return _CompleteResult(text, matches, origins, ()) |
|
2574 | cursor_position=cursor_pos, | |
|
2575 | cursor_line=cursor_line, | |||
|
2576 | token=text, | |||
|
2577 | ) | |||
2134 |
|
2578 | |||
2135 | # Start with a clean slate of completions |
|
2579 | # Start with a clean slate of completions | |
2136 |
|
|
2580 | results = {} | |
2137 |
|
2581 | |||
2138 | # FIXME: we should extend our api to return a dict with completions for |
|
2582 | custom_completer_matcher_id = _get_matcher_id(self.custom_completer_matcher) | |
2139 | # different types of objects. The rlcomplete() method could then |
|
2583 | jedi_matcher_id = _get_matcher_id(self._jedi_matcher) | |
2140 | # simply collapse the dict into a list for readline, but we'd have |
|
|||
2141 | # richer completion semantics in other environments. |
|
|||
2142 | is_magic_prefix = len(text) > 0 and text[0] == "%" |
|
|||
2143 | completions: Iterable[Any] = [] |
|
|||
2144 | if self.use_jedi and not is_magic_prefix: |
|
|||
2145 | if not full_text: |
|
|||
2146 | full_text = line_buffer |
|
|||
2147 | completions = self._jedi_matches( |
|
|||
2148 | cursor_pos, cursor_line, full_text) |
|
|||
2149 |
|
||||
2150 | if self.merge_completions: |
|
|||
2151 | matches = [] |
|
|||
2152 | for matcher in self.matchers: |
|
|||
2153 | try: |
|
|||
2154 | matches.extend([(m, matcher.__qualname__) |
|
|||
2155 | for m in matcher(text)]) |
|
|||
2156 | except: |
|
|||
2157 | # Show the ugly traceback if the matcher causes an |
|
|||
2158 | # exception, but do NOT crash the kernel! |
|
|||
2159 | sys.excepthook(*sys.exc_info()) |
|
|||
2160 | else: |
|
|||
2161 | for matcher in self.matchers: |
|
|||
2162 | matches = [(m, matcher.__qualname__) |
|
|||
2163 | for m in matcher(text)] |
|
|||
2164 | if matches: |
|
|||
2165 | break |
|
|||
2166 |
|
||||
2167 | seen = set() |
|
|||
2168 | filtered_matches = set() |
|
|||
2169 | for m in matches: |
|
|||
2170 | t, c = m |
|
|||
2171 | if t not in seen: |
|
|||
2172 | filtered_matches.add(m) |
|
|||
2173 | seen.add(t) |
|
|||
2174 |
|
2584 | |||
2175 | _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) |
|
2585 | for matcher in self.matchers: | |
|
2586 | api_version = _get_matcher_api_version(matcher) | |||
|
2587 | matcher_id = _get_matcher_id(matcher) | |||
2176 |
|
2588 | |||
2177 | custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] |
|
2589 | if matcher_id in results: | |
2178 |
|
2590 | warnings.warn(f"Duplicate matcher ID: {matcher_id}.") | ||
2179 | _filtered_matches = custom_res or _filtered_matches |
|
|||
2180 |
|
||||
2181 | _filtered_matches = _filtered_matches[:MATCHES_LIMIT] |
|
|||
2182 | _matches = [m[0] for m in _filtered_matches] |
|
|||
2183 | origins = [m[1] for m in _filtered_matches] |
|
|||
2184 |
|
2591 | |||
2185 | self.matches = _matches |
|
2592 | try: | |
|
2593 | if api_version == 1: | |||
|
2594 | result = _convert_matcher_v1_result_to_v2( | |||
|
2595 | matcher(text), type=_UNKNOWN_TYPE | |||
|
2596 | ) | |||
|
2597 | elif api_version == 2: | |||
|
2598 | # TODO: MATCHES_LIMIT was used inconsistently in previous version | |||
|
2599 | # (applied individually to latex/unicode and magic arguments matcher, | |||
|
2600 | # but not Jedi, paths, magics, etc). Jedi did not have a limit here at | |||
|
2601 | # all, but others had a total limit (retained in `_deduplicate_and_sort`). | |||
|
2602 | # 1) Was that deliberate or an omission? | |||
|
2603 | # 2) Should we include the limit in the API v2 signature to allow | |||
|
2604 | # more expensive matchers to return early? | |||
|
2605 | result = cast(matcher, MatcherAPIv2)(context) | |||
|
2606 | else: | |||
|
2607 | raise ValueError(f"Unsupported API version {api_version}") | |||
|
2608 | except: | |||
|
2609 | # Show the ugly traceback if the matcher causes an | |||
|
2610 | # exception, but do NOT crash the kernel! | |||
|
2611 | sys.excepthook(*sys.exc_info()) | |||
|
2612 | continue | |||
2186 |
|
2613 | |||
2187 | return _CompleteResult(text, _matches, origins, completions) |
|
2614 | # set default value for matched fragment if suffix was not selected. | |
2188 |
|
2615 | result["matched_fragment"] = result.get("matched_fragment", context.token) | ||
2189 | def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: |
|
2616 | ||
|
2617 | suppression_recommended = result.get("suppress_others", False) | |||
|
2618 | ||||
|
2619 | should_suppress = ( | |||
|
2620 | self.suppress_competing_matchers is True | |||
|
2621 | or suppression_recommended | |||
|
2622 | or ( | |||
|
2623 | isinstance(self.suppress_competing_matchers, dict) | |||
|
2624 | and self.suppress_competing_matchers[matcher_id] | |||
|
2625 | ) | |||
|
2626 | ) and len(result["completions"]) | |||
|
2627 | ||||
|
2628 | if should_suppress: | |||
|
2629 | new_results = {matcher_id: result} | |||
|
2630 | if ( | |||
|
2631 | matcher_id == custom_completer_matcher_id | |||
|
2632 | and jedi_matcher_id in results | |||
|
2633 | ): | |||
|
2634 | # custom completer does not suppress Jedi (this may change in future versions). | |||
|
2635 | new_results[jedi_matcher_id] = results[jedi_matcher_id] | |||
|
2636 | results = new_results | |||
|
2637 | break | |||
|
2638 | ||||
|
2639 | results[matcher_id] = result | |||
|
2640 | ||||
|
2641 | _, matches = self._arrange_and_extract( | |||
|
2642 | results, | |||
|
2643 | # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission? | |||
|
2644 | # if it was omission, we can remove the filtering step, otherwise remove this comment. | |||
|
2645 | skip_matchers={jedi_matcher_id}, | |||
|
2646 | abort_if_offset_changes=False, | |||
|
2647 | ) | |||
|
2648 | ||||
|
2649 | # populate legacy stateful API | |||
|
2650 | self.matches = matches | |||
|
2651 | ||||
|
2652 | return results | |||
|
2653 | ||||
|
2654 | @staticmethod | |||
|
2655 | def _deduplicate( | |||
|
2656 | matches: Sequence[SimpleCompletion], | |||
|
2657 | ) -> Iterable[SimpleCompletion]: | |||
|
2658 | filtered_matches = {} | |||
|
2659 | for match in matches: | |||
|
2660 | text = match.text | |||
|
2661 | if ( | |||
|
2662 | text not in filtered_matches | |||
|
2663 | or filtered_matches[text].type == _UNKNOWN_TYPE | |||
|
2664 | ): | |||
|
2665 | filtered_matches[text] = match | |||
|
2666 | ||||
|
2667 | return filtered_matches.values() | |||
|
2668 | ||||
|
2669 | @staticmethod | |||
|
2670 | def _sort(matches: Sequence[SimpleCompletion]): | |||
|
2671 | return sorted(matches, key=lambda x: completions_sorting_key(x.text)) | |||
|
2672 | ||||
|
2673 | @context_matcher() | |||
|
2674 | def fwd_unicode_matcher(self, context): | |||
|
2675 | fragment, matches = self.latex_matches(context.token) | |||
|
2676 | return _convert_matcher_v1_result_to_v2( | |||
|
2677 | matches, type="unicode", fragment=fragment, suppress_if_matches=True | |||
|
2678 | ) | |||
|
2679 | ||||
|
2680 | def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]: | |||
2190 | """ |
|
2681 | """ | |
2191 | Forward match a string starting with a backslash with a list of |
|
2682 | Forward match a string starting with a backslash with a list of | |
2192 | potential Unicode completions. |
|
2683 | potential Unicode completions. |
@@ -298,7 +298,7 class TestCompleter(unittest.TestCase): | |||||
298 | ip = get_ipython() |
|
298 | ip = get_ipython() | |
299 |
|
299 | |||
300 | name, matches = ip.complete("\\Ⅴ") |
|
300 | name, matches = ip.complete("\\Ⅴ") | |
301 |
self.assertEqual(matches, |
|
301 | self.assertEqual(matches, ["\\ROMAN NUMERAL FIVE"]) | |
302 |
|
302 | |||
303 | def test_forward_unicode_completion(self): |
|
303 | def test_forward_unicode_completion(self): | |
304 | ip = get_ipython() |
|
304 | ip = get_ipython() | |
@@ -379,6 +379,12 class TestCompleter(unittest.TestCase): | |||||
379 |
|
379 | |||
380 | def test_quoted_file_completions(self): |
|
380 | def test_quoted_file_completions(self): | |
381 | ip = get_ipython() |
|
381 | ip = get_ipython() | |
|
382 | ||||
|
383 | def _(text): | |||
|
384 | return ip.Completer._complete( | |||
|
385 | cursor_line=0, cursor_pos=len(text), full_text=text | |||
|
386 | )["IPCompleter.file_matcher"]["completions"] | |||
|
387 | ||||
382 | with TemporaryWorkingDirectory(): |
|
388 | with TemporaryWorkingDirectory(): | |
383 | name = "foo'bar" |
|
389 | name = "foo'bar" | |
384 | open(name, "w", encoding="utf-8").close() |
|
390 | open(name, "w", encoding="utf-8").close() | |
@@ -387,25 +393,16 class TestCompleter(unittest.TestCase): | |||||
387 | escaped = name if sys.platform == "win32" else "foo\\'bar" |
|
393 | escaped = name if sys.platform == "win32" else "foo\\'bar" | |
388 |
|
394 | |||
389 | # Single quote matches embedded single quote |
|
395 | # Single quote matches embedded single quote | |
390 |
|
|
396 | c = _("open('foo")[0] | |
391 | c = ip.Completer._complete( |
|
397 | self.assertEqual(c.text, escaped) | |
392 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
|||
393 | )[1] |
|
|||
394 | self.assertEqual(c, [escaped]) |
|
|||
395 |
|
398 | |||
396 | # Double quote requires no escape |
|
399 | # Double quote requires no escape | |
397 |
|
|
400 | c = _('open("foo')[0] | |
398 | c = ip.Completer._complete( |
|
401 | self.assertEqual(c.text, name) | |
399 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
|||
400 | )[1] |
|
|||
401 | self.assertEqual(c, [name]) |
|
|||
402 |
|
402 | |||
403 | # No quote requires an escape |
|
403 | # No quote requires an escape | |
404 |
|
|
404 | c = _("%ls foo")[0] | |
405 | c = ip.Completer._complete( |
|
405 | self.assertEqual(c.text, escaped) | |
406 | cursor_line=0, cursor_pos=len(text), full_text=text |
|
|||
407 | )[1] |
|
|||
408 | self.assertEqual(c, [escaped]) |
|
|||
409 |
|
406 | |||
410 | def test_all_completions_dups(self): |
|
407 | def test_all_completions_dups(self): | |
411 | """ |
|
408 | """ | |
@@ -475,6 +472,17 class TestCompleter(unittest.TestCase): | |||||
475 | "encoding" in c.signature |
|
472 | "encoding" in c.signature | |
476 | ), "Signature of function was not found by completer" |
|
473 | ), "Signature of function was not found by completer" | |
477 |
|
474 | |||
|
475 | def test_completions_have_type(self): | |||
|
476 | """ | |||
|
477 | Lets make sure matchers provide completion type. | |||
|
478 | """ | |||
|
479 | ip = get_ipython() | |||
|
480 | with provisionalcompleter(): | |||
|
481 | ip.Completer.use_jedi = False | |||
|
482 | completions = ip.Completer.completions("%tim", 3) | |||
|
483 | c = next(completions) # should be `%time` or similar | |||
|
484 | assert c.type == "magic", "Type of magic was not assigned by completer" | |||
|
485 | ||||
478 | @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0") |
|
486 | @pytest.mark.xfail(reason="Known failure on jedi<=0.18.0") | |
479 | def test_deduplicate_completions(self): |
|
487 | def test_deduplicate_completions(self): | |
480 | """ |
|
488 | """ |
General Comments 0
You need to be logged in to leave comments.
Login now