Show More
@@ -0,0 +1,7 b'' | |||
|
1 | # encoding: utf-8 | |
|
2 | ||
|
3 | # Copyright (c) IPython Development Team. | |
|
4 | # Distributed under the terms of the Modified BSD License. | |
|
5 | import os | |
|
6 | ||
|
7 | GENERATING_DOCUMENTATION = os.environ.get("IN_SPHINX_RUN", None) == "True" |
@@ -109,21 +109,48 b' The matchers API is provisional and subject to change without notice.' | |||
|
109 | 109 | |
|
110 | 110 | The built-in matchers include: |
|
111 | 111 | |
|
112 |
- |
|
|
113 |
- |
|
|
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), | |
|
112 | - :any:`IPCompleter.dict_key_matcher`: dictionary key completions, | |
|
113 | - :any:`IPCompleter.magic_matcher`: completions for magics, | |
|
114 | - :any:`IPCompleter.unicode_name_matcher`, | |
|
115 | :any:`IPCompleter.fwd_unicode_matcher` | |
|
116 | and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_, | |
|
117 | - :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_, | |
|
118 | - :any:`IPCompleter.file_matcher`: paths to files and directories, | |
|
119 | - :any:`IPCompleter.python_func_kw_matcher` - function keywords, | |
|
120 | - :any:`IPCompleter.python_matches` - globals and attributes (v1 API), | |
|
119 | 121 | - ``IPCompleter.jedi_matcher`` - static analysis with Jedi, |
|
120 |
- |
|
|
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. | |
|
122 | - :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default | |
|
123 | implementation in :any:`InteractiveShell` which uses IPython hooks system | |
|
124 | (`complete_command`) with string dispatch (including regular expressions). | |
|
125 | Differently to other matchers, ``custom_completer_matcher`` will not suppress | |
|
126 | Jedi results to match behaviour in earlier IPython versions. | |
|
124 | 127 | |
|
125 | 128 | Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list. |
|
126 | 129 | |
|
130 | Matcher API | |
|
131 | ----------- | |
|
132 | ||
|
133 | Simplifying some details, the ``Matcher`` interface can described as | |
|
134 | ||
|
135 | .. highlight:: | |
|
136 | ||
|
137 | MatcherAPIv1 = Callable[[str], list[str]] | |
|
138 | MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult] | |
|
139 | ||
|
140 | Matcher = MatcherAPIv1 | MatcherAPIv2 | |
|
141 | ||
|
142 | The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0 | |
|
143 | and remains supported as a simplest way for generating completions. This is also | |
|
144 | currently the only API supported by the IPython hooks system `complete_command`. | |
|
145 | ||
|
146 | To distinguish between matcher versions ``matcher_api_version`` attribute is used. | |
|
147 | More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers, | |
|
148 | and requires a literal ``2`` for v2 Matchers. | |
|
149 | ||
|
150 | Once the API stabilises future versions may relax the requirement for specifying | |
|
151 | ``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore | |
|
152 | please do not rely on the presence of ``matcher_api_version`` for any purposes. | |
|
153 | ||
|
127 | 154 | Suppression of competing matchers |
|
128 | 155 | --------------------------------- |
|
129 | 156 | |
@@ -137,6 +164,9 b' the matcher with higher priority will be returned.' | |||
|
137 | 164 | Sometimes it is desirable to suppress most but not all other matchers; |
|
138 | 165 | this can be achieved by adding a list of identifiers of matchers which |
|
139 | 166 | should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key. |
|
167 | ||
|
168 | The suppression behaviour can is user-configurable via | |
|
169 | :any:`IPCompleter.suppress_competing_matchers`. | |
|
140 | 170 | """ |
|
141 | 171 | |
|
142 | 172 | |
@@ -146,7 +176,7 b' should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key.' | |||
|
146 | 176 | # Some of this code originated from rlcompleter in the Python standard library |
|
147 | 177 | # Copyright (C) 2001 Python Software Foundation, www.python.org |
|
148 | 178 | |
|
149 | ||
|
179 | from __future__ import annotations | |
|
150 | 180 | import builtins as builtin_mod |
|
151 | 181 | import glob |
|
152 | 182 | import inspect |
@@ -176,9 +206,9 b' from typing import (' | |||
|
176 | 206 | NamedTuple, |
|
177 | 207 | Pattern, |
|
178 | 208 | Optional, |
|
179 | Callable, | |
|
180 | 209 | TYPE_CHECKING, |
|
181 | 210 | Set, |
|
211 | Literal, | |
|
182 | 212 | ) |
|
183 | 213 | |
|
184 | 214 | from IPython.core.error import TryNext |
@@ -187,7 +217,9 b' from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol' | |||
|
187 | 217 | from IPython.core.oinspect import InspectColors |
|
188 | 218 | from IPython.testing.skipdoctest import skip_doctest |
|
189 | 219 | from IPython.utils import generics |
|
220 | from IPython.utils.decorators import sphinx_options | |
|
190 | 221 | from IPython.utils.dir2 import dir2, get_real_method |
|
222 | from IPython.utils.docs import GENERATING_DOCUMENTATION | |
|
191 | 223 | from IPython.utils.path import ensure_dir_exists |
|
192 | 224 | from IPython.utils.process import arg_split |
|
193 | 225 | from traitlets import ( |
@@ -218,16 +250,23 b' try:' | |||
|
218 | 250 | except ImportError: |
|
219 | 251 | JEDI_INSTALLED = False |
|
220 | 252 | |
|
221 | if TYPE_CHECKING: | |
|
253 | ||
|
254 | if TYPE_CHECKING or GENERATING_DOCUMENTATION: | |
|
222 | 255 | from typing import cast |
|
223 | from typing_extensions import TypedDict, NotRequired | |
|
256 | from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias | |
|
224 | 257 | else: |
|
225 | 258 | |
|
226 |
def cast(obj, |
|
|
259 | def cast(obj, type_): | |
|
260 | """Workaround for `TypeError: MatcherAPIv2() takes no arguments`""" | |
|
227 | 261 | return obj |
|
228 | 262 | |
|
229 | TypedDict = Dict | |
|
230 | NotRequired = Tuple | |
|
263 | # do not require on runtime | |
|
264 | NotRequired = Tuple # requires Python >=3.11 | |
|
265 | TypedDict = Dict # by extension of `NotRequired` requires 3.11 too | |
|
266 | Protocol = object # requires Python >=3.8 | |
|
267 | TypeAlias = Any # requires Python >=3.10 | |
|
268 | if GENERATING_DOCUMENTATION: | |
|
269 | from typing import TypedDict | |
|
231 | 270 | |
|
232 | 271 | # ----------------------------------------------------------------------------- |
|
233 | 272 | # Globals |
@@ -522,31 +561,36 b' class SimpleCompletion:' | |||
|
522 | 561 | return f"<SimpleCompletion text={self.text!r} type={self.type!r}>" |
|
523 | 562 | |
|
524 | 563 | |
|
525 | class MatcherResultBase(TypedDict): | |
|
564 | class _MatcherResultBase(TypedDict): | |
|
526 | 565 | """Definition of dictionary to be returned by new-style Matcher (API v2).""" |
|
527 | 566 | |
|
528 |
#: |
|
|
567 | #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token. | |
|
529 | 568 | matched_fragment: NotRequired[str] |
|
530 | 569 | |
|
531 |
#: |
|
|
570 | #: Whether to suppress results from all other matchers (True), some | |
|
532 | 571 | #: matchers (set of identifiers) or none (False); default is False. |
|
533 | 572 | suppress: NotRequired[Union[bool, Set[str]]] |
|
534 | 573 | |
|
535 |
#: |
|
|
574 | #: Identifiers of matchers which should NOT be suppressed when this matcher | |
|
575 | #: requests to suppress all other matchers; defaults to an empty set. | |
|
536 | 576 | do_not_suppress: NotRequired[Set[str]] |
|
537 | 577 | |
|
538 |
#: |
|
|
578 | #: Are completions already ordered and should be left as-is? default is False. | |
|
539 | 579 | ordered: NotRequired[bool] |
|
540 | 580 | |
|
541 | 581 | |
|
542 | class SimpleMatcherResult(MatcherResultBase): | |
|
582 | @sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"]) | |
|
583 | class SimpleMatcherResult(_MatcherResultBase, TypedDict): | |
|
543 | 584 | """Result of new-style completion matcher.""" |
|
544 | 585 | |
|
545 | #: list of candidate completions | |
|
586 | # note: TypedDict is added again to the inheritance chain | |
|
587 | # in order to get __orig_bases__ for documentation | |
|
588 | ||
|
589 | #: List of candidate completions | |
|
546 | 590 | completions: Sequence[SimpleCompletion] |
|
547 | 591 | |
|
548 | 592 | |
|
549 | class _JediMatcherResult(MatcherResultBase): | |
|
593 | class _JediMatcherResult(_MatcherResultBase): | |
|
550 | 594 | """Matching result returned by Jedi (will be processed differently)""" |
|
551 | 595 | |
|
552 | 596 | #: list of candidate completions |
@@ -592,11 +636,38 b' class CompletionContext(NamedTuple):' | |||
|
592 | 636 | return self.full_text.split("\n")[self.cursor_line] |
|
593 | 637 | |
|
594 | 638 | |
|
639 | #: Matcher results for API v2. | |
|
595 | 640 | MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult] |
|
596 | 641 | |
|
597 | MatcherAPIv1 = Callable[[str], List[str]] | |
|
598 | MatcherAPIv2 = Callable[[CompletionContext], MatcherResult] | |
|
599 | Matcher = Union[MatcherAPIv1, MatcherAPIv2] | |
|
642 | ||
|
643 | class _MatcherAPIv1Base(Protocol): | |
|
644 | def __call__(self, text: str) -> list[str]: | |
|
645 | """Call signature.""" | |
|
646 | ||
|
647 | ||
|
648 | class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol): | |
|
649 | #: API version | |
|
650 | matcher_api_version: Optional[Literal[1]] | |
|
651 | ||
|
652 | def __call__(self, text: str) -> list[str]: | |
|
653 | """Call signature.""" | |
|
654 | ||
|
655 | ||
|
656 | #: Protocol describing Matcher API v1. | |
|
657 | MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total] | |
|
658 | ||
|
659 | ||
|
660 | class MatcherAPIv2(Protocol): | |
|
661 | """Protocol describing Matcher API v2.""" | |
|
662 | ||
|
663 | #: API version | |
|
664 | matcher_api_version: Literal[2] = 2 | |
|
665 | ||
|
666 | def __call__(self, context: CompletionContext) -> MatcherResult: | |
|
667 | """Call signature.""" | |
|
668 | ||
|
669 | ||
|
670 | Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2] | |
|
600 | 671 | |
|
601 | 672 | |
|
602 | 673 | def completion_matcher( |
@@ -1160,7 +1231,7 b' def _safe_isinstance(obj, module, class_name):' | |||
|
1160 | 1231 | def back_unicode_name_matcher(context): |
|
1161 | 1232 | """Match Unicode characters back to Unicode name |
|
1162 | 1233 | |
|
1163 |
Same as |
|
|
1234 | Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API. | |
|
1164 | 1235 | """ |
|
1165 | 1236 | fragment, matches = back_unicode_name_matches(context.token) |
|
1166 | 1237 | return _convert_matcher_v1_result_to_v2( |
@@ -1178,6 +1249,9 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:' | |||
|
1178 | 1249 | |
|
1179 | 1250 | This will not either back-complete standard sequences like \\n, \\b ... |
|
1180 | 1251 | |
|
1252 | .. deprecated:: 8.6 | |
|
1253 | You can use :meth:`back_unicode_name_matcher` instead. | |
|
1254 | ||
|
1181 | 1255 | Returns |
|
1182 | 1256 | ======= |
|
1183 | 1257 | |
@@ -1187,7 +1261,6 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:' | |||
|
1187 | 1261 | empty string, |
|
1188 | 1262 | - a sequence (of 1), name for the match Unicode character, preceded by |
|
1189 | 1263 | backslash, or empty if no match. |
|
1190 | ||
|
1191 | 1264 | """ |
|
1192 | 1265 | if len(text)<2: |
|
1193 | 1266 | return '', () |
@@ -1212,7 +1285,7 b' def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]:' | |||
|
1212 | 1285 | def back_latex_name_matcher(context): |
|
1213 | 1286 | """Match latex characters back to unicode name |
|
1214 | 1287 | |
|
1215 |
Same as |
|
|
1288 | Same as :any:`back_latex_name_matches`, but adopted to new Matcher API. | |
|
1216 | 1289 | """ |
|
1217 | 1290 | fragment, matches = back_latex_name_matches(context.token) |
|
1218 | 1291 | return _convert_matcher_v1_result_to_v2( |
@@ -1225,6 +1298,8 b' def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]:' | |||
|
1225 | 1298 | |
|
1226 | 1299 | This does ``\\β΅`` -> ``\\aleph`` |
|
1227 | 1300 | |
|
1301 | .. deprecated:: 8.6 | |
|
1302 | You can use :meth:`back_latex_name_matcher` instead. | |
|
1228 | 1303 | """ |
|
1229 | 1304 | if len(text)<2: |
|
1230 | 1305 | return '', () |
@@ -1567,7 +1642,7 b' class IPCompleter(Completer):' | |||
|
1567 | 1642 | |
|
1568 | 1643 | @context_matcher() |
|
1569 | 1644 | def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult: |
|
1570 |
"""Same as |
|
|
1645 | """Same as :any:`file_matches`, but adopted to new Matcher API.""" | |
|
1571 | 1646 | matches = self.file_matches(context.token) |
|
1572 | 1647 | # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter, |
|
1573 | 1648 | # starts with `/home/`, `C:\`, etc) |
@@ -1587,7 +1662,8 b' class IPCompleter(Completer):' | |||
|
1587 | 1662 | current (as of Python 2.3) Python readline it's possible to do |
|
1588 | 1663 | better. |
|
1589 | 1664 | |
|
1590 | DEPRECATED: Deprecated since 8.6. Use ``file_matcher`` instead. | |
|
1665 | .. deprecated:: 8.6 | |
|
1666 | You can use :meth:`file_matcher` instead. | |
|
1591 | 1667 | """ |
|
1592 | 1668 | |
|
1593 | 1669 | # chars that require escaping with backslash - i.e. chars |
@@ -1660,6 +1736,7 b' class IPCompleter(Completer):' | |||
|
1660 | 1736 | |
|
1661 | 1737 | @context_matcher() |
|
1662 | 1738 | def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult: |
|
1739 | """Match magics.""" | |
|
1663 | 1740 | text = context.token |
|
1664 | 1741 | matches = self.magic_matches(text) |
|
1665 | 1742 | result = _convert_matcher_v1_result_to_v2(matches, type="magic") |
@@ -1670,7 +1747,8 b' class IPCompleter(Completer):' | |||
|
1670 | 1747 | def magic_matches(self, text: str): |
|
1671 | 1748 | """Match magics. |
|
1672 | 1749 | |
|
1673 | DEPRECATED: Deprecated since 8.6. Use ``magic_matcher`` instead. | |
|
1750 | .. deprecated:: 8.6 | |
|
1751 | You can use :meth:`magic_matcher` instead. | |
|
1674 | 1752 | """ |
|
1675 | 1753 | # Get all shell magics now rather than statically, so magics loaded at |
|
1676 | 1754 | # runtime show up too. |
@@ -1722,7 +1800,8 b' class IPCompleter(Completer):' | |||
|
1722 | 1800 | def magic_config_matches(self, text: str) -> List[str]: |
|
1723 | 1801 | """Match class names and attributes for %config magic. |
|
1724 | 1802 | |
|
1725 | DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead. | |
|
1803 | .. deprecated:: 8.6 | |
|
1804 | You can use :meth:`magic_config_matcher` instead. | |
|
1726 | 1805 | """ |
|
1727 | 1806 | texts = text.strip().split() |
|
1728 | 1807 | |
@@ -1767,7 +1846,8 b' class IPCompleter(Completer):' | |||
|
1767 | 1846 | def magic_color_matches(self, text: str) -> List[str]: |
|
1768 | 1847 | """Match color schemes for %colors magic. |
|
1769 | 1848 | |
|
1770 | DEPRECATED: Deprecated since 8.6. Use ``magic_color_matcher`` instead. | |
|
1849 | .. deprecated:: 8.6 | |
|
1850 | You can use :meth:`magic_color_matcher` instead. | |
|
1771 | 1851 | """ |
|
1772 | 1852 | texts = text.split() |
|
1773 | 1853 | if text.endswith(' '): |
@@ -1815,7 +1895,8 b' class IPCompleter(Completer):' | |||
|
1815 | 1895 | If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` |
|
1816 | 1896 | object containing a string with the Jedi debug information attached. |
|
1817 | 1897 | |
|
1818 | DEPRECATED: Deprecated since 8.6. Use ``_jedi_matcher`` instead. | |
|
1898 | .. deprecated:: 8.6 | |
|
1899 | You can use :meth:`_jedi_matcher` instead. | |
|
1819 | 1900 | """ |
|
1820 | 1901 | namespaces = [self.namespace] |
|
1821 | 1902 | if self.global_namespace is not None: |
@@ -1961,7 +2042,8 b' class IPCompleter(Completer):' | |||
|
1961 | 2042 | def python_func_kw_matches(self, text): |
|
1962 | 2043 | """Match named parameters (kwargs) of the last open function. |
|
1963 | 2044 | |
|
1964 | DEPRECATED: Deprecated since 8.6. Use ``magic_config_matcher`` instead. | |
|
2045 | .. deprecated:: 8.6 | |
|
2046 | You can use :meth:`python_func_kw_matcher` instead. | |
|
1965 | 2047 | """ |
|
1966 | 2048 | |
|
1967 | 2049 | if "." in text: # a parameter cannot be dotted |
@@ -2068,7 +2150,8 b' class IPCompleter(Completer):' | |||
|
2068 | 2150 | def dict_key_matches(self, text: str) -> List[str]: |
|
2069 | 2151 | """Match string keys in a dictionary, after e.g. ``foo[``. |
|
2070 | 2152 | |
|
2071 | DEPRECATED: Deprecated since 8.6. Use `dict_key_matcher` instead. | |
|
2153 | .. deprecated:: 8.6 | |
|
2154 | You can use :meth:`dict_key_matcher` instead. | |
|
2072 | 2155 | """ |
|
2073 | 2156 | |
|
2074 | 2157 | if self.__dict_key_regexps is not None: |
@@ -2173,6 +2256,7 b' class IPCompleter(Completer):' | |||
|
2173 | 2256 | |
|
2174 | 2257 | @context_matcher() |
|
2175 | 2258 | def unicode_name_matcher(self, context): |
|
2259 | """Same as :any:`unicode_name_matches`, but adopted to new Matcher API.""" | |
|
2176 | 2260 | fragment, matches = self.unicode_name_matches(context.token) |
|
2177 | 2261 | return _convert_matcher_v1_result_to_v2( |
|
2178 | 2262 | matches, type="unicode", fragment=fragment, suppress_if_matches=True |
@@ -2216,7 +2300,8 b' class IPCompleter(Completer):' | |||
|
2216 | 2300 | |
|
2217 | 2301 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``Ξ±`` |
|
2218 | 2302 | |
|
2219 | DEPRECATED: Deprecated since 8.6. Use `latex_matcher` instead. | |
|
2303 | .. deprecated:: 8.6 | |
|
2304 | You can use :meth:`latex_name_matcher` instead. | |
|
2220 | 2305 | """ |
|
2221 | 2306 | slashpos = text.rfind('\\') |
|
2222 | 2307 | if slashpos > -1: |
@@ -2235,9 +2320,13 b' class IPCompleter(Completer):' | |||
|
2235 | 2320 | |
|
2236 | 2321 | @context_matcher() |
|
2237 | 2322 | def custom_completer_matcher(self, context): |
|
2323 | """Dispatch custom completer. | |
|
2324 | ||
|
2325 | If a match is found, suppresses all other matchers except for Jedi. | |
|
2326 | """ | |
|
2238 | 2327 | matches = self.dispatch_custom_completer(context.token) or [] |
|
2239 | 2328 | result = _convert_matcher_v1_result_to_v2( |
|
2240 |
matches, type= |
|
|
2329 | matches, type=_UNKNOWN_TYPE, suppress_if_matches=True | |
|
2241 | 2330 | ) |
|
2242 | 2331 | result["ordered"] = True |
|
2243 | 2332 | result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)} |
@@ -2245,7 +2334,8 b' class IPCompleter(Completer):' | |||
|
2245 | 2334 | |
|
2246 | 2335 | def dispatch_custom_completer(self, text): |
|
2247 | 2336 | """ |
|
2248 | DEPRECATED: Deprecated since 8.6. Use `custom_completer_matcher` instead. | |
|
2337 | .. deprecated:: 8.6 | |
|
2338 | You can use :meth:`custom_completer_matcher` instead. | |
|
2249 | 2339 | """ |
|
2250 | 2340 | if not self.custom_completers: |
|
2251 | 2341 | return |
@@ -2768,7 +2858,7 b' class IPCompleter(Completer):' | |||
|
2768 | 2858 | |
|
2769 | 2859 | @context_matcher() |
|
2770 | 2860 | def fwd_unicode_matcher(self, context): |
|
2771 |
"""Same as |
|
|
2861 | """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API.""" | |
|
2772 | 2862 | fragment, matches = self.latex_matches(context.token) |
|
2773 | 2863 | return _convert_matcher_v1_result_to_v2( |
|
2774 | 2864 | matches, type="unicode", fragment=fragment, suppress_if_matches=True |
@@ -2779,15 +2869,16 b' class IPCompleter(Completer):' | |||
|
2779 | 2869 | Forward match a string starting with a backslash with a list of |
|
2780 | 2870 | potential Unicode completions. |
|
2781 | 2871 | |
|
2782 |
Will compute list |
|
|
2872 | Will compute list of Unicode character names on first call and cache it. | |
|
2873 | ||
|
2874 | .. deprecated:: 8.6 | |
|
2875 | You can use :meth:`fwd_unicode_matcher` instead. | |
|
2783 | 2876 | |
|
2784 | 2877 | Returns |
|
2785 | 2878 | ------- |
|
2786 | 2879 | At tuple with: |
|
2787 | 2880 | - matched text (empty if no matches) |
|
2788 | 2881 | - list of potential completions, empty tuple otherwise) |
|
2789 | ||
|
2790 | DEPRECATED: Deprecated since 8.6. Use `fwd_unicode_matcher` instead. | |
|
2791 | 2882 | """ |
|
2792 | 2883 | # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. |
|
2793 | 2884 | # We could do a faster match using a Trie. |
@@ -2,7 +2,7 b'' | |||
|
2 | 2 | """Decorators that don't go anywhere else. |
|
3 | 3 | |
|
4 | 4 | This module contains misc. decorators that don't really go with another module |
|
5 | in :mod:`IPython.utils`. Beore putting something here please see if it should | |
|
5 | in :mod:`IPython.utils`. Before putting something here please see if it should | |
|
6 | 6 | go into another topical module in :mod:`IPython.utils`. |
|
7 | 7 | """ |
|
8 | 8 | |
@@ -16,6 +16,10 b' go into another topical module in :mod:`IPython.utils`.' | |||
|
16 | 16 | #----------------------------------------------------------------------------- |
|
17 | 17 | # Imports |
|
18 | 18 | #----------------------------------------------------------------------------- |
|
19 | from typing import Sequence | |
|
20 | ||
|
21 | from IPython.utils.docs import GENERATING_DOCUMENTATION | |
|
22 | ||
|
19 | 23 | |
|
20 | 24 | #----------------------------------------------------------------------------- |
|
21 | 25 | # Code |
@@ -48,6 +52,7 b' def flag_calls(func):' | |||
|
48 | 52 | wrapper.__doc__ = func.__doc__ |
|
49 | 53 | return wrapper |
|
50 | 54 | |
|
55 | ||
|
51 | 56 | def undoc(func): |
|
52 | 57 | """Mark a function or class as undocumented. |
|
53 | 58 | |
@@ -56,3 +61,23 b' def undoc(func):' | |||
|
56 | 61 | """ |
|
57 | 62 | return func |
|
58 | 63 | |
|
64 | ||
|
65 | def sphinx_options( | |
|
66 | show_inheritance: bool = True, | |
|
67 | show_inherited_members: bool = False, | |
|
68 | exclude_inherited_from: Sequence[str] = tuple(), | |
|
69 | ): | |
|
70 | """Set sphinx options""" | |
|
71 | ||
|
72 | def wrapper(func): | |
|
73 | if not GENERATING_DOCUMENTATION: | |
|
74 | return func | |
|
75 | ||
|
76 | func._sphinx_options = dict( | |
|
77 | show_inheritance=show_inheritance, | |
|
78 | show_inherited_members=show_inherited_members, | |
|
79 | exclude_inherited_from=exclude_inherited_from, | |
|
80 | ) | |
|
81 | return func | |
|
82 | ||
|
83 | return wrapper |
@@ -41,6 +41,14 b' else:' | |||
|
41 | 41 | html_theme = "sphinx_rtd_theme" |
|
42 | 42 | html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] |
|
43 | 43 | |
|
44 | # Allow Python scripts to change behaviour during sphinx run | |
|
45 | os.environ["IN_SPHINX_RUN"] = "True" | |
|
46 | ||
|
47 | autodoc_type_aliases = { | |
|
48 | "Matcher": " IPython.core.completer.Matcher", | |
|
49 | "MatcherAPIv1": " IPython.core.completer.MatcherAPIv1", | |
|
50 | } | |
|
51 | ||
|
44 | 52 | # If your extensions are in another directory, add it here. If the directory |
|
45 | 53 | # is relative to the documentation root, use os.path.abspath to make it |
|
46 | 54 | # absolute, like shown here. |
@@ -24,14 +24,9 b' import inspect' | |||
|
24 | 24 | import os |
|
25 | 25 | import re |
|
26 | 26 | from importlib import import_module |
|
27 | from types import SimpleNamespace as Obj | |
|
27 | 28 | |
|
28 | 29 | |
|
29 | class Obj(object): | |
|
30 | '''Namespace to hold arbitrary information.''' | |
|
31 | def __init__(self, **kwargs): | |
|
32 | for k, v in kwargs.items(): | |
|
33 | setattr(self, k, v) | |
|
34 | ||
|
35 | 30 | class FuncClsScanner(ast.NodeVisitor): |
|
36 | 31 | """Scan a module for top-level functions and classes. |
|
37 | 32 | |
@@ -42,7 +37,7 b' class FuncClsScanner(ast.NodeVisitor):' | |||
|
42 | 37 | self.classes = [] |
|
43 | 38 | self.classes_seen = set() |
|
44 | 39 | self.functions = [] |
|
45 | ||
|
40 | ||
|
46 | 41 | @staticmethod |
|
47 | 42 | def has_undoc_decorator(node): |
|
48 | 43 | return any(isinstance(d, ast.Name) and d.id == 'undoc' \ |
@@ -62,11 +57,15 b' class FuncClsScanner(ast.NodeVisitor):' | |||
|
62 | 57 | self.functions.append(node.name) |
|
63 | 58 | |
|
64 | 59 | def visit_ClassDef(self, node): |
|
65 | if not (node.name.startswith('_') or self.has_undoc_decorator(node)) \ | |
|
66 | and node.name not in self.classes_seen: | |
|
67 | cls = Obj(name=node.name) | |
|
68 | cls.has_init = any(isinstance(n, ast.FunctionDef) and \ | |
|
69 | n.name=='__init__' for n in node.body) | |
|
60 | if ( | |
|
61 | not (node.name.startswith("_") or self.has_undoc_decorator(node)) | |
|
62 | and node.name not in self.classes_seen | |
|
63 | ): | |
|
64 | cls = Obj(name=node.name, sphinx_options={}) | |
|
65 | cls.has_init = any( | |
|
66 | isinstance(n, ast.FunctionDef) and n.name == "__init__" | |
|
67 | for n in node.body | |
|
68 | ) | |
|
70 | 69 | self.classes.append(cls) |
|
71 | 70 | self.classes_seen.add(node.name) |
|
72 | 71 | |
@@ -221,7 +220,11 b' class ApiDocWriter(object):' | |||
|
221 | 220 | funcs, classes = [], [] |
|
222 | 221 | for name, obj in ns.items(): |
|
223 | 222 | if inspect.isclass(obj): |
|
224 | cls = Obj(name=name, has_init='__init__' in obj.__dict__) | |
|
223 | cls = Obj( | |
|
224 | name=name, | |
|
225 | has_init="__init__" in obj.__dict__, | |
|
226 | sphinx_options=getattr(obj, "_sphinx_options", {}), | |
|
227 | ) | |
|
225 | 228 | classes.append(cls) |
|
226 | 229 | elif inspect.isfunction(obj): |
|
227 | 230 | funcs.append(name) |
@@ -279,10 +282,18 b' class ApiDocWriter(object):' | |||
|
279 | 282 | self.rst_section_levels[2] * len(subhead) + '\n' |
|
280 | 283 | |
|
281 | 284 | for c in classes: |
|
282 | ad += '\n.. autoclass:: ' + c.name + '\n' | |
|
285 | opts = c.sphinx_options | |
|
286 | ad += "\n.. autoclass:: " + c.name + "\n" | |
|
283 | 287 | # must NOT exclude from index to keep cross-refs working |
|
284 |
ad += |
|
|
285 |
|
|
|
288 | ad += " :members:\n" | |
|
289 | if opts.get("show_inheritance", True): | |
|
290 | ad += " :show-inheritance:\n" | |
|
291 | if opts.get("show_inherited_members", False): | |
|
292 | exclusions_list = opts.get("exclude_inherited_from", []) | |
|
293 | exclusions = ( | |
|
294 | (" " + " ".join(exclusions_list)) if exclusions_list else "" | |
|
295 | ) | |
|
296 | ad += f" :inherited-members:{exclusions}\n" | |
|
286 | 297 | if c.has_init: |
|
287 | 298 | ad += '\n .. automethod:: __init__\n' |
|
288 | 299 |
General Comments 0
You need to be logged in to leave comments.
Login now