diff --git "a/openalex_env_map/lib/python3.10/site-packages/IPython/core/completer.py" "b/openalex_env_map/lib/python3.10/site-packages/IPython/core/completer.py" deleted file mode 100644--- "a/openalex_env_map/lib/python3.10/site-packages/IPython/core/completer.py" +++ /dev/null @@ -1,3421 +0,0 @@ -"""Completion for IPython. - -This module started as fork of the rlcompleter module in the Python standard -library. The original enhancements made to rlcompleter have been sent -upstream and were accepted as of Python 2.3, - -This module now support a wide variety of completion mechanism both available -for normal classic Python code, as well as completer for IPython specific -Syntax like magics. - -Latex and Unicode completion -============================ - -IPython and compatible frontends not only can complete your code, but can help -you to input a wide range of characters. In particular we allow you to insert -a unicode character using the tab completion mechanism. - -Forward latex/unicode completion --------------------------------- - -Forward completion allows you to easily type a unicode character using its latex -name, or unicode long description. To do so type a backslash follow by the -relevant name and press tab: - - -Using latex completion: - -.. code:: - - \\alpha<tab> - α - -or using unicode completion: - - -.. code:: - - \\GREEK SMALL LETTER ALPHA<tab> - α - - -Only valid Python identifiers will complete. Combining characters (like arrow or -dots) are also available, unlike latex they need to be put after the their -counterpart that is to say, ``F\\\\vec<tab>`` is correct, not ``\\\\vec<tab>F``. - -Some browsers are known to display combining characters incorrectly. - -Backward latex completion -------------------------- - -It is sometime challenging to know how to type a character, if you are using -IPython, or any compatible frontend you can prepend backslash to the character -and press :kbd:`Tab` to expand it to its latex form. - -.. code:: - - \\α<tab> - \\alpha - - -Both forward and backward completions can be deactivated by setting the -:std:configtrait:`Completer.backslash_combining_completions` option to -``False``. - - -Experimental -============ - -Starting with IPython 6.0, this module can make use of the Jedi library to -generate completions both using static analysis of the code, and dynamically -inspecting multiple namespaces. Jedi is an autocompletion and static analysis -for Python. The APIs attached to this new mechanism is unstable and will -raise unless use in an :any:`provisionalcompleter` context manager. - -You will find that the following are experimental: - - - :any:`provisionalcompleter` - - :any:`IPCompleter.completions` - - :any:`Completion` - - :any:`rectify_completions` - -.. note:: - - better name for :any:`rectify_completions` ? - -We welcome any feedback on these new API, and we also encourage you to try this -module in debug mode (start IPython with ``--Completer.debug=True``) in order -to have extra logging information if :any:`jedi` is crashing, or if current -IPython completer pending deprecations are returning results not yet handled -by :any:`jedi` - -Using Jedi for tab completion allow snippets like the following to work without -having to execute any code: - - >>> myvar = ['hello', 42] - ... myvar[1].bi<tab> - -Tab completion will be able to infer that ``myvar[1]`` is a real number without -executing almost any code unlike the deprecated :any:`IPCompleter.greedy` -option. - -Be sure to update :any:`jedi` to the latest stable version or to try the -current development version to get better completions. - -Matchers -======== - -All completions routines are implemented using unified *Matchers* API. -The matchers API is provisional and subject to change without notice. - -The built-in matchers include: - -- :any:`IPCompleter.dict_key_matcher`: dictionary key completions, -- :any:`IPCompleter.magic_matcher`: completions for magics, -- :any:`IPCompleter.unicode_name_matcher`, - :any:`IPCompleter.fwd_unicode_matcher` - and :any:`IPCompleter.latex_name_matcher`: see `Forward latex/unicode completion`_, -- :any:`back_unicode_name_matcher` and :any:`back_latex_name_matcher`: see `Backward latex completion`_, -- :any:`IPCompleter.file_matcher`: paths to files and directories, -- :any:`IPCompleter.python_func_kw_matcher` - function keywords, -- :any:`IPCompleter.python_matches` - globals and attributes (v1 API), -- ``IPCompleter.jedi_matcher`` - static analysis with Jedi, -- :any:`IPCompleter.custom_completer_matcher` - pluggable completer with a default - implementation in :any:`InteractiveShell` which uses IPython hooks system - (`complete_command`) with string dispatch (including regular expressions). - Differently to other matchers, ``custom_completer_matcher`` will not suppress - Jedi results to match behaviour in earlier IPython versions. - -Custom matchers can be added by appending to ``IPCompleter.custom_matchers`` list. - -Matcher API ------------ - -Simplifying some details, the ``Matcher`` interface can described as - -.. code-block:: - - MatcherAPIv1 = Callable[[str], list[str]] - MatcherAPIv2 = Callable[[CompletionContext], SimpleMatcherResult] - - Matcher = MatcherAPIv1 | MatcherAPIv2 - -The ``MatcherAPIv1`` reflects the matcher API as available prior to IPython 8.6.0 -and remains supported as a simplest way for generating completions. This is also -currently the only API supported by the IPython hooks system `complete_command`. - -To distinguish between matcher versions ``matcher_api_version`` attribute is used. -More precisely, the API allows to omit ``matcher_api_version`` for v1 Matchers, -and requires a literal ``2`` for v2 Matchers. - -Once the API stabilises future versions may relax the requirement for specifying -``matcher_api_version`` by switching to :any:`functools.singledispatch`, therefore -please do not rely on the presence of ``matcher_api_version`` for any purposes. - -Suppression of competing matchers ---------------------------------- - -By default results from all matchers are combined, in the order determined by -their priority. Matchers can request to suppress results from subsequent -matchers by setting ``suppress`` to ``True`` in the ``MatcherResult``. - -When multiple matchers simultaneously request suppression, the results from of -the matcher with higher priority will be returned. - -Sometimes it is desirable to suppress most but not all other matchers; -this can be achieved by adding a set of identifiers of matchers which -should not be suppressed to ``MatcherResult`` under ``do_not_suppress`` key. - -The suppression behaviour can is user-configurable via -:std:configtrait:`IPCompleter.suppress_competing_matchers`. -""" - - -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. -# -# Some of this code originated from rlcompleter in the Python standard library -# Copyright (C) 2001 Python Software Foundation, www.python.org - -from __future__ import annotations -import builtins as builtin_mod -import enum -import glob -import inspect -import itertools -import keyword -import ast -import os -import re -import string -import sys -import tokenize -import time -import unicodedata -import uuid -import warnings -from ast import literal_eval -from collections import defaultdict -from contextlib import contextmanager -from dataclasses import dataclass -from functools import cached_property, partial -from types import SimpleNamespace -from typing import ( - Iterable, - Iterator, - List, - Tuple, - Union, - Any, - Sequence, - Dict, - Optional, - TYPE_CHECKING, - Set, - Sized, - TypeVar, - Literal, -) - -from IPython.core.guarded_eval import guarded_eval, EvaluationContext -from IPython.core.error import TryNext -from IPython.core.inputtransformer2 import ESC_MAGIC -from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol -from IPython.core.oinspect import InspectColors -from IPython.testing.skipdoctest import skip_doctest -from IPython.utils import generics -from IPython.utils.decorators import sphinx_options -from IPython.utils.dir2 import dir2, get_real_method -from IPython.utils.docs import GENERATING_DOCUMENTATION -from IPython.utils.path import ensure_dir_exists -from IPython.utils.process import arg_split -from traitlets import ( - Bool, - Enum, - Int, - List as ListTrait, - Unicode, - Dict as DictTrait, - Union as UnionTrait, - observe, -) -from traitlets.config.configurable import Configurable - -import __main__ - -# skip module docstests -__skip_doctest__ = True - - -try: - import jedi - jedi.settings.case_insensitive_completion = False - import jedi.api.helpers - import jedi.api.classes - JEDI_INSTALLED = True -except ImportError: - JEDI_INSTALLED = False - - -if TYPE_CHECKING or GENERATING_DOCUMENTATION and sys.version_info >= (3, 11): - from typing import cast - from typing_extensions import TypedDict, NotRequired, Protocol, TypeAlias, TypeGuard -else: - from typing import Generic - - def cast(type_, obj): - """Workaround for `TypeError: MatcherAPIv2() takes no arguments`""" - return obj - - # do not require on runtime - NotRequired = Tuple # requires Python >=3.11 - TypedDict = Dict # by extension of `NotRequired` requires 3.11 too - Protocol = object # requires Python >=3.8 - TypeAlias = Any # requires Python >=3.10 - TypeGuard = Generic # requires Python >=3.10 -if GENERATING_DOCUMENTATION: - from typing import TypedDict - -# ----------------------------------------------------------------------------- -# Globals -#----------------------------------------------------------------------------- - -# ranges where we have most of the valid unicode names. We could be more finer -# grained but is it worth it for performance While unicode have character in the -# range 0, 0x110000, we seem to have name for about 10% of those. (131808 as I -# write this). With below range we cover them all, with a density of ~67% -# biggest next gap we consider only adds up about 1% density and there are 600 -# gaps that would need hard coding. -_UNICODE_RANGES = [(32, 0x323B0), (0xE0001, 0xE01F0)] - -# Public API -__all__ = ["Completer", "IPCompleter"] - -if sys.platform == 'win32': - PROTECTABLES = ' ' -else: - PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&' - -# Protect against returning an enormous number of completions which the frontend -# may have trouble processing. -MATCHES_LIMIT = 500 - -# Completion type reported when no type can be inferred. -_UNKNOWN_TYPE = "<unknown>" - -# sentinel value to signal lack of a match -not_found = object() - -class ProvisionalCompleterWarning(FutureWarning): - """ - Exception raise by an experimental feature in this module. - - Wrap code in :any:`provisionalcompleter` context manager if you - are certain you want to use an unstable feature. - """ - pass - -warnings.filterwarnings('error', category=ProvisionalCompleterWarning) - - -@skip_doctest -@contextmanager -def provisionalcompleter(action='ignore'): - """ - This context manager has to be used in any place where unstable completer - behavior and API may be called. - - >>> with provisionalcompleter(): - ... completer.do_experimental_things() # works - - >>> completer.do_experimental_things() # raises. - - .. note:: - - Unstable - - By using this context manager you agree that the API in use may change - without warning, and that you won't complain if they do so. - - You also understand that, if the API is not to your liking, you should report - a bug to explain your use case upstream. - - We'll be happy to get your feedback, feature requests, and improvements on - any of the unstable APIs! - """ - with warnings.catch_warnings(): - warnings.filterwarnings(action, category=ProvisionalCompleterWarning) - yield - - -def has_open_quotes(s: str) -> Union[str, bool]: - """Return whether a string has open quotes. - - This simply counts whether the number of quote characters of either type in - the string is odd. - - Returns - ------- - If there is an open quote, the quote character is returned. Else, return - False. - """ - # We check " first, then ', so complex cases with nested quotes will get - # the " to take precedence. - if s.count('"') % 2: - return '"' - elif s.count("'") % 2: - return "'" - else: - return False - - -def protect_filename(s: str, protectables: str = PROTECTABLES) -> str: - """Escape a string to protect certain characters.""" - if set(s) & set(protectables): - if sys.platform == "win32": - return '"' + s + '"' - else: - return "".join(("\\" + c if c in protectables else c) for c in s) - else: - return s - - -def expand_user(path:str) -> Tuple[str, bool, str]: - """Expand ``~``-style usernames in strings. - - This is similar to :func:`os.path.expanduser`, but it computes and returns - extra information that will be useful if the input was being used in - computing completions, and you wish to return the completions with the - original '~' instead of its expanded value. - - Parameters - ---------- - path : str - String to be expanded. If no ~ is present, the output is the same as the - input. - - Returns - ------- - newpath : str - Result of ~ expansion in the input path. - tilde_expand : bool - Whether any expansion was performed or not. - tilde_val : str - The value that ~ was replaced with. - """ - # Default values - tilde_expand = False - tilde_val = '' - newpath = path - - if path.startswith('~'): - tilde_expand = True - rest = len(path)-1 - newpath = os.path.expanduser(path) - if rest: - tilde_val = newpath[:-rest] - else: - tilde_val = newpath - - return newpath, tilde_expand, tilde_val - - -def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: - """Does the opposite of expand_user, with its outputs. - """ - if tilde_expand: - return path.replace(tilde_val, '~') - else: - return path - - -def completions_sorting_key(word): - """key for sorting completions - - This does several things: - - - Demote any completions starting with underscores to the end - - Insert any %magic and %%cellmagic completions in the alphabetical order - by their name - """ - prio1, prio2 = 0, 0 - - if word.startswith('__'): - prio1 = 2 - elif word.startswith('_'): - prio1 = 1 - - if word.endswith('='): - prio1 = -1 - - if word.startswith('%%'): - # If there's another % in there, this is something else, so leave it alone - if "%" not in word[2:]: - word = word[2:] - prio2 = 2 - elif word.startswith('%'): - if "%" not in word[1:]: - word = word[1:] - prio2 = 1 - - return prio1, word, prio2 - - -class _FakeJediCompletion: - """ - This is a workaround to communicate to the UI that Jedi has crashed and to - report a bug. Will be used only id :any:`IPCompleter.debug` is set to true. - - Added in IPython 6.0 so should likely be removed for 7.0 - - """ - - def __init__(self, name): - - self.name = name - self.complete = name - self.type = 'crashed' - self.name_with_symbols = name - self.signature = "" - self._origin = "fake" - self.text = "crashed" - - def __repr__(self): - return '<Fake completion object jedi has crashed>' - - -_JediCompletionLike = Union["jedi.api.Completion", _FakeJediCompletion] - - -class Completion: - """ - Completion object used and returned by IPython completers. - - .. warning:: - - Unstable - - This function is unstable, API may change without warning. - It will also raise unless use in proper context manager. - - This act as a middle ground :any:`Completion` object between the - :any:`jedi.api.classes.Completion` object and the Prompt Toolkit completion - object. While Jedi need a lot of information about evaluator and how the - code should be ran/inspected, PromptToolkit (and other frontend) mostly - need user facing information. - - - Which range should be replaced replaced by what. - - Some metadata (like completion type), or meta information to displayed to - the use user. - - For debugging purpose we can also store the origin of the completion (``jedi``, - ``IPython.python_matches``, ``IPython.magics_matches``...). - """ - - __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] - - def __init__( - self, - start: int, - end: int, - text: str, - *, - type: Optional[str] = None, - _origin="", - signature="", - ) -> None: - warnings.warn( - "``Completion`` is a provisional API (as of IPython 6.0). " - "It may change without warnings. " - "Use in corresponding context manager.", - category=ProvisionalCompleterWarning, - stacklevel=2, - ) - - self.start = start - self.end = end - self.text = text - self.type = type - self.signature = signature - self._origin = _origin - - def __repr__(self): - return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \ - (self.start, self.end, self.text, self.type or '?', self.signature or '?') - - def __eq__(self, other) -> bool: - """ - Equality and hash do not hash the type (as some completer may not be - able to infer the type), but are use to (partially) de-duplicate - completion. - - Completely de-duplicating completion is a bit tricker that just - comparing as it depends on surrounding text, which Completions are not - aware of. - """ - return self.start == other.start and \ - self.end == other.end and \ - self.text == other.text - - def __hash__(self): - return hash((self.start, self.end, self.text)) - - -class SimpleCompletion: - """Completion item to be included in the dictionary returned by new-style Matcher (API v2). - - .. warning:: - - Provisional - - This class is used to describe the currently supported attributes of - simple completion items, and any additional implementation details - should not be relied on. Additional attributes may be included in - future versions, and meaning of text disambiguated from the current - dual meaning of "text to insert" and "text to used as a label". - """ - - __slots__ = ["text", "type"] - - def __init__(self, text: str, *, type: Optional[str] = None): - self.text = text - self.type = type - - def __repr__(self): - return f"<SimpleCompletion text={self.text!r} type={self.type!r}>" - - -class _MatcherResultBase(TypedDict): - """Definition of dictionary to be returned by new-style Matcher (API v2).""" - - #: Suffix of the provided ``CompletionContext.token``, if not given defaults to full token. - matched_fragment: NotRequired[str] - - #: Whether to suppress results from all other matchers (True), some - #: matchers (set of identifiers) or none (False); default is False. - suppress: NotRequired[Union[bool, Set[str]]] - - #: Identifiers of matchers which should NOT be suppressed when this matcher - #: requests to suppress all other matchers; defaults to an empty set. - do_not_suppress: NotRequired[Set[str]] - - #: Are completions already ordered and should be left as-is? default is False. - ordered: NotRequired[bool] - - -@sphinx_options(show_inherited_members=True, exclude_inherited_from=["dict"]) -class SimpleMatcherResult(_MatcherResultBase, TypedDict): - """Result of new-style completion matcher.""" - - # note: TypedDict is added again to the inheritance chain - # in order to get __orig_bases__ for documentation - - #: List of candidate completions - completions: Sequence[SimpleCompletion] | Iterator[SimpleCompletion] - - -class _JediMatcherResult(_MatcherResultBase): - """Matching result returned by Jedi (will be processed differently)""" - - #: list of candidate completions - completions: Iterator[_JediCompletionLike] - - -AnyMatcherCompletion = Union[_JediCompletionLike, SimpleCompletion] -AnyCompletion = TypeVar("AnyCompletion", AnyMatcherCompletion, Completion) - - -@dataclass -class CompletionContext: - """Completion context provided as an argument to matchers in the Matcher API v2.""" - - # rationale: many legacy matchers relied on completer state (`self.text_until_cursor`) - # which was not explicitly visible as an argument of the matcher, making any refactor - # prone to errors; by explicitly passing `cursor_position` we can decouple the matchers - # from the completer, and make substituting them in sub-classes easier. - - #: Relevant fragment of code directly preceding the cursor. - #: The extraction of token is implemented via splitter heuristic - #: (following readline behaviour for legacy reasons), which is user configurable - #: (by switching the greedy mode). - token: str - - #: The full available content of the editor or buffer - full_text: str - - #: Cursor position in the line (the same for ``full_text`` and ``text``). - cursor_position: int - - #: Cursor line in ``full_text``. - cursor_line: int - - #: The maximum number of completions that will be used downstream. - #: Matchers can use this information to abort early. - #: The built-in Jedi matcher is currently excepted from this limit. - # If not given, return all possible completions. - limit: Optional[int] - - @cached_property - def text_until_cursor(self) -> str: - return self.line_with_cursor[: self.cursor_position] - - @cached_property - def line_with_cursor(self) -> str: - return self.full_text.split("\n")[self.cursor_line] - - -#: Matcher results for API v2. -MatcherResult = Union[SimpleMatcherResult, _JediMatcherResult] - - -class _MatcherAPIv1Base(Protocol): - def __call__(self, text: str) -> List[str]: - """Call signature.""" - ... - - #: Used to construct the default matcher identifier - __qualname__: str - - -class _MatcherAPIv1Total(_MatcherAPIv1Base, Protocol): - #: API version - matcher_api_version: Optional[Literal[1]] - - def __call__(self, text: str) -> List[str]: - """Call signature.""" - ... - - -#: Protocol describing Matcher API v1. -MatcherAPIv1: TypeAlias = Union[_MatcherAPIv1Base, _MatcherAPIv1Total] - - -class MatcherAPIv2(Protocol): - """Protocol describing Matcher API v2.""" - - #: API version - matcher_api_version: Literal[2] = 2 - - def __call__(self, context: CompletionContext) -> MatcherResult: - """Call signature.""" - ... - - #: Used to construct the default matcher identifier - __qualname__: str - - -Matcher: TypeAlias = Union[MatcherAPIv1, MatcherAPIv2] - - -def _is_matcher_v1(matcher: Matcher) -> TypeGuard[MatcherAPIv1]: - api_version = _get_matcher_api_version(matcher) - return api_version == 1 - - -def _is_matcher_v2(matcher: Matcher) -> TypeGuard[MatcherAPIv2]: - api_version = _get_matcher_api_version(matcher) - return api_version == 2 - - -def _is_sizable(value: Any) -> TypeGuard[Sized]: - """Determines whether objects is sizable""" - return hasattr(value, "__len__") - - -def _is_iterator(value: Any) -> TypeGuard[Iterator]: - """Determines whether objects is sizable""" - return hasattr(value, "__next__") - - -def has_any_completions(result: MatcherResult) -> bool: - """Check if any result includes any completions.""" - completions = result["completions"] - if _is_sizable(completions): - return len(completions) != 0 - if _is_iterator(completions): - try: - old_iterator = completions - first = next(old_iterator) - result["completions"] = cast( - Iterator[SimpleCompletion], - itertools.chain([first], old_iterator), - ) - return True - except StopIteration: - return False - raise ValueError( - "Completions returned by matcher need to be an Iterator or a Sizable" - ) - - -def completion_matcher( - *, - priority: Optional[float] = None, - identifier: Optional[str] = None, - api_version: int = 1, -) -> Callable[[Matcher], Matcher]: - """Adds attributes describing the matcher. - - Parameters - ---------- - priority : Optional[float] - The priority of the matcher, determines the order of execution of matchers. - Higher priority means that the matcher will be executed first. Defaults to 0. - identifier : Optional[str] - identifier of the matcher allowing users to modify the behaviour via traitlets, - and also used to for debugging (will be passed as ``origin`` with the completions). - - Defaults to matcher function's ``__qualname__`` (for example, - ``IPCompleter.file_matcher`` for the built-in matched defined - as a ``file_matcher`` method of the ``IPCompleter`` class). - api_version: Optional[int] - version of the Matcher API used by this matcher. - Currently supported values are 1 and 2. - Defaults to 1. - """ - - def wrapper(func: Matcher): - func.matcher_priority = priority or 0 # type: ignore - func.matcher_identifier = identifier or func.__qualname__ # type: ignore - func.matcher_api_version = api_version # type: ignore - if TYPE_CHECKING: - if api_version == 1: - func = cast(MatcherAPIv1, func) - elif api_version == 2: - func = cast(MatcherAPIv2, func) - return func - - return wrapper - - -def _get_matcher_priority(matcher: Matcher): - return getattr(matcher, "matcher_priority", 0) - - -def _get_matcher_id(matcher: Matcher): - return getattr(matcher, "matcher_identifier", matcher.__qualname__) - - -def _get_matcher_api_version(matcher): - return getattr(matcher, "matcher_api_version", 1) - - -context_matcher = partial(completion_matcher, api_version=2) - - -_IC = Iterable[Completion] - - -def _deduplicate_completions(text: str, completions: _IC)-> _IC: - """ - Deduplicate a set of completions. - - .. warning:: - - Unstable - - This function is unstable, API may change without warning. - - Parameters - ---------- - text : str - text that should be completed. - completions : Iterator[Completion] - iterator over the completions to deduplicate - - Yields - ------ - `Completions` objects - Completions coming from multiple sources, may be different but end up having - the same effect when applied to ``text``. If this is the case, this will - consider completions as equal and only emit the first encountered. - Not folded in `completions()` yet for debugging purpose, and to detect when - the IPython completer does return things that Jedi does not, but should be - at some point. - """ - completions = list(completions) - if not completions: - return - - new_start = min(c.start for c in completions) - new_end = max(c.end for c in completions) - - seen = set() - for c in completions: - new_text = text[new_start:c.start] + c.text + text[c.end:new_end] - if new_text not in seen: - yield c - seen.add(new_text) - - -def rectify_completions(text: str, completions: _IC, *, _debug: bool = False) -> _IC: - """ - Rectify a set of completions to all have the same ``start`` and ``end`` - - .. warning:: - - Unstable - - This function is unstable, API may change without warning. - It will also raise unless use in proper context manager. - - Parameters - ---------- - text : str - text that should be completed. - completions : Iterator[Completion] - iterator over the completions to rectify - _debug : bool - Log failed completion - - Notes - ----- - :any:`jedi.api.classes.Completion` s returned by Jedi may not have the same start and end, though - the Jupyter Protocol requires them to behave like so. This will readjust - the completion to have the same ``start`` and ``end`` by padding both - extremities with surrounding text. - - During stabilisation should support a ``_debug`` option to log which - completion are return by the IPython completer and not found in Jedi in - order to make upstream bug report. - """ - warnings.warn("`rectify_completions` is a provisional API (as of IPython 6.0). " - "It may change without warnings. " - "Use in corresponding context manager.", - category=ProvisionalCompleterWarning, stacklevel=2) - - completions = list(completions) - if not completions: - return - starts = (c.start for c in completions) - ends = (c.end for c in completions) - - new_start = min(starts) - new_end = max(ends) - - seen_jedi = set() - seen_python_matches = set() - for c in completions: - new_text = text[new_start:c.start] + c.text + text[c.end:new_end] - if c._origin == 'jedi': - seen_jedi.add(new_text) - elif c._origin == "IPCompleter.python_matcher": - seen_python_matches.add(new_text) - yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) - diff = seen_python_matches.difference(seen_jedi) - if diff and _debug: - print('IPython.python matches have extras:', diff) - - -if sys.platform == 'win32': - DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?' -else: - DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' - -GREEDY_DELIMS = ' =\r\n' - - -class CompletionSplitter(object): - """An object to split an input line in a manner similar to readline. - - By having our own implementation, we can expose readline-like completion in - a uniform manner to all frontends. This object only needs to be given the - line of text to be split and the cursor position on said line, and it - returns the 'word' to be completed on at the cursor after splitting the - entire line. - - What characters are used as splitting delimiters can be controlled by - setting the ``delims`` attribute (this is a property that internally - automatically builds the necessary regular expression)""" - - # Private interface - - # A string of delimiter characters. The default value makes sense for - # IPython's most typical usage patterns. - _delims = DELIMS - - # The expression (a normal string) to be compiled into a regular expression - # for actual splitting. We store it as an attribute mostly for ease of - # debugging, since this type of code can be so tricky to debug. - _delim_expr = None - - # The regular expression that does the actual splitting - _delim_re = None - - def __init__(self, delims=None): - delims = CompletionSplitter._delims if delims is None else delims - self.delims = delims - - @property - def delims(self): - """Return the string of delimiter characters.""" - return self._delims - - @delims.setter - def delims(self, delims): - """Set the delimiters for line splitting.""" - expr = '[' + ''.join('\\'+ c for c in delims) + ']' - self._delim_re = re.compile(expr) - self._delims = delims - self._delim_expr = expr - - def split_line(self, line, cursor_pos=None): - """Split a line of text with a cursor at the given position. - """ - cut_line = line if cursor_pos is None else line[:cursor_pos] - return self._delim_re.split(cut_line)[-1] - - - -class Completer(Configurable): - - greedy = Bool( - False, - help="""Activate greedy completion. - - .. deprecated:: 8.8 - Use :std:configtrait:`Completer.evaluation` and :std:configtrait:`Completer.auto_close_dict_keys` instead. - - When enabled in IPython 8.8 or newer, changes configuration as follows: - - - ``Completer.evaluation = 'unsafe'`` - - ``Completer.auto_close_dict_keys = True`` - """, - ).tag(config=True) - - evaluation = Enum( - ("forbidden", "minimal", "limited", "unsafe", "dangerous"), - default_value="limited", - help="""Policy for code evaluation under completion. - - Successive options allow to enable more eager evaluation for better - completion suggestions, including for nested dictionaries, nested lists, - or even results of function calls. - Setting ``unsafe`` or higher can lead to evaluation of arbitrary user - code on :kbd:`Tab` with potentially unwanted or dangerous side effects. - - Allowed values are: - - - ``forbidden``: no evaluation of code is permitted, - - ``minimal``: evaluation of literals and access to built-in namespace; - no item/attribute evaluationm no access to locals/globals, - no evaluation of any operations or comparisons. - - ``limited``: access to all namespaces, evaluation of hard-coded methods - (for example: :any:`dict.keys`, :any:`object.__getattr__`, - :any:`object.__getitem__`) on allow-listed objects (for example: - :any:`dict`, :any:`list`, :any:`tuple`, ``pandas.Series``), - - ``unsafe``: evaluation of all methods and function calls but not of - syntax with side-effects like `del x`, - - ``dangerous``: completely arbitrary evaluation. - """, - ).tag(config=True) - - use_jedi = Bool(default_value=JEDI_INSTALLED, - help="Experimental: Use Jedi to generate autocompletions. " - "Default to True if jedi is installed.").tag(config=True) - - jedi_compute_type_timeout = Int(default_value=400, - help="""Experimental: restrict time (in milliseconds) during which Jedi can compute types. - Set to 0 to stop computing types. Non-zero value lower than 100ms may hurt - performance by preventing jedi to build its cache. - """).tag(config=True) - - debug = Bool(default_value=False, - help='Enable debug for the Completer. Mostly print extra ' - 'information for experimental jedi integration.')\ - .tag(config=True) - - backslash_combining_completions = Bool(True, - help="Enable unicode completions, e.g. \\alpha<tab> . " - "Includes completion of latex commands, unicode names, and expanding " - "unicode characters back to latex commands.").tag(config=True) - - auto_close_dict_keys = Bool( - False, - help=""" - Enable auto-closing dictionary keys. - - When enabled string keys will be suffixed with a final quote - (matching the opening quote), tuple keys will also receive a - separating comma if needed, and keys which are final will - receive a closing bracket (``]``). - """, - ).tag(config=True) - - def __init__(self, namespace=None, global_namespace=None, **kwargs): - """Create a new completer for the command line. - - Completer(namespace=ns, global_namespace=ns2) -> completer instance. - - If unspecified, the default namespace where completions are performed - is __main__ (technically, __main__.__dict__). Namespaces should be - given as dictionaries. - - An optional second namespace can be given. This allows the completer - to handle cases where both the local and global scopes need to be - distinguished. - """ - - # Don't bind to namespace quite yet, but flag whether the user wants a - # specific namespace or to use __main__.__dict__. This will allow us - # to bind to __main__.__dict__ at completion time, not now. - if namespace is None: - self.use_main_ns = True - else: - self.use_main_ns = False - self.namespace = namespace - - # The global namespace, if given, can be bound directly - if global_namespace is None: - self.global_namespace = {} - else: - self.global_namespace = global_namespace - - self.custom_matchers = [] - - super(Completer, self).__init__(**kwargs) - - def complete(self, text, state): - """Return the next possible completion for 'text'. - - This is called successively with state == 0, 1, 2, ... until it - returns None. The completion should begin with 'text'. - - """ - if self.use_main_ns: - self.namespace = __main__.__dict__ - - if state == 0: - if "." in text: - self.matches = self.attr_matches(text) - else: - self.matches = self.global_matches(text) - try: - return self.matches[state] - except IndexError: - return None - - def global_matches(self, text): - """Compute matches when text is a simple name. - - Return a list of all keywords, built-in functions and names currently - defined in self.namespace or self.global_namespace that match. - - """ - matches = [] - match_append = matches.append - n = len(text) - for lst in [ - keyword.kwlist, - builtin_mod.__dict__.keys(), - list(self.namespace.keys()), - list(self.global_namespace.keys()), - ]: - for word in lst: - if word[:n] == text and word != "__builtins__": - match_append(word) - - snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") - for lst in [list(self.namespace.keys()), list(self.global_namespace.keys())]: - shortened = { - "_".join([sub[0] for sub in word.split("_")]): word - for word in lst - if snake_case_re.match(word) - } - for word in shortened.keys(): - if word[:n] == text and word != "__builtins__": - match_append(shortened[word]) - return matches - - def attr_matches(self, text): - """Compute matches when text contains a dot. - - Assuming the text is of the form NAME.NAME....[NAME], and is - evaluatable in self.namespace or self.global_namespace, it will be - evaluated and its attributes (as revealed by dir()) are used as - possible completions. (For class instances, class members are - also considered.) - - WARNING: this can still invoke arbitrary C code, if an object - with a __getattr__ hook is evaluated. - - """ - return self._attr_matches(text)[0] - - # we simple attribute matching with normal identifiers. - _ATTR_MATCH_RE = re.compile(r"(.+)\.(\w*)$") - - def _attr_matches( - self, text: str, include_prefix: bool = True - ) -> Tuple[Sequence[str], str]: - m2 = self._ATTR_MATCH_RE.match(self.line_buffer) - if not m2: - return [], "" - expr, attr = m2.group(1, 2) - - obj = self._evaluate_expr(expr) - - if obj is not_found: - return [], "" - - if self.limit_to__all__ and hasattr(obj, '__all__'): - words = get__all__entries(obj) - else: - words = dir2(obj) - - try: - words = generics.complete_object(obj, words) - except TryNext: - pass - except AssertionError: - raise - except Exception: - # Silence errors from completion function - pass - # Build match list to return - n = len(attr) - - # Note: ideally we would just return words here and the prefix - # reconciliator would know that we intend to append to rather than - # replace the input text; this requires refactoring to return range - # which ought to be replaced (as does jedi). - if include_prefix: - tokens = _parse_tokens(expr) - rev_tokens = reversed(tokens) - skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} - name_turn = True - - parts = [] - for token in rev_tokens: - if token.type in skip_over: - continue - if token.type == tokenize.NAME and name_turn: - parts.append(token.string) - name_turn = False - elif ( - token.type == tokenize.OP and token.string == "." and not name_turn - ): - parts.append(token.string) - name_turn = True - else: - # short-circuit if not empty nor name token - break - - prefix_after_space = "".join(reversed(parts)) - else: - prefix_after_space = "" - - return ( - ["%s.%s" % (prefix_after_space, w) for w in words if w[:n] == attr], - "." + attr, - ) - - def _trim_expr(self, code: str) -> str: - """ - Trim the code until it is a valid expression and not a tuple; - - return the trimmed expression for guarded_eval. - """ - while code: - code = code[1:] - try: - res = ast.parse(code) - except SyntaxError: - continue - - assert res is not None - if len(res.body) != 1: - continue - expr = res.body[0].value - if isinstance(expr, ast.Tuple) and not code[-1] == ")": - # we skip implicit tuple, like when trimming `fun(a,b`<completion> - # as `a,b` would be a tuple, and we actually expect to get only `b` - continue - return code - return "" - - def _evaluate_expr(self, expr): - obj = not_found - done = False - while not done and expr: - try: - obj = guarded_eval( - expr, - EvaluationContext( - globals=self.global_namespace, - locals=self.namespace, - evaluation=self.evaluation, - ), - ) - done = True - except Exception as e: - if self.debug: - print("Evaluation exception", e) - # trim the expression to remove any invalid prefix - # e.g. user starts `(d[`, so we get `expr = '(d'`, - # where parenthesis is not closed. - # TODO: make this faster by reusing parts of the computation? - expr = self._trim_expr(expr) - return obj - -def get__all__entries(obj): - """returns the strings in the __all__ attribute""" - try: - words = getattr(obj, '__all__') - except Exception: - return [] - - return [w for w in words if isinstance(w, str)] - - -class _DictKeyState(enum.Flag): - """Represent state of the key match in context of other possible matches. - - - given `d1 = {'a': 1}` completion on `d1['<tab>` will yield `{'a': END_OF_ITEM}` as there is no tuple. - - given `d2 = {('a', 'b'): 1}`: `d2['a', '<tab>` will yield `{'b': END_OF_TUPLE}` as there is no tuple members to add beyond `'b'`. - - given `d3 = {('a', 'b'): 1}`: `d3['<tab>` will yield `{'a': IN_TUPLE}` as `'a'` can be added. - - given `d4 = {'a': 1, ('a', 'b'): 2}`: `d4['<tab>` will yield `{'a': END_OF_ITEM & END_OF_TUPLE}` - """ - - BASELINE = 0 - END_OF_ITEM = enum.auto() - END_OF_TUPLE = enum.auto() - IN_TUPLE = enum.auto() - - -def _parse_tokens(c): - """Parse tokens even if there is an error.""" - tokens = [] - token_generator = tokenize.generate_tokens(iter(c.splitlines()).__next__) - while True: - try: - tokens.append(next(token_generator)) - except tokenize.TokenError: - return tokens - except StopIteration: - return tokens - - -def _match_number_in_dict_key_prefix(prefix: str) -> Union[str, None]: - """Match any valid Python numeric literal in a prefix of dictionary keys. - - References: - - https://docs.python.org/3/reference/lexical_analysis.html#numeric-literals - - https://docs.python.org/3/library/tokenize.html - """ - if prefix[-1].isspace(): - # if user typed a space we do not have anything to complete - # even if there was a valid number token before - return None - tokens = _parse_tokens(prefix) - rev_tokens = reversed(tokens) - skip_over = {tokenize.ENDMARKER, tokenize.NEWLINE} - number = None - for token in rev_tokens: - if token.type in skip_over: - continue - if number is None: - if token.type == tokenize.NUMBER: - number = token.string - continue - else: - # we did not match a number - return None - if token.type == tokenize.OP: - if token.string == ",": - break - if token.string in {"+", "-"}: - number = token.string + number - else: - return None - return number - - -_INT_FORMATS = { - "0b": bin, - "0o": oct, - "0x": hex, -} - - -def match_dict_keys( - keys: List[Union[str, bytes, Tuple[Union[str, bytes], ...]]], - prefix: str, - delims: str, - extra_prefix: Optional[Tuple[Union[str, bytes], ...]] = None, -) -> Tuple[str, int, Dict[str, _DictKeyState]]: - """Used by dict_key_matches, matching the prefix to a list of keys - - Parameters - ---------- - keys - list of keys in dictionary currently being completed. - prefix - Part of the text already typed by the user. E.g. `mydict[b'fo` - delims - String of delimiters to consider when finding the current key. - extra_prefix : optional - Part of the text already typed in multi-key index cases. E.g. for - `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. - - Returns - ------- - A tuple of three elements: ``quote``, ``token_start``, ``matched``, with - ``quote`` being the quote that need to be used to close current string. - ``token_start`` the position where the replacement should start occurring, - ``matches`` a dictionary of replacement/completion keys on keys and values - indicating whether the state. - """ - prefix_tuple = extra_prefix if extra_prefix else () - - prefix_tuple_size = sum( - [ - # for pandas, do not count slices as taking space - not isinstance(k, slice) - for k in prefix_tuple - ] - ) - text_serializable_types = (str, bytes, int, float, slice) - - def filter_prefix_tuple(key): - # Reject too short keys - if len(key) <= prefix_tuple_size: - return False - # Reject keys which cannot be serialised to text - for k in key: - if not isinstance(k, text_serializable_types): - return False - # Reject keys that do not match the prefix - for k, pt in zip(key, prefix_tuple): - if k != pt and not isinstance(pt, slice): - return False - # All checks passed! - return True - - filtered_key_is_final: Dict[Union[str, bytes, int, float], _DictKeyState] = ( - defaultdict(lambda: _DictKeyState.BASELINE) - ) - - for k in keys: - # If at least one of the matches is not final, mark as undetermined. - # This can happen with `d = {111: 'b', (111, 222): 'a'}` where - # `111` appears final on first match but is not final on the second. - - if isinstance(k, tuple): - if filter_prefix_tuple(k): - key_fragment = k[prefix_tuple_size] - filtered_key_is_final[key_fragment] |= ( - _DictKeyState.END_OF_TUPLE - if len(k) == prefix_tuple_size + 1 - else _DictKeyState.IN_TUPLE - ) - elif prefix_tuple_size > 0: - # we are completing a tuple but this key is not a tuple, - # so we should ignore it - pass - else: - if isinstance(k, text_serializable_types): - filtered_key_is_final[k] |= _DictKeyState.END_OF_ITEM - - filtered_keys = filtered_key_is_final.keys() - - if not prefix: - return "", 0, {repr(k): v for k, v in filtered_key_is_final.items()} - - quote_match = re.search("(?:\"|')", prefix) - is_user_prefix_numeric = False - - if quote_match: - quote = quote_match.group() - valid_prefix = prefix + quote - try: - prefix_str = literal_eval(valid_prefix) - except Exception: - return "", 0, {} - else: - # If it does not look like a string, let's assume - # we are dealing with a number or variable. - number_match = _match_number_in_dict_key_prefix(prefix) - - # We do not want the key matcher to suggest variable names so we yield: - if number_match is None: - # The alternative would be to assume that user forgort the quote - # and if the substring matches, suggest adding it at the start. - return "", 0, {} - - prefix_str = number_match - is_user_prefix_numeric = True - quote = "" - - pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' - token_match = re.search(pattern, prefix, re.UNICODE) - assert token_match is not None # silence mypy - token_start = token_match.start() - token_prefix = token_match.group() - - matched: Dict[str, _DictKeyState] = {} - - str_key: Union[str, bytes] - - for key in filtered_keys: - if isinstance(key, (int, float)): - # User typed a number but this key is not a number. - if not is_user_prefix_numeric: - continue - str_key = str(key) - if isinstance(key, int): - int_base = prefix_str[:2].lower() - # if user typed integer using binary/oct/hex notation: - if int_base in _INT_FORMATS: - int_format = _INT_FORMATS[int_base] - str_key = int_format(key) - else: - # User typed a string but this key is a number. - if is_user_prefix_numeric: - continue - str_key = key - try: - if not str_key.startswith(prefix_str): - continue - except (AttributeError, TypeError, UnicodeError): - # Python 3+ TypeError on b'a'.startswith('a') or vice-versa - continue - - # reformat remainder of key to begin with prefix - rem = str_key[len(prefix_str) :] - # force repr wrapped in ' - rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') - rem_repr = rem_repr[1 + rem_repr.index("'"):-2] - if quote == '"': - # The entered prefix is quoted with ", - # but the match is quoted with '. - # A contained " hence needs escaping for comparison: - rem_repr = rem_repr.replace('"', '\\"') - - # then reinsert prefix from start of token - match = "%s%s" % (token_prefix, rem_repr) - - matched[match] = filtered_key_is_final[key] - return quote, token_start, matched - - -def cursor_to_position(text:str, line:int, column:int)->int: - """ - Convert the (line,column) position of the cursor in text to an offset in a - string. - - Parameters - ---------- - text : str - The text in which to calculate the cursor offset - line : int - Line of the cursor; 0-indexed - column : int - Column of the cursor 0-indexed - - Returns - ------- - Position of the cursor in ``text``, 0-indexed. - - See Also - -------- - position_to_cursor : reciprocal of this function - - """ - lines = text.split('\n') - assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) - - return sum(len(line) + 1 for line in lines[:line]) + column - -def position_to_cursor(text:str, offset:int)->Tuple[int, int]: - """ - Convert the position of the cursor in text (0 indexed) to a line - number(0-indexed) and a column number (0-indexed) pair - - Position should be a valid position in ``text``. - - Parameters - ---------- - text : str - The text in which to calculate the cursor offset - offset : int - Position of the cursor in ``text``, 0-indexed. - - Returns - ------- - (line, column) : (int, int) - Line of the cursor; 0-indexed, column of the cursor 0-indexed - - See Also - -------- - cursor_to_position : reciprocal of this function - - """ - - assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) - - before = text[:offset] - blines = before.split('\n') # ! splitnes trim trailing \n - line = before.count('\n') - col = len(blines[-1]) - return line, col - - -def _safe_isinstance(obj, module, class_name, *attrs): - """Checks if obj is an instance of module.class_name if loaded - """ - if module in sys.modules: - m = sys.modules[module] - for attr in [class_name, *attrs]: - m = getattr(m, attr) - return isinstance(obj, m) - - -@context_matcher() -def back_unicode_name_matcher(context: CompletionContext): - """Match Unicode characters back to Unicode name - - Same as :any:`back_unicode_name_matches`, but adopted to new Matcher API. - """ - fragment, matches = back_unicode_name_matches(context.text_until_cursor) - return _convert_matcher_v1_result_to_v2( - matches, type="unicode", fragment=fragment, suppress_if_matches=True - ) - - -def back_unicode_name_matches(text: str) -> Tuple[str, Sequence[str]]: - """Match Unicode characters back to Unicode name - - This does ``☃`` -> ``\\snowman`` - - Note that snowman is not a valid python3 combining character but will be expanded. - Though it will not recombine back to the snowman character by the completion machinery. - - This will not either back-complete standard sequences like \\n, \\b ... - - .. deprecated:: 8.6 - You can use :meth:`back_unicode_name_matcher` instead. - - Returns - ======= - - Return a tuple with two elements: - - - The Unicode character that was matched (preceded with a backslash), or - empty string, - - a sequence (of 1), name for the match Unicode character, preceded by - backslash, or empty if no match. - """ - if len(text)<2: - return '', () - maybe_slash = text[-2] - if maybe_slash != '\\': - return '', () - - char = text[-1] - # no expand on quote for completion in strings. - # nor backcomplete standard ascii keys - if char in string.ascii_letters or char in ('"',"'"): - return '', () - try : - unic = unicodedata.name(char) - return '\\'+char,('\\'+unic,) - except KeyError: - pass - return '', () - - -@context_matcher() -def back_latex_name_matcher(context: CompletionContext): - """Match latex characters back to unicode name - - Same as :any:`back_latex_name_matches`, but adopted to new Matcher API. - """ - fragment, matches = back_latex_name_matches(context.text_until_cursor) - return _convert_matcher_v1_result_to_v2( - matches, type="latex", fragment=fragment, suppress_if_matches=True - ) - - -def back_latex_name_matches(text: str) -> Tuple[str, Sequence[str]]: - """Match latex characters back to unicode name - - This does ``\\ℵ`` -> ``\\aleph`` - - .. deprecated:: 8.6 - You can use :meth:`back_latex_name_matcher` instead. - """ - if len(text)<2: - return '', () - maybe_slash = text[-2] - if maybe_slash != '\\': - return '', () - - - char = text[-1] - # no expand on quote for completion in strings. - # nor backcomplete standard ascii keys - if char in string.ascii_letters or char in ('"',"'"): - return '', () - try : - latex = reverse_latex_symbol[char] - # '\\' replace the \ as well - return '\\'+char,[latex] - except KeyError: - pass - return '', () - - -def _formatparamchildren(parameter) -> str: - """ - Get parameter name and value from Jedi Private API - - Jedi does not expose a simple way to get `param=value` from its API. - - Parameters - ---------- - parameter - Jedi's function `Param` - - Returns - ------- - A string like 'a', 'b=1', '*args', '**kwargs' - - """ - description = parameter.description - if not description.startswith('param '): - raise ValueError('Jedi function parameter description have change format.' - 'Expected "param ...", found %r".' % description) - return description[6:] - -def _make_signature(completion)-> str: - """ - Make the signature from a jedi completion - - Parameters - ---------- - completion : jedi.Completion - object does not complete a function type - - Returns - ------- - a string consisting of the function signature, with the parenthesis but - without the function name. example: - `(a, *args, b=1, **kwargs)` - - """ - - # it looks like this might work on jedi 0.17 - if hasattr(completion, 'get_signatures'): - signatures = completion.get_signatures() - if not signatures: - return '(?)' - - c0 = completion.get_signatures()[0] - return '('+c0.to_string().split('(', maxsplit=1)[1] - - return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() - for p in signature.defined_names()) if f]) - - -_CompleteResult = Dict[str, MatcherResult] - - -DICT_MATCHER_REGEX = re.compile( - r"""(?x) -( # match dict-referring - or any get item object - expression - .+ -) -\[ # open bracket -\s* # and optional whitespace -# Capture any number of serializable objects (e.g. "a", "b", 'c') -# and slices -((?:(?: - (?: # closed string - [uUbB]? # string prefix (r not handled) - (?: - '(?:[^']|(?<!\\)\\')*' - | - "(?:[^"]|(?<!\\)\\")*" - ) - ) - | - # capture integers and slices - (?:[-+]?\d+)?(?::(?:[-+]?\d+)?){0,2} - | - # integer in bin/hex/oct notation - 0[bBxXoO]_?(?:\w|\d)+ - ) - \s*,\s* -)*) -((?: - (?: # unclosed string - [uUbB]? # string prefix (r not handled) - (?: - '(?:[^']|(?<!\\)\\')* - | - "(?:[^"]|(?<!\\)\\")* - ) - ) - | - # unfinished integer - (?:[-+]?\d+) - | - # integer in bin/hex/oct notation - 0[bBxXoO]_?(?:\w|\d)+ - ) -)? -$ -""" -) - - -def _convert_matcher_v1_result_to_v2( - matches: Sequence[str], - type: str, - fragment: Optional[str] = None, - suppress_if_matches: bool = False, -) -> SimpleMatcherResult: - """Utility to help with transition""" - result = { - "completions": [SimpleCompletion(text=match, type=type) for match in matches], - "suppress": (True if matches else False) if suppress_if_matches else False, - } - if fragment is not None: - result["matched_fragment"] = fragment - return cast(SimpleMatcherResult, result) - - -class IPCompleter(Completer): - """Extension of the completer class with IPython-specific features""" - - @observe('greedy') - def _greedy_changed(self, change): - """update the splitter and readline delims when greedy is changed""" - if change["new"]: - self.evaluation = "unsafe" - self.auto_close_dict_keys = True - self.splitter.delims = GREEDY_DELIMS - else: - self.evaluation = "limited" - self.auto_close_dict_keys = False - self.splitter.delims = DELIMS - - dict_keys_only = Bool( - False, - help=""" - Whether to show dict key matches only. - - (disables all matchers except for `IPCompleter.dict_key_matcher`). - """, - ) - - suppress_competing_matchers = UnionTrait( - [Bool(allow_none=True), DictTrait(Bool(None, allow_none=True))], - default_value=None, - help=""" - Whether to suppress completions from other *Matchers*. - - When set to ``None`` (default) the matchers will attempt to auto-detect - whether suppression of other matchers is desirable. For example, at - the beginning of a line followed by `%` we expect a magic completion - to be the only applicable option, and after ``my_dict['`` we usually - expect a completion with an existing dictionary key. - - If you want to disable this heuristic and see completions from all matchers, - set ``IPCompleter.suppress_competing_matchers = False``. - To disable the heuristic for specific matchers provide a dictionary mapping: - ``IPCompleter.suppress_competing_matchers = {'IPCompleter.dict_key_matcher': False}``. - - Set ``IPCompleter.suppress_competing_matchers = True`` to limit - completions to the set of matchers with the highest priority; - this is equivalent to ``IPCompleter.merge_completions`` and - can be beneficial for performance, but will sometimes omit relevant - candidates from matchers further down the priority list. - """, - ).tag(config=True) - - merge_completions = Bool( - True, - help="""Whether to merge completion results into a single list - - If False, only the completion results from the first non-empty - completer will be returned. - - As of version 8.6.0, setting the value to ``False`` is an alias for: - ``IPCompleter.suppress_competing_matchers = True.``. - """, - ).tag(config=True) - - disable_matchers = ListTrait( - Unicode(), - help="""List of matchers to disable. - - The list should contain matcher identifiers (see :any:`completion_matcher`). - """, - ).tag(config=True) - - omit__names = Enum( - (0, 1, 2), - default_value=2, - help="""Instruct the completer to omit private method names - - Specifically, when completing on ``object.<tab>``. - - When 2 [default]: all names that start with '_' will be excluded. - - When 1: all 'magic' names (``__foo__``) will be excluded. - - When 0: nothing will be excluded. - """ - ).tag(config=True) - limit_to__all__ = Bool(False, - help=""" - DEPRECATED as of version 5.0. - - Instruct the completer to use __all__ for the completion - - Specifically, when completing on ``object.<tab>``. - - When True: only those names in obj.__all__ will be included. - - When False [default]: the __all__ attribute is ignored - """, - ).tag(config=True) - - profile_completions = Bool( - default_value=False, - help="If True, emit profiling data for completion subsystem using cProfile." - ).tag(config=True) - - profiler_output_dir = Unicode( - default_value=".completion_profiles", - help="Template for path at which to output profile data for completions." - ).tag(config=True) - - @observe('limit_to__all__') - def _limit_to_all_changed(self, change): - warnings.warn('`IPython.core.IPCompleter.limit_to__all__` configuration ' - 'value has been deprecated since IPython 5.0, will be made to have ' - 'no effects and then removed in future version of IPython.', - UserWarning) - - def __init__( - self, shell=None, namespace=None, global_namespace=None, config=None, **kwargs - ): - """IPCompleter() -> completer - - Return a completer object. - - Parameters - ---------- - shell - a pointer to the ipython shell itself. This is needed - because this completer knows about magic functions, and those can - only be accessed via the ipython instance. - namespace : dict, optional - an optional dict where completions are performed. - global_namespace : dict, optional - secondary optional dict for completions, to - handle cases (such as IPython embedded inside functions) where - both Python scopes are visible. - config : Config - traitlet's config object - **kwargs - passed to super class unmodified. - """ - - self.magic_escape = ESC_MAGIC - self.splitter = CompletionSplitter() - - # _greedy_changed() depends on splitter and readline being defined: - super().__init__( - namespace=namespace, - global_namespace=global_namespace, - config=config, - **kwargs, - ) - - # List where completion matches will be stored - self.matches = [] - self.shell = shell - # Regexp to split filenames with spaces in them - self.space_name_re = re.compile(r'([^\\] )') - # Hold a local ref. to glob.glob for speed - self.glob = glob.glob - - # Determine if we are running on 'dumb' terminals, like (X)Emacs - # buffers, to avoid completion problems. - term = os.environ.get('TERM','xterm') - self.dumb_terminal = term in ['dumb','emacs'] - - # Special handling of backslashes needed in win32 platforms - if sys.platform == "win32": - self.clean_glob = self._clean_glob_win32 - else: - self.clean_glob = self._clean_glob - - #regexp to parse docstring for function signature - self.docstring_sig_re = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') - self.docstring_kwd_re = re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') - #use this if positional argument name is also needed - #= re.compile(r'[\s|\[]*(\w+)(?:\s*=?\s*.*)') - - self.magic_arg_matchers = [ - self.magic_config_matcher, - self.magic_color_matcher, - ] - - # This is set externally by InteractiveShell - self.custom_completers = None - - # This is a list of names of unicode characters that can be completed - # into their corresponding unicode value. The list is large, so we - # lazily initialize it on first use. Consuming code should access this - # attribute through the `@unicode_names` property. - self._unicode_names = None - - self._backslash_combining_matchers = [ - self.latex_name_matcher, - self.unicode_name_matcher, - back_latex_name_matcher, - back_unicode_name_matcher, - self.fwd_unicode_matcher, - ] - - if not self.backslash_combining_completions: - for matcher in self._backslash_combining_matchers: - self.disable_matchers.append(_get_matcher_id(matcher)) - - if not self.merge_completions: - self.suppress_competing_matchers = True - - @property - def matchers(self) -> List[Matcher]: - """All active matcher routines for completion""" - if self.dict_keys_only: - return [self.dict_key_matcher] - - if self.use_jedi: - return [ - *self.custom_matchers, - *self._backslash_combining_matchers, - *self.magic_arg_matchers, - self.custom_completer_matcher, - self.magic_matcher, - self._jedi_matcher, - self.dict_key_matcher, - self.file_matcher, - ] - else: - return [ - *self.custom_matchers, - *self._backslash_combining_matchers, - *self.magic_arg_matchers, - self.custom_completer_matcher, - self.dict_key_matcher, - self.magic_matcher, - self.python_matcher, - self.file_matcher, - self.python_func_kw_matcher, - ] - - def all_completions(self, text:str) -> List[str]: - """ - Wrapper around the completion methods for the benefit of emacs. - """ - prefix = text.rpartition('.')[0] - with provisionalcompleter(): - return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text - for c in self.completions(text, len(text))] - - return self.complete(text)[1] - - def _clean_glob(self, text:str): - return self.glob("%s*" % text) - - def _clean_glob_win32(self, text:str): - return [f.replace("\\","/") - for f in self.glob("%s*" % text)] - - @context_matcher() - def file_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Same as :any:`file_matches`, but adopted to new Matcher API.""" - matches = self.file_matches(context.token) - # TODO: add a heuristic for suppressing (e.g. if it has OS-specific delimiter, - # starts with `/home/`, `C:\`, etc) - return _convert_matcher_v1_result_to_v2(matches, type="path") - - def file_matches(self, text: str) -> List[str]: - """Match filenames, expanding ~USER type strings. - - Most of the seemingly convoluted logic in this completer is an - attempt to handle filenames with spaces in them. And yet it's not - quite perfect, because Python's readline doesn't expose all of the - GNU readline details needed for this to be done correctly. - - For a filename with a space in it, the printed completions will be - only the parts after what's already been typed (instead of the - full completions, as is normally done). I don't think with the - current (as of Python 2.3) Python readline it's possible to do - better. - - .. deprecated:: 8.6 - You can use :meth:`file_matcher` instead. - """ - - # chars that require escaping with backslash - i.e. chars - # that readline treats incorrectly as delimiters, but we - # don't want to treat as delimiters in filename matching - # when escaped with backslash - if text.startswith('!'): - text = text[1:] - text_prefix = u'!' - else: - text_prefix = u'' - - text_until_cursor = self.text_until_cursor - # track strings with open quotes - open_quotes = has_open_quotes(text_until_cursor) - - if '(' in text_until_cursor or '[' in text_until_cursor: - lsplit = text - else: - try: - # arg_split ~ shlex.split, but with unicode bugs fixed by us - lsplit = arg_split(text_until_cursor)[-1] - except ValueError: - # typically an unmatched ", or backslash without escaped char. - if open_quotes: - lsplit = text_until_cursor.split(open_quotes)[-1] - else: - return [] - except IndexError: - # tab pressed on empty line - lsplit = "" - - if not open_quotes and lsplit != protect_filename(lsplit): - # if protectables are found, do matching on the whole escaped name - has_protectables = True - text0,text = text,lsplit - else: - has_protectables = False - text = os.path.expanduser(text) - - if text == "": - return [text_prefix + protect_filename(f) for f in self.glob("*")] - - # Compute the matches from the filesystem - if sys.platform == 'win32': - m0 = self.clean_glob(text) - else: - m0 = self.clean_glob(text.replace('\\', '')) - - if has_protectables: - # If we had protectables, we need to revert our changes to the - # beginning of filename so that we don't double-write the part - # of the filename we have so far - len_lsplit = len(lsplit) - matches = [text_prefix + text0 + - protect_filename(f[len_lsplit:]) for f in m0] - else: - if open_quotes: - # if we have a string with an open quote, we don't need to - # protect the names beyond the quote (and we _shouldn't_, as - # it would cause bugs when the filesystem call is made). - matches = m0 if sys.platform == "win32" else\ - [protect_filename(f, open_quotes) for f in m0] - else: - matches = [text_prefix + - protect_filename(f) for f in m0] - - # Mark directories in input list by appending '/' to their names. - return [x+'/' if os.path.isdir(x) else x for x in matches] - - @context_matcher() - def magic_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Match magics.""" - text = context.token - matches = self.magic_matches(text) - result = _convert_matcher_v1_result_to_v2(matches, type="magic") - is_magic_prefix = len(text) > 0 and text[0] == "%" - result["suppress"] = is_magic_prefix and bool(result["completions"]) - return result - - def magic_matches(self, text: str) -> List[str]: - """Match magics. - - .. deprecated:: 8.6 - You can use :meth:`magic_matcher` instead. - """ - # Get all shell magics now rather than statically, so magics loaded at - # runtime show up too. - lsm = self.shell.magics_manager.lsmagic() - line_magics = lsm['line'] - cell_magics = lsm['cell'] - pre = self.magic_escape - pre2 = pre+pre - - explicit_magic = text.startswith(pre) - - # Completion logic: - # - user gives %%: only do cell magics - # - user gives %: do both line and cell magics - # - no prefix: do both - # In other words, line magics are skipped if the user gives %% explicitly - # - # We also exclude magics that match any currently visible names: - # https://github.com/ipython/ipython/issues/4877, unless the user has - # typed a %: - # https://github.com/ipython/ipython/issues/10754 - bare_text = text.lstrip(pre) - global_matches = self.global_matches(bare_text) - if not explicit_magic: - def matches(magic): - """ - Filter magics, in particular remove magics that match - a name present in global namespace. - """ - return ( magic.startswith(bare_text) and - magic not in global_matches ) - else: - def matches(magic): - return magic.startswith(bare_text) - - comp = [ pre2+m for m in cell_magics if matches(m)] - if not text.startswith(pre2): - comp += [ pre+m for m in line_magics if matches(m)] - - return comp - - @context_matcher() - def magic_config_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Match class names and attributes for %config magic.""" - # NOTE: uses `line_buffer` equivalent for compatibility - matches = self.magic_config_matches(context.line_with_cursor) - return _convert_matcher_v1_result_to_v2(matches, type="param") - - def magic_config_matches(self, text: str) -> List[str]: - """Match class names and attributes for %config magic. - - .. deprecated:: 8.6 - You can use :meth:`magic_config_matcher` instead. - """ - texts = text.strip().split() - - if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): - # get all configuration classes - classes = sorted(set([ c for c in self.shell.configurables - if c.__class__.class_traits(config=True) - ]), key=lambda x: x.__class__.__name__) - classnames = [ c.__class__.__name__ for c in classes ] - - # return all classnames if config or %config is given - if len(texts) == 1: - return classnames - - # match classname - classname_texts = texts[1].split('.') - classname = classname_texts[0] - classname_matches = [ c for c in classnames - if c.startswith(classname) ] - - # return matched classes or the matched class with attributes - if texts[1].find('.') < 0: - return classname_matches - elif len(classname_matches) == 1 and \ - classname_matches[0] == classname: - cls = classes[classnames.index(classname)].__class__ - help = cls.class_get_help() - # strip leading '--' from cl-args: - help = re.sub(re.compile(r'^--', re.MULTILINE), '', help) - return [ attr.split('=')[0] - for attr in help.strip().splitlines() - if attr.startswith(texts[1]) ] - return [] - - @context_matcher() - def magic_color_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Match color schemes for %colors magic.""" - # NOTE: uses `line_buffer` equivalent for compatibility - matches = self.magic_color_matches(context.line_with_cursor) - return _convert_matcher_v1_result_to_v2(matches, type="param") - - def magic_color_matches(self, text: str) -> List[str]: - """Match color schemes for %colors magic. - - .. deprecated:: 8.6 - You can use :meth:`magic_color_matcher` instead. - """ - texts = text.split() - if text.endswith(' '): - # .split() strips off the trailing whitespace. Add '' back - # so that: '%colors ' -> ['%colors', ''] - texts.append('') - - if len(texts) == 2 and (texts[0] == 'colors' or texts[0] == '%colors'): - prefix = texts[1] - return [ color for color in InspectColors.keys() - if color.startswith(prefix) ] - return [] - - @context_matcher(identifier="IPCompleter.jedi_matcher") - def _jedi_matcher(self, context: CompletionContext) -> _JediMatcherResult: - matches = self._jedi_matches( - cursor_column=context.cursor_position, - cursor_line=context.cursor_line, - text=context.full_text, - ) - return { - "completions": matches, - # static analysis should not suppress other matchers - "suppress": False, - } - - def _jedi_matches( - self, cursor_column: int, cursor_line: int, text: str - ) -> Iterator[_JediCompletionLike]: - """ - Return a list of :any:`jedi.api.Completion`\\s object from a ``text`` and - cursor position. - - Parameters - ---------- - cursor_column : int - column position of the cursor in ``text``, 0-indexed. - cursor_line : int - line position of the cursor in ``text``, 0-indexed - text : str - text to complete - - Notes - ----- - If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` - object containing a string with the Jedi debug information attached. - - .. deprecated:: 8.6 - You can use :meth:`_jedi_matcher` instead. - """ - namespaces = [self.namespace] - if self.global_namespace is not None: - namespaces.append(self.global_namespace) - - completion_filter = lambda x:x - offset = cursor_to_position(text, cursor_line, cursor_column) - # filter output if we are completing for object members - if offset: - pre = text[offset-1] - if pre == '.': - if self.omit__names == 2: - completion_filter = lambda c:not c.name.startswith('_') - elif self.omit__names == 1: - completion_filter = lambda c:not (c.name.startswith('__') and c.name.endswith('__')) - elif self.omit__names == 0: - completion_filter = lambda x:x - else: - raise ValueError("Don't understand self.omit__names == {}".format(self.omit__names)) - - interpreter = jedi.Interpreter(text[:offset], namespaces) - try_jedi = True - - try: - # find the first token in the current tree -- if it is a ' or " then we are in a string - completing_string = False - try: - first_child = next(c for c in interpreter._get_module().tree_node.children if hasattr(c, 'value')) - except StopIteration: - pass - else: - # note the value may be ', ", or it may also be ''' or """, or - # in some cases, """what/you/typed..., but all of these are - # strings. - completing_string = len(first_child.value) > 0 and first_child.value[0] in {"'", '"'} - - # if we are in a string jedi is likely not the right candidate for - # now. Skip it. - try_jedi = not completing_string - except Exception as e: - # many of things can go wrong, we are using private API just don't crash. - if self.debug: - print("Error detecting if completing a non-finished string :", e, '|') - - if not try_jedi: - return iter([]) - try: - return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) - except Exception as e: - if self.debug: - return iter( - [ - _FakeJediCompletion( - 'Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' - % (e) - ) - ] - ) - else: - return iter([]) - - @context_matcher() - def python_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Match attributes or global python names""" - text = context.line_with_cursor - if "." in text: - try: - matches, fragment = self._attr_matches(text, include_prefix=False) - if text.endswith(".") and self.omit__names: - if self.omit__names == 1: - # true if txt is _not_ a __ name, false otherwise: - no__name = lambda txt: re.match(r".*\.__.*?__", txt) is None - else: - # true if txt is _not_ a _ name, false otherwise: - no__name = ( - lambda txt: re.match(r"\._.*?", txt[txt.rindex(".") :]) - is None - ) - matches = filter(no__name, matches) - return _convert_matcher_v1_result_to_v2( - matches, type="attribute", fragment=fragment - ) - except NameError: - # catches <undefined attributes>.<tab> - matches = [] - return _convert_matcher_v1_result_to_v2(matches, type="attribute") - else: - matches = self.global_matches(context.token) - # TODO: maybe distinguish between functions, modules and just "variables" - return _convert_matcher_v1_result_to_v2(matches, type="variable") - - @completion_matcher(api_version=1) - def python_matches(self, text: str) -> Iterable[str]: - """Match attributes or global python names. - - .. deprecated:: 8.27 - You can use :meth:`python_matcher` instead.""" - if "." in text: - try: - matches = self.attr_matches(text) - if text.endswith('.') and self.omit__names: - if self.omit__names == 1: - # true if txt is _not_ a __ name, false otherwise: - no__name = (lambda txt: - re.match(r'.*\.__.*?__',txt) is None) - else: - # true if txt is _not_ a _ name, false otherwise: - no__name = (lambda txt: - re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) - matches = filter(no__name, matches) - except NameError: - # catches <undefined attributes>.<tab> - matches = [] - else: - matches = self.global_matches(text) - return matches - - def _default_arguments_from_docstring(self, doc): - """Parse the first line of docstring for call signature. - - Docstring should be of the form 'min(iterable[, key=func])\n'. - It can also parse cython docstring of the form - 'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'. - """ - if doc is None: - return [] - - #care only the firstline - line = doc.lstrip().splitlines()[0] - - #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') - #'min(iterable[, key=func])\n' -> 'iterable[, key=func]' - sig = self.docstring_sig_re.search(line) - if sig is None: - return [] - # iterable[, key=func]' -> ['iterable[' ,' key=func]'] - sig = sig.groups()[0].split(',') - ret = [] - for s in sig: - #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') - ret += self.docstring_kwd_re.findall(s) - return ret - - def _default_arguments(self, obj): - """Return the list of default arguments of obj if it is callable, - or empty list otherwise.""" - call_obj = obj - ret = [] - if inspect.isbuiltin(obj): - pass - elif not (inspect.isfunction(obj) or inspect.ismethod(obj)): - if inspect.isclass(obj): - #for cython embedsignature=True the constructor docstring - #belongs to the object itself not __init__ - ret += self._default_arguments_from_docstring( - getattr(obj, '__doc__', '')) - # for classes, check for __init__,__new__ - call_obj = (getattr(obj, '__init__', None) or - getattr(obj, '__new__', None)) - # for all others, check if they are __call__able - elif hasattr(obj, '__call__'): - call_obj = obj.__call__ - ret += self._default_arguments_from_docstring( - getattr(call_obj, '__doc__', '')) - - _keeps = (inspect.Parameter.KEYWORD_ONLY, - inspect.Parameter.POSITIONAL_OR_KEYWORD) - - try: - sig = inspect.signature(obj) - ret.extend(k for k, v in sig.parameters.items() if - v.kind in _keeps) - except ValueError: - pass - - return list(set(ret)) - - @context_matcher() - def python_func_kw_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Match named parameters (kwargs) of the last open function.""" - matches = self.python_func_kw_matches(context.token) - return _convert_matcher_v1_result_to_v2(matches, type="param") - - def python_func_kw_matches(self, text): - """Match named parameters (kwargs) of the last open function. - - .. deprecated:: 8.6 - You can use :meth:`python_func_kw_matcher` instead. - """ - - if "." in text: # a parameter cannot be dotted - return [] - try: regexp = self.__funcParamsRegex - except AttributeError: - regexp = self.__funcParamsRegex = re.compile(r''' - '.*?(?<!\\)' | # single quoted strings or - ".*?(?<!\\)" | # double quoted strings or - \w+ | # identifier - \S # other characters - ''', re.VERBOSE | re.DOTALL) - # 1. find the nearest identifier that comes before an unclosed - # parenthesis before the cursor - # e.g. for "foo (1+bar(x), pa<cursor>,a=1)", the candidate is "foo" - tokens = regexp.findall(self.text_until_cursor) - iterTokens = reversed(tokens) - openPar = 0 - - for token in iterTokens: - if token == ')': - openPar -= 1 - elif token == '(': - openPar += 1 - if openPar > 0: - # found the last unclosed parenthesis - break - else: - return [] - # 2. Concatenate dotted names ("foo.bar" for "foo.bar(x, pa" ) - ids = [] - isId = re.compile(r'\w+$').match - - while True: - try: - ids.append(next(iterTokens)) - if not isId(ids[-1]): - ids.pop() - break - if not next(iterTokens) == '.': - break - except StopIteration: - break - - # Find all named arguments already assigned to, as to avoid suggesting - # them again - usedNamedArgs = set() - par_level = -1 - for token, next_token in zip(tokens, tokens[1:]): - if token == '(': - par_level += 1 - elif token == ')': - par_level -= 1 - - if par_level != 0: - continue - - if next_token != '=': - continue - - usedNamedArgs.add(token) - - argMatches = [] - try: - callableObj = '.'.join(ids[::-1]) - namedArgs = self._default_arguments(eval(callableObj, - self.namespace)) - - # Remove used named arguments from the list, no need to show twice - for namedArg in set(namedArgs) - usedNamedArgs: - if namedArg.startswith(text): - argMatches.append("%s=" %namedArg) - except: - pass - - return argMatches - - @staticmethod - def _get_keys(obj: Any) -> List[Any]: - # Objects can define their own completions by defining an - # _ipy_key_completions_() method. - method = get_real_method(obj, '_ipython_key_completions_') - if method is not None: - return method() - - # Special case some common in-memory dict-like types - if isinstance(obj, dict) or _safe_isinstance(obj, "pandas", "DataFrame"): - try: - return list(obj.keys()) - except Exception: - return [] - elif _safe_isinstance(obj, "pandas", "core", "indexing", "_LocIndexer"): - try: - return list(obj.obj.keys()) - except Exception: - return [] - elif _safe_isinstance(obj, 'numpy', 'ndarray') or\ - _safe_isinstance(obj, 'numpy', 'void'): - return obj.dtype.names or [] - return [] - - @context_matcher() - def dict_key_matcher(self, context: CompletionContext) -> SimpleMatcherResult: - """Match string keys in a dictionary, after e.g. ``foo[``.""" - matches = self.dict_key_matches(context.token) - return _convert_matcher_v1_result_to_v2( - matches, type="dict key", suppress_if_matches=True - ) - - def dict_key_matches(self, text: str) -> List[str]: - """Match string keys in a dictionary, after e.g. ``foo[``. - - .. deprecated:: 8.6 - You can use :meth:`dict_key_matcher` instead. - """ - - # Short-circuit on closed dictionary (regular expression would - # not match anyway, but would take quite a while). - if self.text_until_cursor.strip().endswith("]"): - return [] - - match = DICT_MATCHER_REGEX.search(self.text_until_cursor) - - if match is None: - return [] - - expr, prior_tuple_keys, key_prefix = match.groups() - - obj = self._evaluate_expr(expr) - - if obj is not_found: - return [] - - keys = self._get_keys(obj) - if not keys: - return keys - - tuple_prefix = guarded_eval( - prior_tuple_keys, - EvaluationContext( - globals=self.global_namespace, - locals=self.namespace, - evaluation=self.evaluation, # type: ignore - in_subscript=True, - ), - ) - - closing_quote, token_offset, matches = match_dict_keys( - keys, key_prefix, self.splitter.delims, extra_prefix=tuple_prefix - ) - if not matches: - return [] - - # get the cursor position of - # - the text being completed - # - the start of the key text - # - the start of the completion - text_start = len(self.text_until_cursor) - len(text) - if key_prefix: - key_start = match.start(3) - completion_start = key_start + token_offset - else: - key_start = completion_start = match.end() - - # grab the leading prefix, to make sure all completions start with `text` - if text_start > key_start: - leading = '' - else: - leading = text[text_start:completion_start] - - # append closing quote and bracket as appropriate - # this is *not* appropriate if the opening quote or bracket is outside - # the text given to this method, e.g. `d["""a\nt - can_close_quote = False - can_close_bracket = False - - continuation = self.line_buffer[len(self.text_until_cursor) :].strip() - - if continuation.startswith(closing_quote): - # do not close if already closed, e.g. `d['a<tab>'` - continuation = continuation[len(closing_quote) :] - else: - can_close_quote = True - - continuation = continuation.strip() - - # e.g. `pandas.DataFrame` has different tuple indexer behaviour, - # handling it is out of scope, so let's avoid appending suffixes. - has_known_tuple_handling = isinstance(obj, dict) - - can_close_bracket = ( - not continuation.startswith("]") and self.auto_close_dict_keys - ) - can_close_tuple_item = ( - not continuation.startswith(",") - and has_known_tuple_handling - and self.auto_close_dict_keys - ) - can_close_quote = can_close_quote and self.auto_close_dict_keys - - # fast path if closing quote should be appended but not suffix is allowed - if not can_close_quote and not can_close_bracket and closing_quote: - return [leading + k for k in matches] - - results = [] - - end_of_tuple_or_item = _DictKeyState.END_OF_TUPLE | _DictKeyState.END_OF_ITEM - - for k, state_flag in matches.items(): - result = leading + k - if can_close_quote and closing_quote: - result += closing_quote - - if state_flag == end_of_tuple_or_item: - # We do not know which suffix to add, - # e.g. both tuple item and string - # match this item. - pass - - if state_flag in end_of_tuple_or_item and can_close_bracket: - result += "]" - if state_flag == _DictKeyState.IN_TUPLE and can_close_tuple_item: - result += ", " - results.append(result) - return results - - @context_matcher() - def unicode_name_matcher(self, context: CompletionContext): - """Same as :any:`unicode_name_matches`, but adopted to new Matcher API.""" - fragment, matches = self.unicode_name_matches(context.text_until_cursor) - return _convert_matcher_v1_result_to_v2( - matches, type="unicode", fragment=fragment, suppress_if_matches=True - ) - - @staticmethod - def unicode_name_matches(text: str) -> Tuple[str, List[str]]: - """Match Latex-like syntax for unicode characters base - on the name of the character. - - This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` - - Works only on valid python 3 identifier, or on combining characters that - will combine to form a valid identifier. - """ - slashpos = text.rfind('\\') - if slashpos > -1: - s = text[slashpos+1:] - try : - unic = unicodedata.lookup(s) - # allow combining chars - if ('a'+unic).isidentifier(): - return '\\'+s,[unic] - except KeyError: - pass - return '', [] - - @context_matcher() - def latex_name_matcher(self, context: CompletionContext): - """Match Latex syntax for unicode characters. - - This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` - """ - fragment, matches = self.latex_matches(context.text_until_cursor) - return _convert_matcher_v1_result_to_v2( - matches, type="latex", fragment=fragment, suppress_if_matches=True - ) - - def latex_matches(self, text: str) -> Tuple[str, Sequence[str]]: - """Match Latex syntax for unicode characters. - - This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` - - .. deprecated:: 8.6 - You can use :meth:`latex_name_matcher` instead. - """ - slashpos = text.rfind('\\') - if slashpos > -1: - s = text[slashpos:] - if s in latex_symbols: - # Try to complete a full latex symbol to unicode - # \\alpha -> α - return s, [latex_symbols[s]] - else: - # If a user has partially typed a latex symbol, give them - # a full list of options \al -> [\aleph, \alpha] - matches = [k for k in latex_symbols if k.startswith(s)] - if matches: - return s, matches - return '', () - - @context_matcher() - def custom_completer_matcher(self, context): - """Dispatch custom completer. - - If a match is found, suppresses all other matchers except for Jedi. - """ - matches = self.dispatch_custom_completer(context.token) or [] - result = _convert_matcher_v1_result_to_v2( - matches, type=_UNKNOWN_TYPE, suppress_if_matches=True - ) - result["ordered"] = True - result["do_not_suppress"] = {_get_matcher_id(self._jedi_matcher)} - return result - - def dispatch_custom_completer(self, text): - """ - .. deprecated:: 8.6 - You can use :meth:`custom_completer_matcher` instead. - """ - if not self.custom_completers: - return - - line = self.line_buffer - if not line.strip(): - return None - - # Create a little structure to pass all the relevant information about - # the current completion to any custom completer. - event = SimpleNamespace() - event.line = line - event.symbol = text - cmd = line.split(None,1)[0] - event.command = cmd - event.text_until_cursor = self.text_until_cursor - - # for foo etc, try also to find completer for %foo - if not cmd.startswith(self.magic_escape): - try_magic = self.custom_completers.s_matches( - self.magic_escape + cmd) - else: - try_magic = [] - - for c in itertools.chain(self.custom_completers.s_matches(cmd), - try_magic, - self.custom_completers.flat_matches(self.text_until_cursor)): - try: - res = c(event) - if res: - # first, try case sensitive match - withcase = [r for r in res if r.startswith(text)] - if withcase: - return withcase - # if none, then case insensitive ones are ok too - text_low = text.lower() - return [r for r in res if r.lower().startswith(text_low)] - except TryNext: - pass - except KeyboardInterrupt: - """ - If custom completer take too long, - let keyboard interrupt abort and return nothing. - """ - break - - return None - - def completions(self, text: str, offset: int)->Iterator[Completion]: - """ - Returns an iterator over the possible completions - - .. warning:: - - Unstable - - This function is unstable, API may change without warning. - It will also raise unless use in proper context manager. - - Parameters - ---------- - text : str - Full text of the current input, multi line string. - offset : int - Integer representing the position of the cursor in ``text``. Offset - is 0-based indexed. - - Yields - ------ - Completion - - Notes - ----- - The cursor on a text can either be seen as being "in between" - characters or "On" a character depending on the interface visible to - the user. For consistency the cursor being on "in between" characters X - and Y is equivalent to the cursor being "on" character Y, that is to say - the character the cursor is on is considered as being after the cursor. - - Combining characters may span more that one position in the - text. - - .. note:: - - If ``IPCompleter.debug`` is :any:`True` will yield a ``--jedi/ipython--`` - fake Completion token to distinguish completion returned by Jedi - and usual IPython completion. - - .. note:: - - Completions are not completely deduplicated yet. If identical - completions are coming from different sources this function does not - ensure that each completion object will only be present once. - """ - warnings.warn("_complete is a provisional API (as of IPython 6.0). " - "It may change without warnings. " - "Use in corresponding context manager.", - category=ProvisionalCompleterWarning, stacklevel=2) - - seen = set() - profiler:Optional[cProfile.Profile] - try: - if self.profile_completions: - import cProfile - profiler = cProfile.Profile() - profiler.enable() - else: - profiler = None - - for c in self._completions(text, offset, _timeout=self.jedi_compute_type_timeout/1000): - if c and (c in seen): - continue - yield c - seen.add(c) - except KeyboardInterrupt: - """if completions take too long and users send keyboard interrupt, - do not crash and return ASAP. """ - pass - finally: - if profiler is not None: - profiler.disable() - ensure_dir_exists(self.profiler_output_dir) - output_path = os.path.join(self.profiler_output_dir, str(uuid.uuid4())) - print("Writing profiler output to", output_path) - profiler.dump_stats(output_path) - - def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: - """ - Core completion module.Same signature as :any:`completions`, with the - extra `timeout` parameter (in seconds). - - Computing jedi's completion ``.type`` can be quite expensive (it is a - lazy property) and can require some warm-up, more warm up than just - computing the ``name`` of a completion. The warm-up can be : - - - Long warm-up the first time a module is encountered after - install/update: actually build parse/inference tree. - - - first time the module is encountered in a session: load tree from - disk. - - We don't want to block completions for tens of seconds so we give the - completer a "budget" of ``_timeout`` seconds per invocation to compute - completions types, the completions that have not yet been computed will - be marked as "unknown" an will have a chance to be computed next round - are things get cached. - - Keep in mind that Jedi is not the only thing treating the completion so - keep the timeout short-ish as if we take more than 0.3 second we still - have lots of processing to do. - - """ - deadline = time.monotonic() + _timeout - - before = full_text[:offset] - cursor_line, cursor_column = position_to_cursor(full_text, offset) - - jedi_matcher_id = _get_matcher_id(self._jedi_matcher) - - def is_non_jedi_result( - result: MatcherResult, identifier: str - ) -> TypeGuard[SimpleMatcherResult]: - return identifier != jedi_matcher_id - - results = self._complete( - full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column - ) - - non_jedi_results: Dict[str, SimpleMatcherResult] = { - identifier: result - for identifier, result in results.items() - if is_non_jedi_result(result, identifier) - } - - jedi_matches = ( - cast(_JediMatcherResult, results[jedi_matcher_id])["completions"] - if jedi_matcher_id in results - else () - ) - - iter_jm = iter(jedi_matches) - if _timeout: - for jm in iter_jm: - try: - type_ = jm.type - except Exception: - if self.debug: - print("Error in Jedi getting type of ", jm) - type_ = None - delta = len(jm.name_with_symbols) - len(jm.complete) - if type_ == 'function': - signature = _make_signature(jm) - else: - signature = '' - yield Completion(start=offset - delta, - end=offset, - text=jm.name_with_symbols, - type=type_, - signature=signature, - _origin='jedi') - - if time.monotonic() > deadline: - break - - for jm in iter_jm: - delta = len(jm.name_with_symbols) - len(jm.complete) - yield Completion( - start=offset - delta, - end=offset, - text=jm.name_with_symbols, - type=_UNKNOWN_TYPE, # don't compute type for speed - _origin="jedi", - signature="", - ) - - # TODO: - # Suppress this, right now just for debug. - if jedi_matches and non_jedi_results and self.debug: - some_start_offset = before.rfind( - next(iter(non_jedi_results.values()))["matched_fragment"] - ) - yield Completion( - start=some_start_offset, - end=offset, - text="--jedi/ipython--", - _origin="debug", - type="none", - signature="", - ) - - ordered: List[Completion] = [] - sortable: List[Completion] = [] - - for origin, result in non_jedi_results.items(): - matched_text = result["matched_fragment"] - start_offset = before.rfind(matched_text) - is_ordered = result.get("ordered", False) - container = ordered if is_ordered else sortable - - # I'm unsure if this is always true, so let's assert and see if it - # crash - assert before.endswith(matched_text) - - for simple_completion in result["completions"]: - completion = Completion( - start=start_offset, - end=offset, - text=simple_completion.text, - _origin=origin, - signature="", - type=simple_completion.type or _UNKNOWN_TYPE, - ) - container.append(completion) - - yield from list(self._deduplicate(ordered + self._sort(sortable)))[ - :MATCHES_LIMIT - ] - - def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: - """Find completions for the given text and line context. - - Note that both the text and the line_buffer are optional, but at least - one of them must be given. - - Parameters - ---------- - text : string, optional - Text to perform the completion on. If not given, the line buffer - is split using the instance's CompletionSplitter object. - line_buffer : string, optional - If not given, the completer attempts to obtain the current line - buffer via readline. This keyword allows clients which are - requesting for text completions in non-readline contexts to inform - the completer of the entire text. - cursor_pos : int, optional - Index of the cursor in the full line buffer. Should be provided by - remote frontends where kernel has no access to frontend state. - - Returns - ------- - Tuple of two items: - text : str - Text that was actually used in the completion. - matches : list - A list of completion matches. - - Notes - ----- - This API is likely to be deprecated and replaced by - :any:`IPCompleter.completions` in the future. - - """ - warnings.warn('`Completer.complete` is pending deprecation since ' - 'IPython 6.0 and will be replaced by `Completer.completions`.', - PendingDeprecationWarning) - # potential todo, FOLD the 3rd throw away argument of _complete - # into the first 2 one. - # TODO: Q: does the above refer to jedi completions (i.e. 0-indexed?) - # TODO: should we deprecate now, or does it stay? - - results = self._complete( - line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0 - ) - - jedi_matcher_id = _get_matcher_id(self._jedi_matcher) - - return self._arrange_and_extract( - results, - # TODO: can we confirm that excluding Jedi here was a deliberate choice in previous version? - skip_matchers={jedi_matcher_id}, - # this API does not support different start/end positions (fragments of token). - abort_if_offset_changes=True, - ) - - def _arrange_and_extract( - self, - results: Dict[str, MatcherResult], - skip_matchers: Set[str], - abort_if_offset_changes: bool, - ): - sortable: List[AnyMatcherCompletion] = [] - ordered: List[AnyMatcherCompletion] = [] - most_recent_fragment = None - for identifier, result in results.items(): - if identifier in skip_matchers: - continue - if not result["completions"]: - continue - if not most_recent_fragment: - most_recent_fragment = result["matched_fragment"] - if ( - abort_if_offset_changes - and result["matched_fragment"] != most_recent_fragment - ): - break - if result.get("ordered", False): - ordered.extend(result["completions"]) - else: - sortable.extend(result["completions"]) - - if not most_recent_fragment: - most_recent_fragment = "" # to satisfy typechecker (and just in case) - - return most_recent_fragment, [ - m.text for m in self._deduplicate(ordered + self._sort(sortable)) - ] - - def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, - full_text=None) -> _CompleteResult: - """ - Like complete but can also returns raw jedi completions as well as the - origin of the completion text. This could (and should) be made much - cleaner but that will be simpler once we drop the old (and stateful) - :any:`complete` API. - - With current provisional API, cursor_pos act both (depending on the - caller) as the offset in the ``text`` or ``line_buffer``, or as the - ``column`` when passing multiline strings this could/should be renamed - but would add extra noise. - - Parameters - ---------- - cursor_line - Index of the line the cursor is on. 0 indexed. - cursor_pos - Position of the cursor in the current line/line_buffer/text. 0 - indexed. - line_buffer : optional, str - The current line the cursor is in, this is mostly due to legacy - reason that readline could only give a us the single current line. - Prefer `full_text`. - text : str - The current "token" the cursor is in, mostly also for historical - reasons. as the completer would trigger only after the current line - was parsed. - full_text : str - Full text of the current cell. - - Returns - ------- - An ordered dictionary where keys are identifiers of completion - matchers and values are ``MatcherResult``s. - """ - - # if the cursor position isn't given, the only sane assumption we can - # make is that it's at the end of the line (the common case) - if cursor_pos is None: - cursor_pos = len(line_buffer) if text is None else len(text) - - if self.use_main_ns: - self.namespace = __main__.__dict__ - - # if text is either None or an empty string, rely on the line buffer - if (not line_buffer) and full_text: - line_buffer = full_text.split('\n')[cursor_line] - if not text: # issue #11508: check line_buffer before calling split_line - text = ( - self.splitter.split_line(line_buffer, cursor_pos) if line_buffer else "" - ) - - # If no line buffer is given, assume the input text is all there was - if line_buffer is None: - line_buffer = text - - # deprecated - do not use `line_buffer` in new code. - self.line_buffer = line_buffer - self.text_until_cursor = self.line_buffer[:cursor_pos] - - if not full_text: - full_text = line_buffer - - context = CompletionContext( - full_text=full_text, - cursor_position=cursor_pos, - cursor_line=cursor_line, - token=text, - limit=MATCHES_LIMIT, - ) - - # Start with a clean slate of completions - results: Dict[str, MatcherResult] = {} - - jedi_matcher_id = _get_matcher_id(self._jedi_matcher) - - suppressed_matchers: Set[str] = set() - - matchers = { - _get_matcher_id(matcher): matcher - for matcher in sorted( - self.matchers, key=_get_matcher_priority, reverse=True - ) - } - - for matcher_id, matcher in matchers.items(): - matcher_id = _get_matcher_id(matcher) - - if matcher_id in self.disable_matchers: - continue - - if matcher_id in results: - warnings.warn(f"Duplicate matcher ID: {matcher_id}.") - - if matcher_id in suppressed_matchers: - continue - - result: MatcherResult - try: - if _is_matcher_v1(matcher): - result = _convert_matcher_v1_result_to_v2( - matcher(text), type=_UNKNOWN_TYPE - ) - elif _is_matcher_v2(matcher): - result = matcher(context) - else: - api_version = _get_matcher_api_version(matcher) - raise ValueError(f"Unsupported API version {api_version}") - except BaseException: - # Show the ugly traceback if the matcher causes an - # exception, but do NOT crash the kernel! - sys.excepthook(*sys.exc_info()) - continue - - # set default value for matched fragment if suffix was not selected. - result["matched_fragment"] = result.get("matched_fragment", context.token) - - if not suppressed_matchers: - suppression_recommended: Union[bool, Set[str]] = result.get( - "suppress", False - ) - - suppression_config = ( - self.suppress_competing_matchers.get(matcher_id, None) - if isinstance(self.suppress_competing_matchers, dict) - else self.suppress_competing_matchers - ) - should_suppress = ( - (suppression_config is True) - or (suppression_recommended and (suppression_config is not False)) - ) and has_any_completions(result) - - if should_suppress: - suppression_exceptions: Set[str] = result.get( - "do_not_suppress", set() - ) - if isinstance(suppression_recommended, Iterable): - to_suppress = set(suppression_recommended) - else: - to_suppress = set(matchers) - suppressed_matchers = to_suppress - suppression_exceptions - - new_results = {} - for previous_matcher_id, previous_result in results.items(): - if previous_matcher_id not in suppressed_matchers: - new_results[previous_matcher_id] = previous_result - results = new_results - - results[matcher_id] = result - - _, matches = self._arrange_and_extract( - results, - # TODO Jedi completions non included in legacy stateful API; was this deliberate or omission? - # if it was omission, we can remove the filtering step, otherwise remove this comment. - skip_matchers={jedi_matcher_id}, - abort_if_offset_changes=False, - ) - - # populate legacy stateful API - self.matches = matches - - return results - - @staticmethod - def _deduplicate( - matches: Sequence[AnyCompletion], - ) -> Iterable[AnyCompletion]: - filtered_matches: Dict[str, AnyCompletion] = {} - for match in matches: - text = match.text - if ( - text not in filtered_matches - or filtered_matches[text].type == _UNKNOWN_TYPE - ): - filtered_matches[text] = match - - return filtered_matches.values() - - @staticmethod - def _sort(matches: Sequence[AnyCompletion]): - return sorted(matches, key=lambda x: completions_sorting_key(x.text)) - - @context_matcher() - def fwd_unicode_matcher(self, context: CompletionContext): - """Same as :any:`fwd_unicode_match`, but adopted to new Matcher API.""" - # TODO: use `context.limit` to terminate early once we matched the maximum - # number that will be used downstream; can be added as an optional to - # `fwd_unicode_match(text: str, limit: int = None)` or we could re-implement here. - fragment, matches = self.fwd_unicode_match(context.text_until_cursor) - return _convert_matcher_v1_result_to_v2( - matches, type="unicode", fragment=fragment, suppress_if_matches=True - ) - - def fwd_unicode_match(self, text: str) -> Tuple[str, Sequence[str]]: - """ - Forward match a string starting with a backslash with a list of - potential Unicode completions. - - Will compute list of Unicode character names on first call and cache it. - - .. deprecated:: 8.6 - You can use :meth:`fwd_unicode_matcher` instead. - - Returns - ------- - At tuple with: - - matched text (empty if no matches) - - list of potential completions, empty tuple otherwise) - """ - # TODO: self.unicode_names is here a list we traverse each time with ~100k elements. - # We could do a faster match using a Trie. - - # Using pygtrie the following seem to work: - - # s = PrefixSet() - - # for c in range(0,0x10FFFF + 1): - # try: - # s.add(unicodedata.name(chr(c))) - # except ValueError: - # pass - # [''.join(k) for k in s.iter(prefix)] - - # But need to be timed and adds an extra dependency. - - slashpos = text.rfind('\\') - # if text starts with slash - if slashpos > -1: - # PERF: It's important that we don't access self._unicode_names - # until we're inside this if-block. _unicode_names is lazily - # initialized, and it takes a user-noticeable amount of time to - # initialize it, so we don't want to initialize it unless we're - # actually going to use it. - s = text[slashpos + 1 :] - sup = s.upper() - candidates = [x for x in self.unicode_names if x.startswith(sup)] - if candidates: - return s, candidates - candidates = [x for x in self.unicode_names if sup in x] - if candidates: - return s, candidates - splitsup = sup.split(" ") - candidates = [ - x for x in self.unicode_names if all(u in x for u in splitsup) - ] - if candidates: - return s, candidates - - return "", () - - # if text does not start with slash - else: - return '', () - - @property - def unicode_names(self) -> List[str]: - """List of names of unicode code points that can be completed. - - The list is lazily initialized on first access. - """ - if self._unicode_names is None: - names = [] - for c in range(0,0x10FFFF + 1): - try: - names.append(unicodedata.name(chr(c))) - except ValueError: - pass - self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) - - return self._unicode_names - -def _unicode_name_compute(ranges:List[Tuple[int,int]]) -> List[str]: - names = [] - for start,stop in ranges: - for c in range(start, stop) : - try: - names.append(unicodedata.name(chr(c))) - except ValueError: - pass - return names