##// END OF EJS Templates
Implement `priority`, `do_not_suppress`, add tests and docs.
krassowski -
Show More
@@ -101,12 +101,10 b' option.'
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
103
104 .. _Matchers:
105
106 Matchers
104 Matchers
107 ========
105 ========
108
106
109 All completions routines are implemented using unified ``matchers`` API.
107 All completions routines are implemented using unified *Matchers* API.
110 The matchers API is provisional and subject to change without notice.
108 The matchers API is provisional and subject to change without notice.
111
109
112 The built-in matchers include:
110 The built-in matchers include:
@@ -119,13 +117,26 b' The built-in matchers include:'
119 - ``IPCompleter.python_func_kw_matcher`` - function keywords,
117 - ``IPCompleter.python_func_kw_matcher`` - function keywords,
120 - ``IPCompleter.python_matches`` - globals and attributes (v1 API),
118 - ``IPCompleter.python_matches`` - globals and attributes (v1 API),
121 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
119 - ``IPCompleter.jedi_matcher`` - static analysis with Jedi,
122 - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in ``core.InteractiveShell``
120 - ``IPCompleter.custom_completer_matcher`` - pluggable completer with a default implementation in any:`core.InteractiveShell`
123 which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions).
121 which uses uses IPython hooks system (`complete_command`) with string dispatch (including regular expressions).
124 Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match
122 Differently to other matchers, ``custom_completer_matcher`` will not suppress Jedi results to match
125 behaviour in earlier IPython versions.
123 behaviour in earlier IPython versions.
124
125 Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list.
126
127 Suppression of competing matchers
128 ---------------------------------
129
130 By default results from all matchers are combined, in the order determined by
131 their priority. Matchers can request to suppress results from subsequent
132 matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``.
126
133
127 Adding custom matchers is possible by appending to `IPCompleter.custom_matchers` list,
134 When multiple matchers simultaneously request surpression, the results from of
128 but please be aware that this API is subject to change.
135 the matcher with higher priority will be returned.
136
137 Sometimes it is desirable to suppress most but not all other matchers;
138 this can be achieved by adding a list of identifiers of matchers which
139 should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.
129 """
140 """
130
141
131
142
@@ -231,7 +242,7 b' else:'
231 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
242 _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)]
232
243
233 # Public API
244 # Public API
234 __all__ = ['Completer','IPCompleter']
245 __all__ = ["Completer", "IPCompleter"]
235
246
236 if sys.platform == 'win32':
247 if sys.platform == 'win32':
237 PROTECTABLES = ' '
248 PROTECTABLES = ' '
@@ -427,7 +438,7 b' _JediCompletionLike = Union[jedi.api.Completion, _FakeJediCompletion]'
427
438
428 class Completion:
439 class Completion:
429 """
440 """
430 Completion object used and return by IPython completers.
441 Completion object used and returned by IPython completers.
431
442
432 .. warning::
443 .. warning::
433
444
@@ -488,12 +499,19 b' class Completion:'
488
499
489
500
490 class SimpleCompletion:
501 class SimpleCompletion:
491 # TODO: decide whether we should keep the ``SimpleCompletion`` separate from ``Completion``
502 """Completion item to be included in the dictionary returned by new-style Matcher (API v2).
492 # there are two advantages of keeping them separate:
503
493 # - compatibility with old readline `Completer.complete` interface (less important)
504 .. warning::
494 # - ease of use for third parties (just return matched text and don't worry about coordinates)
505
495 # the disadvantage is that we need to loop over the completions again to transform them into
506 Provisional
496 # `Completion` objects (but it was done like that before the refactor into `SimpleCompletion` too).
507
508 This class is used to describe the currently supported attributes of
509 simple completion items, and any additional implementation details
510 should not be relied on. Additional attributes may be included in
511 future versions, and meaning of text disambiguated from the current
512 dual meaning of "text to insert" and "text to used as a label".
513 """
514
497 __slots__ = ["text", "type"]
515 __slots__ = ["text", "type"]
498
516
499 def __init__(self, text: str, *, type: str = None):
517 def __init__(self, text: str, *, type: str = None):
@@ -504,30 +522,31 b' class SimpleCompletion:'
504 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
522 return f"<SimpleCompletion text={self.text!r} type={self.type!r}>"
505
523
506
524
507 class _MatcherResultBase(TypedDict):
525 class MatcherResultBase(TypedDict):
526 """Definition of dictionary to be returned by new-style Matcher (API v2)."""
508
527
509 #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
528 #: suffix of the provided ``CompletionContext.token``, if not given defaults to full token.
510 matched_fragment: NotRequired[str]
529 matched_fragment: NotRequired[str]
511
530
512 #: whether to suppress results from other matchers; default is False.
531 #: whether to suppress results from all other matchers (True), some
513 suppress_others: NotRequired[bool]
532 #: matchers (set of identifiers) or none (False); default is False.
533 suppress: NotRequired[Union[bool, Set[str]]]
534
535 #: identifiers of matchers which should NOT be suppressed
536 do_not_suppress: NotRequired[Set[str]]
514
537
515 #: are completions already ordered and should be left as-is? default is False.
538 #: are completions already ordered and should be left as-is? default is False.
516 ordered: NotRequired[bool]
539 ordered: NotRequired[bool]
517
540
518 # TODO: should we used a relevance score for ordering?
519 #: value between 0 (likely not relevant) and 100 (likely relevant); default is 50.
520 # relevance: NotRequired[float]
521
541
522
542 class SimpleMatcherResult(MatcherResultBase):
523 class SimpleMatcherResult(_MatcherResultBase):
524 """Result of new-style completion matcher."""
543 """Result of new-style completion matcher."""
525
544
526 #: list of candidate completions
545 #: list of candidate completions
527 completions: Sequence[SimpleCompletion]
546 completions: Sequence[SimpleCompletion]
528
547
529
548
530 class _JediMatcherResult(_MatcherResultBase):
549 class _JediMatcherResult(MatcherResultBase):
531 """Matching result returned by Jedi (will be processed differently)"""
550 """Matching result returned by Jedi (will be processed differently)"""
532
551
533 #: list of candidate completions
552 #: list of candidate completions
@@ -535,6 +554,8 b' class _JediMatcherResult(_MatcherResultBase):'
535
554
536
555
537 class CompletionContext(NamedTuple):
556 class CompletionContext(NamedTuple):
557 """Completion context provided as an argument to matchers in the Matcher API v2."""
558
538 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
559 # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`)
539 # which was not explicitly visible as an argument of the matcher, making any refactor
560 # which was not explicitly visible as an argument of the matcher, making any refactor
540 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
561 # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers
@@ -546,14 +567,20 b' class CompletionContext(NamedTuple):'
546 #: (by switching the greedy mode).
567 #: (by switching the greedy mode).
547 token: str
568 token: str
548
569
570 #: The full available content of the editor or buffer
549 full_text: str
571 full_text: str
550
572
551 #: Cursor position in the line (the same for ``full_text`` and `text``).
573 #: Cursor position in the line (the same for ``full_text`` and ``text``).
552 cursor_position: int
574 cursor_position: int
553
575
554 #: Cursor line in ``full_text``.
576 #: Cursor line in ``full_text``.
555 cursor_line: int
577 cursor_line: int
556
578
579 #: The maximum number of completions that will be used downstream.
580 #: Matchers can use this information to abort early.
581 #: The built-in Jedi matcher is currently excepted from this limit.
582 limit: int
583
557 @property
584 @property
558 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
585 @lru_cache(maxsize=None) # TODO change to @cache after dropping Python 3.7
559 def text_until_cursor(self) -> str:
586 def text_until_cursor(self) -> str:
@@ -573,7 +600,7 b' Matcher = Union[MatcherAPIv1, MatcherAPIv2]'
573
600
574
601
575 def completion_matcher(
602 def completion_matcher(
576 *, priority: float = None, identifier: str = None, api_version=1
603 *, priority: float = None, identifier: str = None, api_version: int = 1
577 ):
604 ):
578 """Adds attributes describing the matcher.
605 """Adds attributes describing the matcher.
579
606
@@ -581,7 +608,7 b' def completion_matcher('
581 ----------
608 ----------
582 priority : Optional[float]
609 priority : Optional[float]
583 The priority of the matcher, determines the order of execution of matchers.
610 The priority of the matcher, determines the order of execution of matchers.
584 Higher priority means that the matcher will be executed first. Defaults to 50.
611 Higher priority means that the matcher will be executed first. Defaults to 0.
585 identifier : Optional[str]
612 identifier : Optional[str]
586 identifier of the matcher allowing users to modify the behaviour via traitlets,
613 identifier of the matcher allowing users to modify the behaviour via traitlets,
587 and also used to for debugging (will be passed as ``origin`` with the completions).
614 and also used to for debugging (will be passed as ``origin`` with the completions).
@@ -593,14 +620,23 b' def completion_matcher('
593 """
620 """
594
621
595 def wrapper(func: Matcher):
622 def wrapper(func: Matcher):
596 func.matcher_priority = priority
623 func.matcher_priority = priority or 0
597 func.matcher_identifier = identifier or func.__qualname__
624 func.matcher_identifier = identifier or func.__qualname__
598 func.matcher_api_version = api_version
625 func.matcher_api_version = api_version
626 if TYPE_CHECKING:
627 if api_version == 1:
628 func = cast(func, MatcherAPIv1)
629 elif api_version == 2:
630 func = cast(func, MatcherAPIv2)
599 return func
631 return func
600
632
601 return wrapper
633 return wrapper
602
634
603
635
636 def _get_matcher_priority(matcher: Matcher):
637 return getattr(matcher, "matcher_priority", 0)
638
639
604 def _get_matcher_id(matcher: Matcher):
640 def _get_matcher_id(matcher: Matcher):
605 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
641 return getattr(matcher, "matcher_identifier", matcher.__qualname__)
606
642
@@ -1118,6 +1154,10 b' def _safe_isinstance(obj, module, class_name):'
1118
1154
1119 @context_matcher()
1155 @context_matcher()
1120 def back_unicode_name_matcher(context):
1156 def back_unicode_name_matcher(context):
1157 """Match Unicode characters back to Unicode name
1158
1159 Same as ``back_unicode_name_matches``, but adopted to new Matcher API.
1160 """
1121 fragment, matches = back_unicode_name_matches(context.token)
1161 fragment, matches = back_unicode_name_matches(context.token)
1122 return _convert_matcher_v1_result_to_v2(
1162 return _convert_matcher_v1_result_to_v2(
1123 matches, type="unicode", fragment=fragment, suppress_if_matches=True
1163 matches, type="unicode", fragment=fragment, suppress_if_matches=True
@@ -1166,6 +1206,10 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:'
1166
1206
1167 @context_matcher()
1207 @context_matcher()
1168 def back_latex_name_matcher(context):
1208 def back_latex_name_matcher(context):
1209 """Match latex characters back to unicode name
1210
1211 Same as ``back_latex_name_matches``, but adopted to new Matcher API.
1212 """
1169 fragment, matches = back_latex_name_matches(context.token)
1213 fragment, matches = back_latex_name_matches(context.token)
1170 return _convert_matcher_v1_result_to_v2(
1214 return _convert_matcher_v1_result_to_v2(
1171 matches, type="latex", fragment=fragment, suppress_if_matches=True
1215 matches, type="latex", fragment=fragment, suppress_if_matches=True
@@ -1263,9 +1307,7 b' def _convert_matcher_v1_result_to_v2('
1263 """Utility to help with transition"""
1307 """Utility to help with transition"""
1264 result = {
1308 result = {
1265 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1309 "completions": [SimpleCompletion(text=match, type=type) for match in matches],
1266 "suppress_others": (True if matches else False)
1310 "suppress": (True if matches else False) if suppress_if_matches else False,
1267 if suppress_if_matches
1268 else False,
1269 }
1311 }
1270 if fragment is not None:
1312 if fragment is not None:
1271 result["matched_fragment"] = fragment
1313 result["matched_fragment"] = fragment
@@ -1297,7 +1339,7 b' class IPCompleter(Completer):'
1297 suppress_competing_matchers = UnionTrait(
1339 suppress_competing_matchers = UnionTrait(
1298 [Bool(), DictTrait(Bool(None, allow_none=True))],
1340 [Bool(), DictTrait(Bool(None, allow_none=True))],
1299 help="""
1341 help="""
1300 Whether to suppress completions from other `Matchers`_.
1342 Whether to suppress completions from other *Matchers*.
1301
1343
1302 When set to ``None`` (default) the matchers will attempt to auto-detect
1344 When set to ``None`` (default) the matchers will attempt to auto-detect
1303 whether suppression of other matchers is desirable. For example, at
1345 whether suppression of other matchers is desirable. For example, at
@@ -1312,7 +1354,7 b' class IPCompleter(Completer):'
1312
1354
1313 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1355 Set ``IPCompleter.suppress_competing_matchers = True`` to limit
1314 completions to the set of matchers with the highest priority;
1356 completions to the set of matchers with the highest priority;
1315 this is equivalent to ``IPCompleter.merge_completions`` and
1357 this is equivalent to ``IPCompleter.merge_completions`` and
1316 can be beneficial for performance, but will sometimes omit relevant
1358 can be beneficial for performance, but will sometimes omit relevant
1317 candidates from matchers further down the priority list.
1359 candidates from matchers further down the priority list.
1318 """,
1360 """,
@@ -1325,7 +1367,7 b' class IPCompleter(Completer):'
1325 If False, only the completion results from the first non-empty
1367 If False, only the completion results from the first non-empty
1326 completer will be returned.
1368 completer will be returned.
1327
1369
1328 As of version 8.5.0, setting the value to ``False`` is an alias for:
1370 As of version 8.6.0, setting the value to ``False`` is an alias for:
1329 ``IPCompleter.suppress_competing_matchers = True.``.
1371 ``IPCompleter.suppress_competing_matchers = True.``.
1330 """,
1372 """,
1331 ).tag(config=True)
1373 ).tag(config=True)
@@ -1469,9 +1511,6 b' class IPCompleter(Completer):'
1469 if not self.merge_completions:
1511 if not self.merge_completions:
1470 self.suppress_competing_matchers = True
1512 self.suppress_competing_matchers = True
1471
1513
1472 if self.dict_keys_only:
1473 self.disable_matchers.append(self.dict_key_matcher.matcher_identifier)
1474
1475 @property
1514 @property
1476 def matchers(self) -> List[Matcher]:
1515 def matchers(self) -> List[Matcher]:
1477 """All active matcher routines for completion"""
1516 """All active matcher routines for completion"""
@@ -1523,6 +1562,7 b' class IPCompleter(Completer):'
1523
1562
1524 @context_matcher()
1563 @context_matcher()
1525 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1564 def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1565 """Same as ``file_matches``, but adopted to new Matcher API."""
1526 matches = self.file_matches(context.token)
1566 matches = self.file_matches(context.token)
1527 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1567 # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter,
1528 # starts with `/home/`, `C:\`, etc)
1568 # starts with `/home/`, `C:\`, etc)
@@ -1540,7 +1580,10 b' class IPCompleter(Completer):'
1540 only the parts after what's already been typed (instead of the
1580 only the parts after what's already been typed (instead of the
1541 full completions, as is normally done). I don't think with the
1581 full completions, as is normally done). I don't think with the
1542 current (as of Python 2.3) Python readline it's possible to do
1582 current (as of Python 2.3) Python readline it's possible to do
1543 better."""
1583 better.
1584
1585 DEPRECATED: Deprecated since 8.6. Use ``file_matcher`` instead.
1586 """
1544
1587
1545 # chars that require escaping with backslash - i.e. chars
1588 # chars that require escaping with backslash - i.e. chars
1546 # that readline treats incorrectly as delimiters, but we
1589 # that readline treats incorrectly as delimiters, but we
@@ -1616,11 +1659,14 b' class IPCompleter(Completer):'
1616 matches = self.magic_matches(text)
1659 matches = self.magic_matches(text)
1617 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1660 result = _convert_matcher_v1_result_to_v2(matches, type="magic")
1618 is_magic_prefix = len(text) > 0 and text[0] == "%"
1661 is_magic_prefix = len(text) > 0 and text[0] == "%"
1619 result["suppress_others"] = is_magic_prefix and bool(result["completions"])
1662 result["suppress"] = is_magic_prefix and bool(result["completions"])
1620 return result
1663 return result
1621
1664
1622 def magic_matches(self, text: str):
1665 def magic_matches(self, text: str):
1623 """Match magics"""
1666 """Match magics.
1667
1668 DEPRECATED: Deprecated since 8.6. Use ``magic_matcher`` instead.
1669 """
1624 # Get all shell magics now rather than statically, so magics loaded at
1670 # Get all shell magics now rather than statically, so magics loaded at
1625 # runtime show up too.
1671 # runtime show up too.
1626 lsm = self.shell.magics_manager.lsmagic()
1672 lsm = self.shell.magics_manager.lsmagic()
@@ -1663,12 +1709,16 b' class IPCompleter(Completer):'
1663
1709
1664 @context_matcher()
1710 @context_matcher()
1665 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1711 def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1712 """Match class names and attributes for %config magic."""
1666 # NOTE: uses `line_buffer` equivalent for compatibility
1713 # NOTE: uses `line_buffer` equivalent for compatibility
1667 matches = self.magic_config_matches(context.line_with_cursor)
1714 matches = self.magic_config_matches(context.line_with_cursor)
1668 return _convert_matcher_v1_result_to_v2(matches, type="param")
1715 return _convert_matcher_v1_result_to_v2(matches, type="param")
1669
1716
1670 def magic_config_matches(self, text: str) -> List[str]:
1717 def magic_config_matches(self, text: str) -> List[str]:
1671 """Match class names and attributes for %config magic"""
1718 """Match class names and attributes for %config magic.
1719
1720 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1721 """
1672 texts = text.strip().split()
1722 texts = text.strip().split()
1673
1723
1674 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
1724 if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'):
@@ -1704,12 +1754,16 b' class IPCompleter(Completer):'
1704
1754
1705 @context_matcher()
1755 @context_matcher()
1706 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1756 def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1757 """Match color schemes for %colors magic."""
1707 # NOTE: uses `line_buffer` equivalent for compatibility
1758 # NOTE: uses `line_buffer` equivalent for compatibility
1708 matches = self.magic_color_matches(context.line_with_cursor)
1759 matches = self.magic_color_matches(context.line_with_cursor)
1709 return _convert_matcher_v1_result_to_v2(matches, type="param")
1760 return _convert_matcher_v1_result_to_v2(matches, type="param")
1710
1761
1711 def magic_color_matches(self, text: str) -> List[str]:
1762 def magic_color_matches(self, text: str) -> List[str]:
1712 """Match color schemes for %colors magic"""
1763 """Match color schemes for %colors magic.
1764
1765 DEPRECATED: Deprecated since 8.6. Use ``magic_color_matcher`` instead.
1766 """
1713 texts = text.split()
1767 texts = text.split()
1714 if text.endswith(' '):
1768 if text.endswith(' '):
1715 # .split() strips off the trailing whitespace. Add '' back
1769 # .split() strips off the trailing whitespace. Add '' back
@@ -1731,8 +1785,8 b' class IPCompleter(Completer):'
1731 )
1785 )
1732 return {
1786 return {
1733 "completions": matches,
1787 "completions": matches,
1734 # statis analysis should not suppress other matchers
1788 # static analysis should not suppress other matchers
1735 "suppress_others": False,
1789 "suppress": False,
1736 }
1790 }
1737
1791
1738 def _jedi_matches(
1792 def _jedi_matches(
@@ -1755,6 +1809,8 b' class IPCompleter(Completer):'
1755 -----
1809 -----
1756 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1810 If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion`
1757 object containing a string with the Jedi debug information attached.
1811 object containing a string with the Jedi debug information attached.
1812
1813 DEPRECATED: Deprecated since 8.6. Use ``_jedi_matcher`` instead.
1758 """
1814 """
1759 namespaces = [self.namespace]
1815 namespaces = [self.namespace]
1760 if self.global_namespace is not None:
1816 if self.global_namespace is not None:
@@ -1893,11 +1949,15 b' class IPCompleter(Completer):'
1893
1949
1894 @context_matcher()
1950 @context_matcher()
1895 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1951 def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
1952 """Match named parameters (kwargs) of the last open function."""
1896 matches = self.python_func_kw_matches(context.token)
1953 matches = self.python_func_kw_matches(context.token)
1897 return _convert_matcher_v1_result_to_v2(matches, type="param")
1954 return _convert_matcher_v1_result_to_v2(matches, type="param")
1898
1955
1899 def python_func_kw_matches(self, text):
1956 def python_func_kw_matches(self, text):
1900 """Match named parameters (kwargs) of the last open function"""
1957 """Match named parameters (kwargs) of the last open function.
1958
1959 DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead.
1960 """
1901
1961
1902 if "." in text: # a parameter cannot be dotted
1962 if "." in text: # a parameter cannot be dotted
1903 return []
1963 return []
@@ -1994,6 +2054,7 b' class IPCompleter(Completer):'
1994
2054
1995 @context_matcher()
2055 @context_matcher()
1996 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2056 def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult:
2057 """Match string keys in a dictionary, after e.g. ``foo[``."""
1997 matches = self.dict_key_matches(context.token)
2058 matches = self.dict_key_matches(context.token)
1998 return _convert_matcher_v1_result_to_v2(
2059 return _convert_matcher_v1_result_to_v2(
1999 matches, type="dict key", suppress_if_matches=True
2060 matches, type="dict key", suppress_if_matches=True
@@ -2002,7 +2063,7 b' class IPCompleter(Completer):'
2002 def dict_key_matches(self, text: str) -> List[str]:
2063 def dict_key_matches(self, text: str) -> List[str]:
2003 """Match string keys in a dictionary, after e.g. ``foo[``.
2064 """Match string keys in a dictionary, after e.g. ``foo[``.
2004
2065
2005 DEPRECATED: Deprecated since 8.5. Use ``dict_key_matcher`` instead.
2066 DEPRECATED: Deprecated since 8.6. Use `dict_key_matcher` instead.
2006 """
2067 """
2007
2068
2008 if self.__dict_key_regexps is not None:
2069 if self.__dict_key_regexps is not None:
@@ -2136,6 +2197,10 b' class IPCompleter(Completer):'
2136
2197
2137 @context_matcher()
2198 @context_matcher()
2138 def latex_name_matcher(self, context):
2199 def latex_name_matcher(self, context):
2200 """Match Latex syntax for unicode characters.
2201
2202 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2203 """
2139 fragment, matches = self.latex_matches(context.token)
2204 fragment, matches = self.latex_matches(context.token)
2140 return _convert_matcher_v1_result_to_v2(
2205 return _convert_matcher_v1_result_to_v2(
2141 matches, type="latex", fragment=fragment, suppress_if_matches=True
2206 matches, type="latex", fragment=fragment, suppress_if_matches=True
@@ -2145,6 +2210,8 b' class IPCompleter(Completer):'
2145 """Match Latex syntax for unicode characters.
2210 """Match Latex syntax for unicode characters.
2146
2211
2147 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2212 This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±``
2213
2214 DEPRECATED: Deprecated since 8.6. Use `latex_matcher` instead.
2148 """
2215 """
2149 slashpos = text.rfind('\\')
2216 slashpos = text.rfind('\\')
2150 if slashpos > -1:
2217 if slashpos > -1:
@@ -2168,9 +2235,13 b' class IPCompleter(Completer):'
2168 matches, type="<unknown>", suppress_if_matches=True
2235 matches, type="<unknown>", suppress_if_matches=True
2169 )
2236 )
2170 result["ordered"] = True
2237 result["ordered"] = True
2238 result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)}
2171 return result
2239 return result
2172
2240
2173 def dispatch_custom_completer(self, text):
2241 def dispatch_custom_completer(self, text):
2242 """
2243 DEPRECATED: Deprecated since 8.6. Use `custom_completer_matcher` instead.
2244 """
2174 if not self.custom_completers:
2245 if not self.custom_completers:
2175 return
2246 return
2176
2247
@@ -2418,8 +2489,9 b' class IPCompleter(Completer):'
2418 )
2489 )
2419 container.append(completion)
2490 container.append(completion)
2420
2491
2421 yield from self._deduplicate(ordered + self._sort(sortable))
2492 yield from list(self._deduplicate(ordered + self._sort(sortable)))[
2422
2493 :MATCHES_LIMIT
2494 ]
2423
2495
2424 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2496 def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]:
2425 """Find completions for the given text and line context.
2497 """Find completions for the given text and line context.
@@ -2490,6 +2562,8 b' class IPCompleter(Completer):'
2490 for identifier, result in results.items():
2562 for identifier, result in results.items():
2491 if identifier in skip_matchers:
2563 if identifier in skip_matchers:
2492 continue
2564 continue
2565 if not result["completions"]:
2566 continue
2493 if not most_recent_fragment:
2567 if not most_recent_fragment:
2494 most_recent_fragment = result["matched_fragment"]
2568 most_recent_fragment = result["matched_fragment"]
2495 if (
2569 if (
@@ -2578,34 +2652,42 b' class IPCompleter(Completer):'
2578 cursor_position=cursor_pos,
2652 cursor_position=cursor_pos,
2579 cursor_line=cursor_line,
2653 cursor_line=cursor_line,
2580 token=text,
2654 token=text,
2655 limit=MATCHES_LIMIT,
2581 )
2656 )
2582
2657
2583 # Start with a clean slate of completions
2658 # Start with a clean slate of completions
2584 results = {}
2659 results = {}
2585
2660
2586 custom_completer_matcher_id = _get_matcher_id(self.custom_completer_matcher)
2587 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2661 jedi_matcher_id = _get_matcher_id(self._jedi_matcher)
2588
2662
2589 for matcher in self.matchers:
2663 suppressed_matchers = set()
2664
2665 matchers = {
2666 _get_matcher_id(matcher): matcher
2667 for matcher in sorted(
2668 self.matchers, key=_get_matcher_priority, reverse=True
2669 )
2670 }
2671
2672 for matcher_id, matcher in matchers.items():
2590 api_version = _get_matcher_api_version(matcher)
2673 api_version = _get_matcher_api_version(matcher)
2591 matcher_id = _get_matcher_id(matcher)
2674 matcher_id = _get_matcher_id(matcher)
2592
2675
2676 if matcher_id in self.disable_matchers:
2677 continue
2678
2593 if matcher_id in results:
2679 if matcher_id in results:
2594 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
2680 warnings.warn(f"Duplicate matcher ID: {matcher_id}.")
2595
2681
2682 if matcher_id in suppressed_matchers:
2683 continue
2684
2596 try:
2685 try:
2597 if api_version == 1:
2686 if api_version == 1:
2598 result = _convert_matcher_v1_result_to_v2(
2687 result = _convert_matcher_v1_result_to_v2(
2599 matcher(text), type=_UNKNOWN_TYPE
2688 matcher(text), type=_UNKNOWN_TYPE
2600 )
2689 )
2601 elif api_version == 2:
2690 elif api_version == 2:
2602 # TODO: MATCHES_LIMIT was used inconsistently in previous version
2603 # (applied individually to latex/unicode and magic arguments matcher,
2604 # but not Jedi, paths, magics, etc). Jedi did not have a limit here at
2605 # all, but others had a total limit (retained in `_deduplicate_and_sort`).
2606 # 1) Was that deliberate or an omission?
2607 # 2) Should we include the limit in the API v2 signature to allow
2608 # more expensive matchers to return early?
2609 result = cast(matcher, MatcherAPIv2)(context)
2691 result = cast(matcher, MatcherAPIv2)(context)
2610 else:
2692 else:
2611 raise ValueError(f"Unsupported API version {api_version}")
2693 raise ValueError(f"Unsupported API version {api_version}")
@@ -2618,27 +2700,31 b' class IPCompleter(Completer):'
2618 # set default value for matched fragment if suffix was not selected.
2700 # set default value for matched fragment if suffix was not selected.
2619 result["matched_fragment"] = result.get("matched_fragment", context.token)
2701 result["matched_fragment"] = result.get("matched_fragment", context.token)
2620
2702
2621 suppression_recommended = result.get("suppress_others", False)
2703 if not suppressed_matchers:
2704 suppression_recommended = result.get("suppress", False)
2622
2705
2623 should_suppress = (
2706 should_suppress = (
2624 self.suppress_competing_matchers is True
2707 self.suppress_competing_matchers is True
2625 or suppression_recommended
2708 or suppression_recommended
2626 or (
2709 or (
2627 isinstance(self.suppress_competing_matchers, dict)
2710 isinstance(self.suppress_competing_matchers, dict)
2628 and self.suppress_competing_matchers[matcher_id]
2711 and self.suppress_competing_matchers[matcher_id]
2629 )
2712 )
2630 ) and len(result["completions"])
2713 ) and len(result["completions"])
2631
2714
2632 if should_suppress:
2715 if should_suppress:
2633 new_results = {matcher_id: result}
2716 suppression_exceptions = result.get("do_not_suppress", set())
2634 if (
2717 try:
2635 matcher_id == custom_completer_matcher_id
2718 to_suppress = set(suppression_recommended)
2636 and jedi_matcher_id in results
2719 except TypeError:
2637 ):
2720 to_suppress = set(matchers)
2638 # custom completer does not suppress Jedi (this may change in future versions).
2721 suppressed_matchers = to_suppress - suppression_exceptions
2639 new_results[jedi_matcher_id] = results[jedi_matcher_id]
2722
2640 results = new_results
2723 new_results = {}
2641 break
2724 for previous_matcher_id, previous_result in results.items():
2725 if previous_matcher_id not in suppressed_matchers:
2726 new_results[previous_matcher_id] = previous_result
2727 results = new_results
2642
2728
2643 results[matcher_id] = result
2729 results[matcher_id] = result
2644
2730
@@ -2676,6 +2762,7 b' class IPCompleter(Completer):'
2676
2762
2677 @context_matcher()
2763 @context_matcher()
2678 def fwd_unicode_matcher(self, context):
2764 def fwd_unicode_matcher(self, context):
2765 """Same as ``fwd_unicode_match``, but adopted to new Matcher API."""
2679 fragment, matches = self.latex_matches(context.token)
2766 fragment, matches = self.latex_matches(context.token)
2680 return _convert_matcher_v1_result_to_v2(
2767 return _convert_matcher_v1_result_to_v2(
2681 matches, type="unicode", fragment=fragment, suppress_if_matches=True
2768 matches, type="unicode", fragment=fragment, suppress_if_matches=True
@@ -2693,6 +2780,8 b' class IPCompleter(Completer):'
2693 At tuple with:
2780 At tuple with:
2694 - matched text (empty if no matches)
2781 - matched text (empty if no matches)
2695 - list of potential completions, empty tuple otherwise)
2782 - list of potential completions, empty tuple otherwise)
2783
2784 DEPRECATED: Deprecated since 8.6. Use `fwd_unicode_matcher` instead.
2696 """
2785 """
2697 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2786 # TODO: self.unicode_names is here a list we traverse each time with ~100k elements.
2698 # We could do a faster match using a Trie.
2787 # We could do a faster match using a Trie.
@@ -105,7 +105,7 b' class ConfigMagics(Magics):'
105 Whether to merge completion results into a single list
105 Whether to merge completion results into a single list
106 If False, only the completion results from the first non-empty
106 If False, only the completion results from the first non-empty
107 completer will be returned.
107 completer will be returned.
108 As of version 8.5.0, setting the value to ``False`` is an alias for:
108 As of version 8.6.0, setting the value to ``False`` is an alias for:
109 ``IPCompleter.suppress_competing_matchers = True.``.
109 ``IPCompleter.suppress_competing_matchers = True.``.
110 Current: True
110 Current: True
111 IPCompleter.omit__names=<Enum>
111 IPCompleter.omit__names=<Enum>
@@ -123,7 +123,7 b' class ConfigMagics(Magics):'
123 Template for path at which to output profile data for completions.
123 Template for path at which to output profile data for completions.
124 Current: '.completion_profiles'
124 Current: '.completion_profiles'
125 IPCompleter.suppress_competing_matchers=<Union>
125 IPCompleter.suppress_competing_matchers=<Union>
126 Whether to suppress completions from other `Matchers`_.
126 Whether to suppress completions from other *Matchers*.
127 When set to ``None`` (default) the matchers will attempt to auto-detect
127 When set to ``None`` (default) the matchers will attempt to auto-detect
128 whether suppression of other matchers is desirable. For example, at the
128 whether suppression of other matchers is desirable. For example, at the
129 beginning of a line followed by `%` we expect a magic completion to be the
129 beginning of a line followed by `%` we expect a magic completion to be the
@@ -136,9 +136,9 b' class ConfigMagics(Magics):'
136 False}``.
136 False}``.
137 Set ``IPCompleter.suppress_competing_matchers = True`` to limit completions
137 Set ``IPCompleter.suppress_competing_matchers = True`` to limit completions
138 to the set of matchers with the highest priority; this is equivalent to
138 to the set of matchers with the highest priority; this is equivalent to
139 ``IPCompleter.merge_completions`` and can be beneficial for
139 ``IPCompleter.merge_completions`` and can be beneficial for performance, but
140 performance, but will sometimes omit relevant candidates from matchers
140 will sometimes omit relevant candidates from matchers further down the
141 further down the priority list.
141 priority list.
142 Current: False
142 Current: False
143 IPCompleter.use_jedi=<Bool>
143 IPCompleter.use_jedi=<Bool>
144 Experimental: Use Jedi to generate autocompletions. Default to True if jedi
144 Experimental: Use Jedi to generate autocompletions. Default to True if jedi
@@ -24,6 +24,9 b' from IPython.core.completer import ('
24 provisionalcompleter,
24 provisionalcompleter,
25 match_dict_keys,
25 match_dict_keys,
26 _deduplicate_completions,
26 _deduplicate_completions,
27 completion_matcher,
28 SimpleCompletion,
29 CompletionContext,
27 )
30 )
28
31
29 # -----------------------------------------------------------------------------
32 # -----------------------------------------------------------------------------
@@ -109,6 +112,16 b' def greedy_completion():'
109 ip.Completer.greedy = greedy_original
112 ip.Completer.greedy = greedy_original
110
113
111
114
115 @contextmanager
116 def custom_matchers(matchers):
117 ip = get_ipython()
118 try:
119 ip.Completer.custom_matchers.extend(matchers)
120 yield
121 finally:
122 ip.Completer.custom_matchers.clear()
123
124
112 def test_protect_filename():
125 def test_protect_filename():
113 if sys.platform == "win32":
126 if sys.platform == "win32":
114 pairs = [
127 pairs = [
@@ -1281,3 +1294,97 b' class TestCompleter(unittest.TestCase):'
1281 completions = completer.completions(text, len(text))
1294 completions = completer.completions(text, len(text))
1282 for c in completions:
1295 for c in completions:
1283 self.assertEqual(c.text[0], "%")
1296 self.assertEqual(c.text[0], "%")
1297
1298 def test_matcher_suppression(self):
1299 @completion_matcher(identifier="a_matcher")
1300 def a_matcher(text):
1301 return ["completion_a"]
1302
1303 @completion_matcher(identifier="b_matcher", api_version=2)
1304 def b_matcher(context: CompletionContext):
1305 text = context.token
1306 result = {"completions": [SimpleCompletion("completion_b")]}
1307
1308 if text == "suppress c":
1309 result["suppress"] = {"c_matcher"}
1310
1311 if text.startswith("suppress all"):
1312 result["suppress"] = True
1313 if text == "suppress all but c":
1314 result["do_not_suppress"] = {"c_matcher"}
1315 if text == "suppress all but a":
1316 result["do_not_suppress"] = {"a_matcher"}
1317
1318 return result
1319
1320 @completion_matcher(identifier="c_matcher")
1321 def c_matcher(text):
1322 return ["completion_c"]
1323
1324 with custom_matchers([a_matcher, b_matcher, c_matcher]):
1325 ip = get_ipython()
1326 c = ip.Completer
1327
1328 def _(text, expected):
1329 with provisionalcompleter():
1330 c.use_jedi = False
1331 s, matches = c.complete(text)
1332 self.assertEqual(expected, matches)
1333
1334 _("do not suppress", ["completion_a", "completion_b", "completion_c"])
1335 _("suppress all", ["completion_b"])
1336 _("suppress all but a", ["completion_a", "completion_b"])
1337 _("suppress all but c", ["completion_b", "completion_c"])
1338
1339 def test_matcher_disabling(self):
1340 @completion_matcher(identifier="a_matcher")
1341 def a_matcher(text):
1342 return ["completion_a"]
1343
1344 @completion_matcher(identifier="b_matcher")
1345 def b_matcher(text):
1346 return ["completion_b"]
1347
1348 def _(expected):
1349 with provisionalcompleter():
1350 c.use_jedi = False
1351 s, matches = c.complete("completion_")
1352 self.assertEqual(expected, matches)
1353
1354 with custom_matchers([a_matcher, b_matcher]):
1355 ip = get_ipython()
1356 c = ip.Completer
1357
1358 _(["completion_a", "completion_b"])
1359
1360 cfg = Config()
1361 cfg.IPCompleter.disable_matchers = ["b_matcher"]
1362 c.update_config(cfg)
1363
1364 _(["completion_a"])
1365
1366 cfg.IPCompleter.disable_matchers = []
1367 c.update_config(cfg)
1368
1369 def test_matcher_priority(self):
1370 @completion_matcher(identifier="a_matcher", priority=0, api_version=2)
1371 def a_matcher(text):
1372 return {"completions": [SimpleCompletion("completion_a")], "suppress": True}
1373
1374 @completion_matcher(identifier="b_matcher", priority=2, api_version=2)
1375 def b_matcher(text):
1376 return {"completions": [SimpleCompletion("completion_b")], "suppress": True}
1377
1378 def _(expected):
1379 with provisionalcompleter():
1380 c.use_jedi = False
1381 s, matches = c.complete("completion_")
1382 self.assertEqual(expected, matches)
1383
1384 with custom_matchers([a_matcher, b_matcher]):
1385 ip = get_ipython()
1386 c = ip.Completer
1387
1388 _(["completion_b"])
1389 a_matcher.matcher_priority = 3
1390 _(["completion_a"])
General Comments 0
You need to be logged in to leave comments. Login now