##// END OF EJS Templates
Refactor `IPCompleter` Matcher API
krassowski -
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(False,
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(True,
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_matches,
1440 self.magic_config_matcher,
1177 self.magic_color_matches,
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[Any]:
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_matches]
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.dict_key_matches,
1480 *self._backslash_combining_matchers,
1199 self.file_matches,
1481 *self.magic_arg_matchers,
1200 self.magic_matches,
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.dict_key_matches,
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_matches,
1498 self.file_matcher,
1208 self.magic_matches,
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.Completions` object from a ``text`` and
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(start=offset - delta,
2369 yield Completion(
1988 end=offset,
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 matches and self.debug:
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 matches = []
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, ("\\ROMAN NUMERAL FIVE",))
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 text = "open('foo"
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 text = 'open("foo'
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 text = "%ls foo"
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