completer.py
2239 lines
| 77.8 KiB
| text/x-python
|
PythonLexer
Matthias Bussonnier
|
r23284 | """Completion for IPython. | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r22633 | This module started as fork of the rlcompleter module in the Python standard | ||
ville
|
r988 | library. The original enhancements made to rlcompleter have been sent | ||
Matthias Bussonnier
|
r22628 | upstream and were accepted as of Python 2.3, | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r23284 | 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. | ||||
Matthias Bussonnier
|
r23471 | 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:: | ||||
Scott Sanderson
|
r25698 | \\GREEK SMALL LETTER ALPHA<tab> | ||
Matthias Bussonnier
|
r23471 | α | ||
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 `<tab>` to expand it to its latex form. | ||||
.. code:: | ||||
\\α<tab> | ||||
\\alpha | ||||
Both forward and backward completions can be deactivated by setting the | ||||
``Completer.backslash_combining_completions`` option to ``False``. | ||||
Matthias Bussonnier
|
r23284 | 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 | ||||
Brandon T. Willard
|
r25121 | inspecting multiple namespaces. Jedi is an autocompletion and static analysis | ||
for Python. The APIs attached to this new mechanism is unstable and will | ||||
luciana
|
r24916 | raise unless use in an :any:`provisionalcompleter` context manager. | ||
Matthias Bussonnier
|
r23284 | |||
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 | ||||
luciana
|
r24916 | to have extra logging information if :any:`jedi` is crashing, or if current | ||
Matthias Bussonnier
|
r23284 | IPython completer pending deprecations are returning results not yet handled | ||
Matthias Bussonnier
|
r23753 | by :any:`jedi` | ||
Matthias Bussonnier
|
r23284 | |||
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 any code unlike the previously available ``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. | ||||
ville
|
r988 | """ | ||
Matthias Bussonnier
|
r23284 | |||
Paul Ivanov
|
r17735 | # Copyright (c) IPython Development Team. | ||
# Distributed under the terms of the Modified BSD License. | ||||
Paul Ivanov
|
r17756 | # | ||
# Some of this code originated from rlcompleter in the Python standard library | ||||
# Copyright (C) 2001 Python Software Foundation, www.python.org | ||||
Fernando Perez
|
r2365 | |||
Kelly Liu
|
r22292 | |||
Thomas Kluyver
|
r23129 | import builtins as builtin_mod | ||
ville
|
r988 | import glob | ||
Brian Granger
|
r2498 | import inspect | ||
Brian Granger
|
r2248 | import itertools | ||
ville
|
r988 | import keyword | ||
import os | ||||
import re | ||||
gorogoroumaru
|
r25654 | import string | ||
ville
|
r988 | import sys | ||
gorogoroumaru
|
r25654 | import time | ||
Matthias Bussonnier
|
r21101 | import unicodedata | ||
Scott Sanderson
|
r25693 | import uuid | ||
Thomas Kluyver
|
r22934 | import warnings | ||
Matthias Bussonnier
|
r23284 | from contextlib import contextmanager | ||
Diego Garcia
|
r22954 | from importlib import import_module | ||
Matthias Bussonnier
|
r23284 | from types import SimpleNamespace | ||
Matthias Bussonnier
|
r25705 | from typing import Iterable, Iterator, List, Tuple, Union, Any, Sequence, Dict, NamedTuple, Pattern, Optional | ||
Brian Granger
|
r2205 | |||
from IPython.core.error import TryNext | ||||
Thomas Kluyver
|
r24176 | from IPython.core.inputtransformer2 import ESC_MAGIC | ||
Matthias Bussonnier
|
r21101 | from IPython.core.latex_symbols import latex_symbols, reverse_latex_symbol | ||
Sang Min Park
|
r23685 | from IPython.core.oinspect import InspectColors | ||
Fernando Perez
|
r2959 | from IPython.utils import generics | ||
Thomas Kluyver
|
r22148 | from IPython.utils.dir2 import dir2, get_real_method | ||
Scott Sanderson
|
r25693 | from IPython.utils.path import ensure_dir_exists | ||
Fernando Perez
|
r3074 | from IPython.utils.process import arg_split | ||
Scott Sanderson
|
r25696 | from traitlets import Bool, Enum, Int, List as ListTrait, Unicode, default, observe | ||
gorogoroumaru
|
r25654 | from traitlets.config.configurable import Configurable | ||
import __main__ | ||||
ville
|
r988 | |||
Matthias Bussonnier
|
r23753 | # skip module docstests | ||
Nikita Kniazev
|
r26873 | __skip_doctest__ = True | ||
Matthias Bussonnier
|
r23753 | |||
Matthias Bussonnier
|
r23284 | try: | ||
import jedi | ||||
David Cottrell
|
r24137 | jedi.settings.case_insensitive_completion = False | ||
Matthias Bussonnier
|
r23284 | import jedi.api.helpers | ||
Matthias Bussonnier
|
r23753 | import jedi.api.classes | ||
Matthias Bussonnier
|
r23284 | JEDI_INSTALLED = True | ||
except ImportError: | ||||
JEDI_INSTALLED = False | ||||
#----------------------------------------------------------------------------- | ||||
# Globals | ||||
#----------------------------------------------------------------------------- | ||||
Fernando Perez
|
r2365 | |||
Matthias Bussonnier
|
r25711 | # ranges where we have most of the valid unicode names. We could be more finer | ||
Dimitri Papadopoulos
|
r26875 | # 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 | ||||
Matthias Bussonnier
|
r25711 | # 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. | ||||
Matthias Bussonnier
|
r25729 | _UNICODE_RANGES = [(32, 0x3134b), (0xe0001, 0xe01f0)] | ||
Matthias Bussonnier
|
r25711 | |||
Fernando Perez
|
r2365 | # Public API | ||
ville
|
r988 | __all__ = ['Completer','IPCompleter'] | ||
Fernando Perez
|
r2365 | if sys.platform == 'win32': | ||
PROTECTABLES = ' ' | ||||
else: | ||||
Fernando Perez
|
r3176 | PROTECTABLES = ' ()[]{}?=\\|;:\'#*"^&' | ||
Fernando Perez
|
r2365 | |||
Thomas Kluyver
|
r23844 | # Protect against returning an enormous number of completions which the frontend | ||
# may have trouble processing. | ||||
MATCHES_LIMIT = 500 | ||||
Joel Nothman
|
r16466 | |||
Matthias Bussonnier
|
r23284 | _deprecation_readline_sentinel = 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) | ||||
@contextmanager | ||||
def provisionalcompleter(action='ignore'): | ||||
""" | ||||
Brandon T. Willard
|
r25121 | This context manager has to be used in any place where unstable completer | ||
Matthias Bussonnier
|
r23284 | behavior and API may be called. | ||
>>> with provisionalcompleter(): | ||||
Brandon T. Willard
|
r25121 | ... completer.do_experimental_things() # works | ||
Matthias Bussonnier
|
r23284 | |||
>>> completer.do_experimental_things() # raises. | ||||
Matthias Bussonnier
|
r26333 | .. note:: | ||
Unstable | ||||
Matthias Bussonnier
|
r23284 | |||
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. | ||||
Brandon T. Willard
|
r25121 | You also understand that, if the API is not to your liking, you should report | ||
a bug to explain your use case upstream. | ||||
Matthias Bussonnier
|
r23284 | |||
Brandon T. Willard
|
r25121 | We'll be happy to get your feedback, feature requests, and improvements on | ||
any of the unstable APIs! | ||||
Matthias Bussonnier
|
r23284 | """ | ||
with warnings.catch_warnings(): | ||||
warnings.filterwarnings(action, category=ProvisionalCompleterWarning) | ||||
yield | ||||
Fernando Perez
|
r3184 | def has_open_quotes(s): | ||
"""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 | ||||
Christopher C. Aycock
|
r23613 | def protect_filename(s, protectables=PROTECTABLES): | ||
Fernando Perez
|
r2365 | """Escape a string to protect certain characters.""" | ||
Christopher C. Aycock
|
r23613 | if set(s) & set(protectables): | ||
Antony Lee
|
r22418 | if sys.platform == "win32": | ||
return '"' + s + '"' | ||||
else: | ||||
Christopher C. Aycock
|
r23613 | return "".join(("\\" + c if c in protectables else c) for c in s) | ||
Antony Lee
|
r22418 | else: | ||
return s | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2365 | |||
Matthias Bussonnier
|
r23753 | def expand_user(path:str) -> Tuple[str, bool, str]: | ||
Matthias Bussonnier
|
r23284 | """Expand ``~``-style usernames in strings. | ||
Fernando Perez
|
r2965 | |||
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 | ||||
Matthias Bussonnier
|
r25890 | String to be expanded. If no ~ is present, the output is the same as the | ||
input. | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2965 | Returns | ||
------- | ||||
newpath : str | ||||
Matthias Bussonnier
|
r25890 | Result of ~ expansion in the input path. | ||
Fernando Perez
|
r2965 | tilde_expand : bool | ||
Matthias Bussonnier
|
r25890 | Whether any expansion was performed or not. | ||
Fernando Perez
|
r2965 | tilde_val : str | ||
Matthias Bussonnier
|
r25890 | The value that ~ was replaced with. | ||
Fernando Perez
|
r2965 | """ | ||
# Default values | ||||
tilde_expand = False | ||||
tilde_val = '' | ||||
newpath = path | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2965 | if path.startswith('~'): | ||
tilde_expand = True | ||||
MinRK
|
r5201 | rest = len(path)-1 | ||
Fernando Perez
|
r2965 | newpath = os.path.expanduser(path) | ||
MinRK
|
r5201 | if rest: | ||
tilde_val = newpath[:-rest] | ||||
else: | ||||
tilde_val = newpath | ||||
Fernando Perez
|
r2965 | |||
return newpath, tilde_expand, tilde_val | ||||
Matthias Bussonnier
|
r23753 | def compress_user(path:str, tilde_expand:bool, tilde_val:str) -> str: | ||
Fernando Perez
|
r2965 | """Does the opposite of expand_user, with its outputs. | ||
""" | ||||
if tilde_expand: | ||||
return path.replace(tilde_val, '~') | ||||
else: | ||||
return path | ||||
Fernando Perez
|
r6945 | |||
Thomas Kluyver
|
r21917 | def completions_sorting_key(word): | ||
"""key for sorting completions | ||||
David P. Sanders
|
r13343 | |||
Thomas Kluyver
|
r21917 | This does several things: | ||
David P. Sanders
|
r13343 | |||
Thomas Kluyver
|
r21917 | - Demote any completions starting with underscores to the end | ||
- Insert any %magic and %%cellmagic completions in the alphabetical order | ||||
by their name | ||||
David P. Sanders
|
r13343 | """ | ||
Thomas Kluyver
|
r21917 | prio1, prio2 = 0, 0 | ||
David P. Sanders
|
r13343 | |||
Thomas Kluyver
|
r21917 | if word.startswith('__'): | ||
prio1 = 2 | ||||
elif word.startswith('_'): | ||||
prio1 = 1 | ||||
David P. Sanders
|
r13343 | |||
Matthias Bussonnier
|
r22282 | if word.endswith('='): | ||
prio1 = -1 | ||||
Thomas Kluyver
|
r21917 | 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('%'): | ||||
David P. Sanders
|
r13343 | if not "%" in word[1:]: | ||
Thomas Kluyver
|
r21917 | word = word[1:] | ||
prio2 = 1 | ||||
return prio1, word, prio2 | ||||
David P. Sanders
|
r13343 | |||
Matthias Bussonnier
|
r23284 | 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 | ||||
Matthias Bussonnier
|
r23753 | self.signature = '' | ||
self._origin = 'fake' | ||||
Matthias Bussonnier
|
r23284 | |||
def __repr__(self): | ||||
return '<Fake completion object jedi has crashed>' | ||||
class Completion: | ||||
""" | ||||
Completion object used and return by IPython completers. | ||||
Matthias Bussonnier
|
r26333 | .. warning:: | ||
Unstable | ||||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | 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. | ||||
luzpaz
|
r24084 | - Some metadata (like completion type), or meta information to displayed to | ||
Matthias Bussonnier
|
r23284 | the use user. | ||
For debugging purpose we can also store the origin of the completion (``jedi``, | ||||
``IPython.python_matches``, ``IPython.magics_matches``...). | ||||
""" | ||||
Matthias Bussonnier
|
r23753 | __slots__ = ['start', 'end', 'text', 'type', 'signature', '_origin'] | ||
def __init__(self, start: int, end: int, text: str, *, type: str=None, _origin='', signature='') -> None: | ||||
Matthias Bussonnier
|
r23284 | 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 | ||||
Matthias Bussonnier
|
r23753 | self.signature = signature | ||
Matthias Bussonnier
|
r23284 | self._origin = _origin | ||
def __repr__(self): | ||||
Matthias Bussonnier
|
r23753 | return '<Completion start=%s end=%s text=%r type=%r, signature=%r,>' % \ | ||
(self.start, self.end, self.text, self.type or '?', self.signature or '?') | ||||
Matthias Bussonnier
|
r23284 | |||
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)) | ||||
Matthias Bussonnier
|
r23358 | |||
Matthias Bussonnier
|
r23677 | _IC = Iterable[Completion] | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r23358 | |||
def _deduplicate_completions(text: str, completions: _IC)-> _IC: | ||||
Matthias Bussonnier
|
r23284 | """ | ||
Matthias Bussonnier
|
r23358 | Deduplicate a set of completions. | ||
Matthias Bussonnier
|
r26333 | .. warning:: | ||
Unstable | ||||
Matthias Bussonnier
|
r23358 | |||
This function is unstable, API may change without warning. | ||||
Parameters | ||||
---------- | ||||
Matthias Bussonnier
|
r26491 | text : str | ||
Matthias Bussonnier
|
r23358 | text that should be completed. | ||
Matthias Bussonnier
|
r26491 | completions : Iterator[Completion] | ||
Matthias Bussonnier
|
r23358 | iterator over the completions to deduplicate | ||
Matthias Bussonnier
|
r23753 | Yields | ||
------ | ||||
`Completions` objects | ||||
Matthias Bussonnier
|
r23358 | 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=False)->_IC: | ||||
""" | ||||
Rectify a set of completions to all have the same ``start`` and ``end`` | ||||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r26333 | .. warning:: | ||
Unstable | ||||
Matthias Bussonnier
|
r23284 | |||
This function is unstable, API may change without warning. | ||||
It will also raise unless use in proper context manager. | ||||
Parameters | ||||
---------- | ||||
Matthias Bussonnier
|
r26491 | text : str | ||
Matthias Bussonnier
|
r23284 | text that should be completed. | ||
Matthias Bussonnier
|
r26491 | completions : Iterator[Completion] | ||
Matthias Bussonnier
|
r23284 | iterator over the completions to rectify | ||
Matthias Bussonnier
|
r25889 | Notes | ||
----- | ||||
Matthias Bussonnier
|
r23284 | :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 | ||||
Matthias Bussonnier
|
r23465 | the completion to have the same ``start`` and ``end`` by padding both | ||
Matthias Bussonnier
|
r23284 | 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) | ||||
Matthias Bussonnier
|
r23556 | completions = list(completions) | ||
Matthias Bussonnier
|
r23284 | 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_matches': | ||||
seen_python_matches.add(new_text) | ||||
Matthias Bussonnier
|
r23753 | yield Completion(new_start, new_end, new_text, type=c.type, _origin=c._origin, signature=c.signature) | ||
Matthias Bussonnier
|
r23284 | diff = seen_python_matches.difference(seen_jedi) | ||
if diff and _debug: | ||||
print('IPython.python matches have extras:', diff) | ||||
Fernando Perez
|
r2855 | |||
Fernando Perez
|
r6945 | |||
tmr232
|
r22745 | if sys.platform == 'win32': | ||
DELIMS = ' \t\n`!@#$^&*()=+[{]}|;\'",<>?' | ||||
else: | ||||
DELIMS = ' \t\n`!@#$^&*()=+[{]}\\|;:\'",<>?' | ||||
klonuo
|
r9207 | GREEDY_DELIMS = ' =\r\n' | ||
Fernando Perez
|
r2855 | |||
Fernando Perez
|
r6945 | |||
Fernando Perez
|
r2855 | 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 | ||||
Matthias Bussonnier
|
r23284 | setting the ``delims`` attribute (this is a property that internally | ||
Fernando Perez
|
r6945 | automatically builds the necessary regular expression)""" | ||
Fernando Perez
|
r2855 | |||
# Private interface | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2855 | # A string of delimiter characters. The default value makes sense for | ||
# IPython's most typical usage patterns. | ||||
MinRK
|
r4825 | _delims = DELIMS | ||
Fernando Perez
|
r2855 | |||
# 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 | ||||
Fernando Perez
|
r6945 | self.delims = delims | ||
Fernando Perez
|
r2855 | |||
Fernando Perez
|
r6945 | @property | ||
def delims(self): | ||||
"""Return the string of delimiter characters.""" | ||||
return self._delims | ||||
@delims.setter | ||||
def delims(self, delims): | ||||
Fernando Perez
|
r2855 | """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. | ||||
""" | ||||
l = line if cursor_pos is None else line[:cursor_pos] | ||||
return self._delim_re.split(l)[-1] | ||||
Matthias Bussonnier
|
r23284 | |||
MinRK
|
r4825 | class Completer(Configurable): | ||
Bernardo B. Marques
|
r4872 | |||
Min RK
|
r22340 | greedy = Bool(False, | ||
MinRK
|
r4825 | help="""Activate greedy completion | ||
Kelly Liu
|
r22292 | PENDING DEPRECTION. this is now mostly taken care of with Jedi. | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r4825 | This will enable completion on elements of lists, results of function calls, etc., | ||
but can be unsafe because the code is actually evaluated on TAB. | ||||
""" | ||||
Min RK
|
r22340 | ).tag(config=True) | ||
Matthias Bussonnier
|
r23284 | |||
Philipp A
|
r24842 | use_jedi = Bool(default_value=JEDI_INSTALLED, | ||
Matthias Bussonnier
|
r23284 | help="Experimental: Use Jedi to generate autocompletions. " | ||
Philipp A
|
r24842 | "Default to True if jedi is installed.").tag(config=True) | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r23314 | 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) | ||||
Matthias Bussonnier
|
r23284 | debug = Bool(default_value=False, | ||
help='Enable debug for the Completer. Mostly print extra ' | ||||
'information for experimental jedi integration.')\ | ||||
.tag(config=True) | ||||
Steven Silvester
|
r24587 | backslash_combining_completions = Bool(True, | ||
Thomas Kluyver
|
r23450 | 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) | ||||
Matthias Bussonnier
|
r23446 | |||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r11064 | def __init__(self, namespace=None, global_namespace=None, **kwargs): | ||
ville
|
r988 | """Create a new completer for the command line. | ||
Kelly Liu
|
r22292 | Completer(namespace=ns, global_namespace=ns2) -> completer instance. | ||
ville
|
r988 | |||
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: | ||||
Matthias Bussonnier
|
r23284 | self.use_main_ns = True | ||
ville
|
r988 | else: | ||
Matthias Bussonnier
|
r23284 | self.use_main_ns = False | ||
ville
|
r988 | 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 | ||||
Bernardo B. Marques
|
r4872 | |||
Nathan Goldbaum
|
r25490 | self.custom_matchers = [] | ||
MinRK
|
r11064 | super(Completer, self).__init__(**kwargs) | ||
ville
|
r988 | |||
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__ | ||||
Bernardo B. Marques
|
r4872 | |||
ville
|
r988 | 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, | ||||
Thomas Kluyver
|
r13351 | builtin_mod.__dict__.keys(), | ||
ville
|
r988 | self.namespace.keys(), | ||
self.global_namespace.keys()]: | ||||
for word in lst: | ||||
if word[:n] == text and word != "__builtins__": | ||||
match_append(word) | ||||
sagnak
|
r23614 | |||
sagnak
|
r23615 | snake_case_re = re.compile(r"[^_]+(_[^_]+)+?\Z") | ||
sagnak
|
r23614 | for lst in [self.namespace.keys(), | ||
self.global_namespace.keys()]: | ||||
shortened = {"_".join([sub[0] for sub in word.split('_')]) : word | ||||
sagnak
|
r23615 | for word in lst if snake_case_re.match(word)} | ||
sagnak
|
r23614 | for word in shortened.keys(): | ||
if word[:n] == text and word != "__builtins__": | ||||
match_append(shortened[word]) | ||||
Srinivas Reddy Thatiparthy
|
r23669 | return matches | ||
ville
|
r988 | |||
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 | ||||
luz.paz
|
r24236 | possible completions. (For class instances, class members are | ||
ville
|
r988 | also considered.) | ||
WARNING: this can still invoke arbitrary C code, if an object | ||||
with a __getattr__ hook is evaluated. | ||||
""" | ||||
# Another option, seems to work great. Catches things like ''.<tab> | ||||
m = re.match(r"(\S+(\.\w+)*)\.(\w*)$", text) | ||||
Steven Silvester
|
r24587 | |||
macgyver
|
r4477 | if m: | ||
Bernardo B. Marques
|
r4872 | expr, attr = m.group(1, 3) | ||
MinRK
|
r4825 | elif self.greedy: | ||
macgyver
|
r4477 | m2 = re.match(r"(.+)\.(\w*)$", self.line_buffer) | ||
if not m2: | ||||
return [] | ||||
expr, attr = m2.group(1,2) | ||||
MinRK
|
r4825 | else: | ||
return [] | ||||
Steven Silvester
|
r24587 | |||
ville
|
r988 | try: | ||
obj = eval(expr, self.namespace) | ||||
except: | ||||
try: | ||||
obj = eval(expr, self.global_namespace) | ||||
except: | ||||
return [] | ||||
Tim Couper
|
r6308 | if self.limit_to__all__ and hasattr(obj, '__all__'): | ||
words = get__all__entries(obj) | ||||
Matthias Bussonnier
|
r23284 | else: | ||
Tim Couper
|
r6308 | words = dir2(obj) | ||
Bernardo B. Marques
|
r4872 | |||
ville
|
r988 | try: | ||
words = generics.complete_object(obj, words) | ||||
Brian Granger
|
r2205 | except TryNext: | ||
ville
|
r988 | pass | ||
Matthias Bussonnier
|
r23284 | except AssertionError: | ||
raise | ||||
Thomas Kluyver
|
r5155 | except Exception: | ||
# Silence errors from completion function | ||||
#raise # dbg | ||||
pass | ||||
ville
|
r988 | # Build match list to return | ||
n = len(attr) | ||||
Matthias Bussonnier
|
r22243 | return [u"%s.%s" % (expr, w) for w in words if w[:n] == attr ] | ||
ville
|
r988 | |||
Fernando Perez
|
r2365 | |||
Tim Couper
|
r6308 | def get__all__entries(obj): | ||
"""returns the strings in the __all__ attribute""" | ||||
try: | ||||
Fernando Perez
|
r6945 | words = getattr(obj, '__all__') | ||
Tim Couper
|
r6308 | except: | ||
return [] | ||||
Steven Silvester
|
r24587 | |||
Srinivas Reddy Thatiparthy
|
r23669 | return [w for w in words if isinstance(w, str)] | ||
Tim Couper
|
r6308 | |||
Corentin Cadiou
|
r25848 | def match_dict_keys(keys: List[Union[str, bytes, Tuple[Union[str, bytes]]]], prefix: str, delims: str, | ||
Corentin Cadiou
|
r25849 | extra_prefix: Optional[Tuple[str, bytes]]=None) -> Tuple[str, int, List[str]]: | ||
Matthias Bussonnier
|
r23322 | """Used by dict_key_matches, matching the prefix to a list of keys | ||
Parameters | ||||
Matthias Bussonnier
|
r25890 | ---------- | ||
Matthias Bussonnier
|
r26491 | keys | ||
Matthias Bussonnier
|
r23677 | list of keys in dictionary currently being completed. | ||
Matthias Bussonnier
|
r26491 | prefix | ||
Corentin Cadiou
|
r25848 | Part of the text already typed by the user. E.g. `mydict[b'fo` | ||
Matthias Bussonnier
|
r26491 | delims | ||
Matthias Bussonnier
|
r23677 | String of delimiters to consider when finding the current key. | ||
Matthias Bussonnier
|
r26491 | extra_prefix : optional | ||
Matthias Bussonnier
|
r25890 | Part of the text already typed in multi-key index cases. E.g. for | ||
Corentin Cadiou
|
r25848 | `mydict['foo', "bar", 'b`, this would be `('foo', 'bar')`. | ||
Matthias Bussonnier
|
r23322 | |||
Returns | ||||
Matthias Bussonnier
|
r25890 | ------- | ||
Matthias Bussonnier
|
r23322 | 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 list of replacement/completion | ||||
""" | ||||
Corentin Cadiou
|
r25849 | prefix_tuple = extra_prefix if extra_prefix else () | ||
Corentin Cadiou
|
r25848 | Nprefix = len(prefix_tuple) | ||
Corentin Cadiou
|
r25852 | def filter_prefix_tuple(key): | ||
# Reject too short keys | ||||
Corentin Cadiou
|
r25851 | if len(key) <= Nprefix: | ||
Corentin Cadiou
|
r25848 | return False | ||
Corentin Cadiou
|
r25852 | # Reject keys with non str/bytes in it | ||
for k in key: | ||||
if not isinstance(k, (str, bytes)): | ||||
return False | ||||
# Reject keys that do not match the prefix | ||||
Corentin Cadiou
|
r25848 | for k, pt in zip(key, prefix_tuple): | ||
if k != pt: | ||||
return False | ||||
Corentin Cadiou
|
r25852 | # All checks passed! | ||
return True | ||||
Corentin Cadiou
|
r25850 | |||
filtered_keys:List[Union[str,bytes]] = [] | ||||
def _add_to_filtered_keys(key): | ||||
if isinstance(key, (str, bytes)): | ||||
filtered_keys.append(key) | ||||
Corentin Cadiou
|
r25848 | |||
for k in keys: | ||||
Corentin Cadiou
|
r25850 | if isinstance(k, tuple): | ||
Corentin Cadiou
|
r25852 | if filter_prefix_tuple(k): | ||
Corentin Cadiou
|
r25850 | _add_to_filtered_keys(k[Nprefix]) | ||
else: | ||||
_add_to_filtered_keys(k) | ||||
Corentin Cadiou
|
r25848 | |||
Joel Nothman
|
r15766 | if not prefix: | ||
Corentin Cadiou
|
r25850 | return '', 0, [repr(k) for k in filtered_keys] | ||
Joel Nothman
|
r15766 | quote_match = re.search('["\']', prefix) | ||
Matthias Bussonnier
|
r25705 | assert quote_match is not None # silence mypy | ||
Joel Nothman
|
r15766 | quote = quote_match.group() | ||
try: | ||||
prefix_str = eval(prefix + quote, {}) | ||||
except Exception: | ||||
Matthias Bussonnier
|
r25705 | return '', 0, [] | ||
Jeff Hussmann
|
r21294 | |||
pattern = '[^' + ''.join('\\' + c for c in delims) + ']*$' | ||||
token_match = re.search(pattern, prefix, re.UNICODE) | ||||
Matthias Bussonnier
|
r25705 | assert token_match is not None # silence mypy | ||
MinRK
|
r16564 | token_start = token_match.start() | ||
token_prefix = token_match.group() | ||||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25705 | matched:List[str] = [] | ||
Corentin Cadiou
|
r25850 | for key in filtered_keys: | ||
Joel Nothman
|
r15766 | try: | ||
if not key.startswith(prefix_str): | ||||
continue | ||||
Joel Nothman
|
r16456 | except (AttributeError, TypeError, UnicodeError): | ||
Joel Nothman
|
r15766 | # Python 3+ TypeError on b'a'.startswith('a') or vice-versa | ||
continue | ||||
# reformat remainder of key to begin with prefix | ||||
rem = key[len(prefix_str):] | ||||
Joel Nothman
|
r16456 | # force repr wrapped in ' | ||
Matthias Bussonnier
|
r23322 | rem_repr = repr(rem + '"') if isinstance(rem, str) else repr(rem + b'"') | ||
Joel Nothman
|
r15766 | rem_repr = rem_repr[1 + rem_repr.index("'"):-2] | ||
if quote == '"': | ||||
Joel Nothman
|
r16456 | # The entered prefix is quoted with ", | ||
# but the match is quoted with '. | ||||
# A contained " hence needs escaping for comparison: | ||||
Joel Nothman
|
r15766 | rem_repr = rem_repr.replace('"', '\\"') | ||
# then reinsert prefix from start of token | ||||
matched.append('%s%s' % (token_prefix, rem_repr)) | ||||
MinRK
|
r16564 | return quote, token_start, matched | ||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r23677 | def cursor_to_position(text:str, line:int, column:int)->int: | ||
Matthias Bussonnier
|
r23284 | """ | ||
Convert the (line,column) position of the cursor in text to an offset in a | ||||
string. | ||||
Matthias Bussonnier
|
r23465 | Parameters | ||
---------- | ||||
Matthias Bussonnier
|
r23284 | 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 | ||||
Matthias Bussonnier
|
r25890 | Returns | ||
------- | ||||
Position of the cursor in ``text``, 0-indexed. | ||||
Matthias Bussonnier
|
r23284 | |||
See Also | ||||
-------- | ||||
Matthias Bussonnier
|
r25890 | position_to_cursor : reciprocal of this function | ||
Matthias Bussonnier
|
r23284 | |||
""" | ||||
lines = text.split('\n') | ||||
assert line <= len(lines), '{} <= {}'.format(str(line), str(len(lines))) | ||||
return sum(len(l) + 1 for l in lines[:line]) + column | ||||
Matthias Bussonnier
|
r23677 | def position_to_cursor(text:str, offset:int)->Tuple[int, int]: | ||
Matthias Bussonnier
|
r23284 | """ | ||
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``. | ||||
Matthias Bussonnier
|
r23465 | Parameters | ||
---------- | ||||
Matthias Bussonnier
|
r23284 | text : str | ||
The text in which to calculate the cursor offset | ||||
offset : int | ||||
Position of the cursor in ``text``, 0-indexed. | ||||
Matthias Bussonnier
|
r25890 | Returns | ||
------- | ||||
Matthias Bussonnier
|
r23284 | (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 | ||||
""" | ||||
Thomas Kluyver
|
r24243 | assert 0 <= offset <= len(text) , "0 <= %s <= %s" % (offset , len(text)) | ||
Matthias Bussonnier
|
r23284 | |||
before = text[:offset] | ||||
blines = before.split('\n') # ! splitnes trim trailing \n | ||||
line = before.count('\n') | ||||
col = len(blines[-1]) | ||||
return line, col | ||||
Joel Nothman
|
r16468 | def _safe_isinstance(obj, module, class_name): | ||
"""Checks if obj is an instance of module.class_name if loaded | ||||
""" | ||||
return (module in sys.modules and | ||||
Diego Garcia
|
r22954 | isinstance(obj, getattr(import_module(module), class_name))) | ||
Joel Nothman
|
r16468 | |||
Matthias Bussonnier
|
r25705 | def back_unicode_name_matches(text:str) -> Tuple[str, Sequence[str]]: | ||
Matthias Bussonnier
|
r25706 | """Match Unicode characters back to Unicode name | ||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | This does ``☃`` -> ``\\snowman`` | ||
Matthias Bussonnier
|
r21103 | |||
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. | ||||
Thomas Kluyver
|
r21578 | This will not either back-complete standard sequences like \\n, \\b ... | ||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r25705 | Returns | ||
======= | ||||
Matthias Bussonnier
|
r25706 | 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. | ||||
Matthias Bussonnier
|
r25705 | |||
Matthias Bussonnier
|
r21103 | """ | ||
if len(text)<2: | ||||
Matthias Bussonnier
|
r25705 | return '', () | ||
Matthias Bussonnier
|
r21103 | maybe_slash = text[-2] | ||
if maybe_slash != '\\': | ||||
Matthias Bussonnier
|
r25705 | return '', () | ||
Matthias Bussonnier
|
r21103 | |||
char = text[-1] | ||||
# no expand on quote for completion in strings. | ||||
# nor backcomplete standard ascii keys | ||||
Matthias Bussonnier
|
r25706 | if char in string.ascii_letters or char in ('"',"'"): | ||
Matthias Bussonnier
|
r25705 | return '', () | ||
Matthias Bussonnier
|
r21103 | try : | ||
unic = unicodedata.name(char) | ||||
Matthias Bussonnier
|
r25706 | return '\\'+char,('\\'+unic,) | ||
Min RK
|
r22340 | except KeyError: | ||
Matthias Bussonnier
|
r21103 | pass | ||
Matthias Bussonnier
|
r25705 | return '', () | ||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r25707 | def back_latex_name_matches(text:str) -> Tuple[str, Sequence[str]] : | ||
Matthias Bussonnier
|
r23284 | """Match latex characters back to unicode name | ||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | This does ``\\ℵ`` -> ``\\aleph`` | ||
Matthias Bussonnier
|
r21103 | |||
""" | ||||
if len(text)<2: | ||||
Matthias Bussonnier
|
r25707 | return '', () | ||
Matthias Bussonnier
|
r21103 | maybe_slash = text[-2] | ||
if maybe_slash != '\\': | ||||
Matthias Bussonnier
|
r25707 | return '', () | ||
Matthias Bussonnier
|
r21103 | |||
char = text[-1] | ||||
# no expand on quote for completion in strings. | ||||
# nor backcomplete standard ascii keys | ||||
Matthias Bussonnier
|
r25707 | if char in string.ascii_letters or char in ('"',"'"): | ||
return '', () | ||||
Matthias Bussonnier
|
r21103 | try : | ||
latex = reverse_latex_symbol[char] | ||||
# '\\' replace the \ as well | ||||
return '\\'+char,[latex] | ||||
Min RK
|
r22340 | except KeyError: | ||
Matthias Bussonnier
|
r21103 | pass | ||
Matthias Bussonnier
|
r25707 | return '', () | ||
Matthias Bussonnier
|
r21103 | |||
Matthias Bussonnier
|
r23753 | 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. | ||||
Matthias Bussonnier
|
r25890 | Parameters | ||
---------- | ||||
Matthias Bussonnier
|
r26491 | parameter | ||
Matthias Bussonnier
|
r23753 | Jedi's function `Param` | ||
Returns | ||||
Matthias Bussonnier
|
r25890 | ------- | ||
Matthias Bussonnier
|
r23753 | 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 | ||||
Matthias Bussonnier
|
r25890 | Parameters | ||
---------- | ||||
Matthias Bussonnier
|
r26491 | completion : jedi.Completion | ||
Matthias Bussonnier
|
r23753 | object does not complete a function type | ||
Returns | ||||
Matthias Bussonnier
|
r25890 | ------- | ||
Matthias Bussonnier
|
r23753 | a string consisting of the function signature, with the parenthesis but | ||
without the function name. example: | ||||
`(a, *args, b=1, **kwargs)` | ||||
""" | ||||
Matthias Bussonnier
|
r25647 | # it looks like this might work on jedi 0.17 | ||
if hasattr(completion, 'get_signatures'): | ||||
Matthias Bussonnier
|
r25650 | signatures = completion.get_signatures() | ||
if not signatures: | ||||
return '(?)' | ||||
Matthias Bussonnier
|
r25647 | c0 = completion.get_signatures()[0] | ||
return '('+c0.to_string().split('(', maxsplit=1)[1] | ||||
Diego Fernandez
|
r25593 | return '(%s)'% ', '.join([f for f in (_formatparamchildren(p) for signature in completion.get_signatures() | ||
for p in signature.defined_names()) if f]) | ||||
Matthias Bussonnier
|
r23753 | |||
Matthias Bussonnier
|
r25705 | |||
class _CompleteResult(NamedTuple): | ||||
matched_text : str | ||||
matches: Sequence[str] | ||||
matches_origin: Sequence[str] | ||||
jedi_matches: Any | ||||
ville
|
r988 | class IPCompleter(Completer): | ||
"""Extension of the completer class with IPython-specific features""" | ||||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r25705 | __dict_key_regexps: Optional[Dict[bool,Pattern]] = None | ||
Min RK
|
r22340 | @observe('greedy') | ||
def _greedy_changed(self, change): | ||||
MinRK
|
r4825 | """update the splitter and readline delims when greedy is changed""" | ||
Min RK
|
r22340 | if change['new']: | ||
Fernando Perez
|
r6945 | self.splitter.delims = GREEDY_DELIMS | ||
MinRK
|
r4825 | else: | ||
Fernando Perez
|
r6945 | self.splitter.delims = DELIMS | ||
Steven Silvester
|
r24587 | |||
dict_keys_only = Bool(False, | ||||
help="""Whether to show dict key matches only""") | ||||
Min RK
|
r22340 | merge_completions = Bool(True, | ||
MinRK
|
r5231 | help="""Whether to merge completion results into a single list | ||
Steven Silvester
|
r24587 | |||
MinRK
|
r5231 | If False, only the completion results from the first non-empty | ||
completer will be returned. | ||||
""" | ||||
Min RK
|
r22340 | ).tag(config=True) | ||
omit__names = Enum((0,1,2), default_value=2, | ||||
MinRK
|
r5231 | help="""Instruct the completer to omit private method names | ||
Steven Silvester
|
r24587 | |||
MinRK
|
r5231 | Specifically, when completing on ``object.<tab>``. | ||
Steven Silvester
|
r24587 | |||
MinRK
|
r5231 | When 2 [default]: all names that start with '_' will be excluded. | ||
Steven Silvester
|
r24587 | |||
MinRK
|
r5231 | When 1: all 'magic' names (``__foo__``) will be excluded. | ||
Steven Silvester
|
r24587 | |||
MinRK
|
r5231 | When 0: nothing will be excluded. | ||
""" | ||||
Min RK
|
r22340 | ).tag(config=True) | ||
limit_to__all__ = Bool(False, | ||||
Kelly Liu
|
r22292 | help=""" | ||
DEPRECATED as of version 5.0. | ||||
Steven Silvester
|
r24587 | |||
Kelly Liu
|
r22292 | Instruct the completer to use __all__ for the completion | ||
Steven Silvester
|
r24587 | |||
Tim Couper
|
r6308 | Specifically, when completing on ``object.<tab>``. | ||
Steven Silvester
|
r24587 | |||
Tim Couper
|
r6311 | When True: only those names in obj.__all__ will be included. | ||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | When False [default]: the __all__ attribute is ignored | ||
Min RK
|
r22340 | """, | ||
).tag(config=True) | ||||
Bernardo B. Marques
|
r4872 | |||
Scott Sanderson
|
r25693 | 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) | ||||
Scott Sanderson
|
r25696 | |||
Matthias Bussonnier
|
r23235 | @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) | ||||
MinRK
|
r4825 | def __init__(self, shell=None, namespace=None, global_namespace=None, | ||
Matthias Bussonnier
|
r23284 | use_readline=_deprecation_readline_sentinel, config=None, **kwargs): | ||
ville
|
r988 | """IPCompleter() -> completer | ||
Matthias Bussonnier
|
r23284 | Return a completer object. | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r23284 | 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. | ||||
Fernando Perez
|
r2839 | use_readline : bool, optional | ||
Matthias Bussonnier
|
r23284 | DEPRECATED, ignored since IPython 6.0, will have no effects | ||
Thomas Kluyver
|
r22934 | """ | ||
ville
|
r988 | |||
Brian Granger
|
r2244 | self.magic_escape = ESC_MAGIC | ||
Fernando Perez
|
r2857 | self.splitter = CompletionSplitter() | ||
Matthias Bussonnier
|
r23284 | if use_readline is not _deprecation_readline_sentinel: | ||
warnings.warn('The `use_readline` parameter is deprecated and ignored since IPython 6.0.', | ||||
Thomas Kluyver
|
r22934 | DeprecationWarning, stacklevel=2) | ||
Fernando Perez
|
r2839 | |||
MinRK
|
r4825 | # _greedy_changed() depends on splitter and readline being defined: | ||
Completer.__init__(self, namespace=namespace, global_namespace=global_namespace, | ||||
MinRK
|
r5231 | config=config, **kwargs) | ||
MinRK
|
r4825 | |||
Fernando Perez
|
r2839 | # List where completion matches will be stored | ||
self.matches = [] | ||||
Fernando Perez
|
r6906 | self.shell = shell | ||
ville
|
r988 | # 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'] | ||||
Bernardo B. Marques
|
r4872 | |||
ville
|
r988 | # 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 | ||||
Fernando Perez
|
r2365 | |||
Piti Ongmongkolkul
|
r9169 | #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*.*)') | ||||
Thomas Kluyver
|
r24128 | self.magic_arg_matchers = [ | ||
self.magic_config_matches, | ||||
self.magic_color_matches, | ||||
] | ||||
# This is set externally by InteractiveShell | ||||
self.custom_completers = None | ||||
Scott Sanderson
|
r25697 | # This is a list of names of unicode characters that can be completed | ||
# into their corresponding unicode value. The list is large, so we | ||||
# laziliy initialize it on first use. Consuming code should access this | ||||
# attribute through the `@unicode_names` property. | ||||
self._unicode_names = None | ||||
Thomas Kluyver
|
r24128 | @property | ||
Matthias Bussonnier
|
r25705 | def matchers(self) -> List[Any]: | ||
Thomas Kluyver
|
r24128 | """All active matcher routines for completion""" | ||
Steven Silvester
|
r24587 | if self.dict_keys_only: | ||
return [self.dict_key_matches] | ||||
Thomas Kluyver
|
r24127 | if self.use_jedi: | ||
Thomas Kluyver
|
r24128 | return [ | ||
Nathan Goldbaum
|
r25490 | *self.custom_matchers, | ||
Thomas Kluyver
|
r24127 | self.file_matches, | ||
self.magic_matches, | ||||
self.dict_key_matches, | ||||
] | ||||
else: | ||||
Thomas Kluyver
|
r24128 | return [ | ||
Nathan Goldbaum
|
r25490 | *self.custom_matchers, | ||
Thomas Kluyver
|
r24127 | self.python_matches, | ||
self.file_matches, | ||||
self.magic_matches, | ||||
self.python_func_kw_matches, | ||||
self.dict_key_matches, | ||||
] | ||||
Thomas Kluyver
|
r22388 | |||
Matthias Bussonnier
|
r25705 | def all_completions(self, text:str) -> List[str]: | ||
andy wilson
|
r3430 | """ | ||
Brandon T. Willard
|
r25121 | Wrapper around the completion methods for the benefit of emacs. | ||
andy wilson
|
r3430 | """ | ||
Brandon T. Willard
|
r25122 | prefix = text.rpartition('.')[0] | ||
Matthias Bussonnier
|
r24920 | with provisionalcompleter(): | ||
Brandon T. Willard
|
r25122 | return ['.'.join([prefix, c.text]) if prefix and self.use_jedi else c.text | ||
for c in self.completions(text, len(text))] | ||||
memeplex
|
r24918 | |||
andy wilson
|
r3430 | return self.complete(text)[1] | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r25705 | def _clean_glob(self, text:str): | ||
ville
|
r988 | return self.glob("%s*" % text) | ||
Matthias Bussonnier
|
r25707 | def _clean_glob_win32(self, text:str): | ||
ville
|
r988 | return [f.replace("\\","/") | ||
Bernardo B. Marques
|
r4872 | for f in self.glob("%s*" % text)] | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r25705 | def file_matches(self, text:str)->List[str]: | ||
ville
|
r988 | """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.""" | ||||
# 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:] | ||||
Matthias Bussonnier
|
r22243 | text_prefix = u'!' | ||
ville
|
r988 | else: | ||
Matthias Bussonnier
|
r22243 | text_prefix = u'' | ||
Fernando Perez
|
r3184 | |||
Fernando Perez
|
r2956 | text_until_cursor = self.text_until_cursor | ||
Fernando Perez
|
r3184 | # 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 = "" | ||||
ville
|
r988 | |||
Fernando Perez
|
r2903 | if not open_quotes and lsplit != protect_filename(lsplit): | ||
Fernando Perez
|
r3184 | # if protectables are found, do matching on the whole escaped name | ||
has_protectables = True | ||||
ville
|
r988 | text0,text = text,lsplit | ||
else: | ||||
Fernando Perez
|
r3184 | has_protectables = False | ||
ville
|
r988 | text = os.path.expanduser(text) | ||
if text == "": | ||||
Srinivas Reddy Thatiparthy
|
r23669 | return [text_prefix + protect_filename(f) for f in self.glob("*")] | ||
ville
|
r988 | |||
Fernando Perez
|
r3184 | # Compute the matches from the filesystem | ||
tmr232
|
r22745 | if sys.platform == 'win32': | ||
m0 = self.clean_glob(text) | ||||
else: | ||||
m0 = self.clean_glob(text.replace('\\', '')) | ||||
Fernando Perez
|
r3184 | |||
ville
|
r988 | 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) | ||||
Bernardo B. Marques
|
r4872 | matches = [text_prefix + text0 + | ||
ville
|
r988 | 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 | ||||
Christopher C. Aycock
|
r23613 | # 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] | ||||
ville
|
r988 | else: | ||
Bernardo B. Marques
|
r4872 | matches = [text_prefix + | ||
ville
|
r988 | protect_filename(f) for f in m0] | ||
Bradley M. Froehle
|
r5766 | # Mark directories in input list by appending '/' to their names. | ||
Srinivas Reddy Thatiparthy
|
r23669 | return [x+'/' if os.path.isdir(x) else x for x in matches] | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r25705 | def magic_matches(self, text:str): | ||
Fernando Perez
|
r2365 | """Match magics""" | ||
# Get all shell magics now rather than statically, so magics loaded at | ||||
Fernando Perez
|
r6991 | # runtime show up too. | ||
lsm = self.shell.magics_manager.lsmagic() | ||||
line_magics = lsm['line'] | ||||
cell_magics = lsm['cell'] | ||||
Fernando Perez
|
r2365 | pre = self.magic_escape | ||
Fernando Perez
|
r6991 | pre2 = pre+pre | ||
Matthias Bussonnier
|
r23898 | |||
explicit_magic = text.startswith(pre) | ||||
Fernando Perez
|
r6991 | # 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 | ||||
Alex Alekseyev
|
r23642 | # | ||
# We also exclude magics that match any currently visible names: | ||||
Matthias Bussonnier
|
r23898 | # https://github.com/ipython/ipython/issues/4877, unless the user has | ||
# typed a %: | ||||
# https://github.com/ipython/ipython/issues/10754 | ||||
Fernando Perez
|
r6991 | bare_text = text.lstrip(pre) | ||
Alex Alekseyev
|
r23642 | global_matches = self.global_matches(bare_text) | ||
Matthias Bussonnier
|
r23898 | 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) | ||||
Alex Alekseyev
|
r23642 | comp = [ pre2+m for m in cell_magics if matches(m)] | ||
Fernando Perez
|
r6991 | if not text.startswith(pre2): | ||
Alex Alekseyev
|
r23642 | comp += [ pre+m for m in line_magics if matches(m)] | ||
Srinivas Reddy Thatiparthy
|
r23669 | return comp | ||
Fernando Perez
|
r2365 | |||
Matthias Bussonnier
|
r23686 | def magic_config_matches(self, text:str) -> List[str]: | ||
Sang Min Park
|
r23641 | """ Match class names and attributes for %config magic """ | ||
Matthias Bussonnier
|
r23686 | texts = text.strip().split() | ||
Sang Min Park
|
r23641 | |||
Sang Min Park
|
r23685 | if len(texts) > 0 and (texts[0] == 'config' or texts[0] == '%config'): | ||
Sang Min Park
|
r23641 | # 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 [] | ||||
Matthias Bussonnier
|
r23686 | def magic_color_matches(self, text:str) -> List[str] : | ||
Sang Min Park
|
r23685 | """ Match color schemes for %colors magic""" | ||
Thomas Kluyver
|
r23824 | 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] | ||||
Sang Min Park
|
r23685 | return [ color for color in InspectColors.keys() | ||
if color.startswith(prefix) ] | ||||
return [] | ||||
Matthias Bussonnier
|
r25705 | def _jedi_matches(self, cursor_column:int, cursor_line:int, text:str) -> Iterable[Any]: | ||
Matthias Bussonnier
|
r23284 | """ | ||
Return a list of :any:`jedi.api.Completions` 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 | ||||
Matthias Bussonnier
|
r25889 | Notes | ||
----- | ||||
Matthias Bussonnier
|
r23284 | If ``IPCompleter.debug`` is ``True`` may return a :any:`_FakeJediCompletion` | ||
object containing a string with the Jedi debug information attached. | ||||
""" | ||||
namespaces = [self.namespace] | ||||
if self.global_namespace is not None: | ||||
namespaces.append(self.global_namespace) | ||||
Ian Rose
|
r23461 | completion_filter = lambda x:x | ||
Matthias Bussonnier
|
r23284 | offset = cursor_to_position(text, cursor_line, cursor_column) | ||
Ian Rose
|
r23461 | # filter output if we are completing for object members | ||
Matthias Bussonnier
|
r23284 | if offset: | ||
pre = text[offset-1] | ||||
Ian Rose
|
r23461 | 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)) | ||||
Matthias Bussonnier
|
r23284 | |||
gorogoroumaru
|
r25654 | interpreter = jedi.Interpreter(text[:offset], namespaces) | ||
Matthias Bussonnier
|
r23948 | try_jedi = True | ||
Matthias Bussonnier
|
r23357 | |||
try: | ||||
Coon, Ethan T
|
r25487 | # find the first token in the current tree -- if it is a ' or " then we are in a string | ||
completing_string = False | ||||
Rastislav Barlik
|
r24034 | try: | ||
Coon, Ethan T
|
r25487 | 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 {"'", '"'} | ||||
Rastislav Barlik
|
r24034 | |||
Matthias Bussonnier
|
r23357 | # 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 [] | ||||
Matthias Bussonnier
|
r23284 | try: | ||
gorogoroumaru
|
r25654 | return filter(completion_filter, interpreter.complete(column=cursor_column, line=cursor_line + 1)) | ||
Matthias Bussonnier
|
r23284 | except Exception as e: | ||
if self.debug: | ||||
Ian Rose
|
r23461 | return [_FakeJediCompletion('Oops Jedi has crashed, please report a bug with the following:\n"""\n%s\ns"""' % (e))] | ||
Matthias Bussonnier
|
r23284 | else: | ||
return [] | ||||
Kelly Liu
|
r22292 | |||
Matthias Bussonnier
|
r25709 | def python_matches(self, text:str)->List[str]: | ||
Kelly Liu
|
r22292 | """Match attributes or global python names""" | ||
ville
|
r988 | 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: | ||||
Paul Ivanov
|
r17755 | re.match(r'\._.*?',txt[txt.rindex('.'):]) is None) | ||
ville
|
r988 | matches = filter(no__name, matches) | ||
except NameError: | ||||
# catches <undefined attributes>.<tab> | ||||
matches = [] | ||||
else: | ||||
matches = self.global_matches(text) | ||||
return matches | ||||
Piti Ongmongkolkul
|
r9171 | def _default_arguments_from_docstring(self, doc): | ||
Piti Ongmongkolkul
|
r9173 | """Parse the first line of docstring for call signature. | ||
Piti Ongmongkolkul
|
r9171 | |||
Docstring should be of the form 'min(iterable[, key=func])\n'. | ||||
Piti Ongmongkolkul
|
r9172 | It can also parse cython docstring of the form | ||
'Minuit.migrad(self, int ncall=10000, resume=True, int nsplit=1)'. | ||||
Piti Ongmongkolkul
|
r9166 | """ | ||
Piti Ongmongkolkul
|
r9169 | if doc is None: | ||
return [] | ||||
Piti Ongmongkolkul
|
r9173 | |||
Piti Ongmongkolkul
|
r9165 | #care only the firstline | ||
Piti Ongmongkolkul
|
r9173 | line = doc.lstrip().splitlines()[0] | ||
Piti Ongmongkolkul
|
r9169 | #p = re.compile(r'^[\w|\s.]+\(([^)]*)\).*') | ||
Piti Ongmongkolkul
|
r9165 | #'min(iterable[, key=func])\n' -> 'iterable[, key=func]' | ||
Piti Ongmongkolkul
|
r9169 | sig = self.docstring_sig_re.search(line) | ||
if sig is None: | ||||
return [] | ||||
Piti Ongmongkolkul
|
r9165 | # iterable[, key=func]' -> ['iterable[' ,' key=func]'] | ||
sig = sig.groups()[0].split(',') | ||||
ret = [] | ||||
for s in sig: | ||||
Piti Ongmongkolkul
|
r9169 | #re.compile(r'[\s|\[]*(\w+)(?:\s*=\s*.*)') | ||
ret += self.docstring_kwd_re.findall(s) | ||||
Piti Ongmongkolkul
|
r9165 | return ret | ||
ville
|
r988 | def _default_arguments(self, obj): | ||
"""Return the list of default arguments of obj if it is callable, | ||||
or empty list otherwise.""" | ||||
Piti Ongmongkolkul
|
r9165 | call_obj = obj | ||
ret = [] | ||||
if inspect.isbuiltin(obj): | ||||
Piti Ongmongkolkul
|
r9167 | pass | ||
Piti Ongmongkolkul
|
r9165 | elif not (inspect.isfunction(obj) or inspect.ismethod(obj)): | ||
ville
|
r988 | if inspect.isclass(obj): | ||
Jean Cruypenynck
|
r23808 | #for cython embedsignature=True the constructor docstring | ||
Piti Ongmongkolkul
|
r9168 | #belongs to the object itself not __init__ | ||
ret += self._default_arguments_from_docstring( | ||||
Piti Ongmongkolkul
|
r9169 | getattr(obj, '__doc__', '')) | ||
Piti Ongmongkolkul
|
r9167 | # for classes, check for __init__,__new__ | ||
Piti Ongmongkolkul
|
r9169 | call_obj = (getattr(obj, '__init__', None) or | ||
getattr(obj, '__new__', None)) | ||||
ville
|
r988 | # for all others, check if they are __call__able | ||
elif hasattr(obj, '__call__'): | ||||
Piti Ongmongkolkul
|
r9165 | call_obj = obj.__call__ | ||
Piti Ongmongkolkul
|
r9167 | ret += self._default_arguments_from_docstring( | ||
Piti Ongmongkolkul
|
r9169 | getattr(call_obj, '__doc__', '')) | ||
Thomas A Caswell
|
r21642 | |||
Min RK
|
r23020 | _keeps = (inspect.Parameter.KEYWORD_ONLY, | ||
inspect.Parameter.POSITIONAL_OR_KEYWORD) | ||||
Thomas A Caswell
|
r21645 | |||
try: | ||||
gpotter2
|
r26860 | sig = inspect.signature(obj) | ||
Thomas A Caswell
|
r21645 | ret.extend(k for k, v in sig.parameters.items() if | ||
v.kind in _keeps) | ||||
except ValueError: | ||||
pass | ||||
Piti Ongmongkolkul
|
r9165 | return list(set(ret)) | ||
ville
|
r988 | |||
Matthias Bussonnier
|
r25705 | def python_func_kw_matches(self, text): | ||
ville
|
r988 | """Match named parameters (kwargs) of the last open function""" | ||
Thomas A Caswell
|
r21642 | |||
ville
|
r988 | if "." in text: # a parameter cannot be dotted | ||
return [] | ||||
try: regexp = self.__funcParamsRegex | ||||
except AttributeError: | ||||
regexp = self.__funcParamsRegex = re.compile(r''' | ||||
Jez Ng
|
r7507 | '.*?(?<!\\)' | # single quoted strings or | ||
".*?(?<!\\)" | # double quoted strings or | ||||
\w+ | # identifier | ||||
\S # other characters | ||||
ville
|
r988 | ''', re.VERBOSE | re.DOTALL) | ||
# 1. find the nearest identifier that comes before an unclosed | ||||
piti118
|
r6510 | # 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) | ||||
Tamir Bahar
|
r22926 | iterTokens = reversed(tokens); openPar = 0 | ||
Piti Ongmongkolkul
|
r9165 | |||
ville
|
r988 | 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 | ||||
Piti Ongmongkolkul
|
r9165 | |||
ville
|
r988 | while True: | ||
try: | ||||
Bradley M. Froehle
|
r7847 | ids.append(next(iterTokens)) | ||
ville
|
r988 | if not isId(ids[-1]): | ||
ids.pop(); break | ||||
Bradley M. Froehle
|
r7847 | if not next(iterTokens) == '.': | ||
ville
|
r988 | break | ||
except StopIteration: | ||||
break | ||||
Tamir Bahar
|
r22926 | |||
# Find all named arguments already assigned to, as to avoid suggesting | ||||
# them again | ||||
usedNamedArgs = set() | ||||
par_level = -1 | ||||
Tamir Bahar
|
r22929 | for token, next_token in zip(tokens, tokens[1:]): | ||
Tamir Bahar
|
r22926 | if token == '(': | ||
par_level += 1 | ||||
elif token == ')': | ||||
par_level -= 1 | ||||
if par_level != 0: | ||||
continue | ||||
if next_token != '=': | ||||
continue | ||||
usedNamedArgs.add(token) | ||||
ville
|
r988 | argMatches = [] | ||
Elyashiv
|
r24729 | try: | ||
callableObj = '.'.join(ids[::-1]) | ||||
namedArgs = self._default_arguments(eval(callableObj, | ||||
self.namespace)) | ||||
Piti Ongmongkolkul
|
r9165 | |||
Tamir Bahar
|
r22926 | # Remove used named arguments from the list, no need to show twice | ||
for namedArg in set(namedArgs) - usedNamedArgs: | ||||
ville
|
r988 | if namedArg.startswith(text): | ||
Matthias Bussonnier
|
r25710 | argMatches.append("%s=" %namedArg) | ||
Elyashiv
|
r24729 | except: | ||
pass | ||||
Brandon T. Willard
|
r25121 | |||
ville
|
r988 | return argMatches | ||
Matthias Bussonnier
|
r25705 | @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, 'numpy', 'ndarray') or\ | ||||
_safe_isinstance(obj, 'numpy', 'void'): | ||||
return obj.dtype.names or [] | ||||
return [] | ||||
def dict_key_matches(self, text:str) -> List[str]: | ||||
Thomas Kluyver
|
r17138 | "Match string keys in a dictionary, after e.g. 'foo[' " | ||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25705 | |||
if self.__dict_key_regexps is not None: | ||||
Joel Nothman
|
r15766 | regexps = self.__dict_key_regexps | ||
Matthias Bussonnier
|
r25705 | else: | ||
Joel Nothman
|
r15766 | dict_key_re_fmt = r'''(?x) | ||
( # match dict-referring expression wrt greedy setting | ||||
%s | ||||
) | ||||
\[ # open bracket | ||||
\s* # and optional whitespace | ||||
Corentin Cadiou
|
r25848 | # Capture any number of str-like objects (e.g. "a", "b", 'c') | ||
Corentin Cadiou
|
r25835 | ((?:[uUbB]? # string prefix (r not handled) | ||
(?: | ||||
'(?:[^']|(?<!\\)\\')*' | ||||
| | ||||
"(?:[^"]|(?<!\\)\\")*" | ||||
) | ||||
\s*,\s* | ||||
)*) | ||||
Joel Nothman
|
r15766 | ([uUbB]? # string prefix (r not handled) | ||
(?: # unclosed string | ||||
'(?:[^']|(?<!\\)\\')* | ||||
| | ||||
"(?:[^"]|(?<!\\)\\")* | ||||
) | ||||
)? | ||||
$ | ||||
''' | ||||
regexps = self.__dict_key_regexps = { | ||||
Matthias Bussonnier
|
r24452 | False: re.compile(dict_key_re_fmt % r''' | ||
Joel Nothman
|
r15766 | # identifiers separated by . | ||
(?!\d)\w+ | ||||
(?:\.(?!\d)\w+)* | ||||
'''), | ||||
True: re.compile(dict_key_re_fmt % ''' | ||||
.+ | ||||
''') | ||||
} | ||||
match = regexps[self.greedy].search(self.text_until_cursor) | ||||
Corentin Cadiou
|
r25848 | |||
Joel Nothman
|
r15766 | if match is None: | ||
return [] | ||||
Corentin Cadiou
|
r25848 | expr, prefix0, prefix = match.groups() | ||
Joel Nothman
|
r15766 | try: | ||
obj = eval(expr, self.namespace) | ||||
except Exception: | ||||
try: | ||||
obj = eval(expr, self.global_namespace) | ||||
except Exception: | ||||
return [] | ||||
Matthias Bussonnier
|
r25705 | keys = self._get_keys(obj) | ||
Joel Nothman
|
r15766 | if not keys: | ||
return keys | ||||
Corentin Cadiou
|
r25848 | |||
Corentin Cadiou
|
r25849 | extra_prefix = eval(prefix0) if prefix0 != '' else None | ||
Corentin Cadiou
|
r25848 | |||
Corentin Cadiou
|
r25849 | closing_quote, token_offset, matches = match_dict_keys(keys, prefix, self.splitter.delims, extra_prefix=extra_prefix) | ||
MinRK
|
r16564 | if not matches: | ||
return matches | ||||
Steven Silvester
|
r24587 | |||
MinRK
|
r16564 | # 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 prefix: | ||||
Corentin Cadiou
|
r25848 | key_start = match.start(3) | ||
MinRK
|
r16564 | completion_start = key_start + token_offset | ||
else: | ||||
key_start = completion_start = match.end() | ||||
Steven Silvester
|
r24587 | |||
MinRK
|
r16564 | # 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] | ||||
Steven Silvester
|
r24587 | |||
MinRK
|
r16564 | # the index of the `[` character | ||
bracket_idx = match.end(1) | ||||
Joel Nothman
|
r15766 | |||
# append closing quote and bracket as appropriate | ||||
MinRK
|
r16564 | # this is *not* appropriate if the opening quote or bracket is outside | ||
# the text given to this method | ||||
suf = '' | ||||
Joel Nothman
|
r15766 | continuation = self.line_buffer[len(self.text_until_cursor):] | ||
MinRK
|
r16564 | if key_start > text_start and closing_quote: | ||
# quotes were opened inside text, maybe close them | ||||
if continuation.startswith(closing_quote): | ||||
continuation = continuation[len(closing_quote):] | ||||
else: | ||||
suf += closing_quote | ||||
if bracket_idx > text_start: | ||||
# brackets were opened inside text, maybe close them | ||||
if not continuation.startswith(']'): | ||||
suf += ']' | ||||
Steven Silvester
|
r24587 | |||
MinRK
|
r16564 | return [leading + k + suf for k in matches] | ||
Joel Nothman
|
r15766 | |||
Matthias Bussonnier
|
r25705 | @staticmethod | ||
def unicode_name_matches(text:str) -> Tuple[str, List[str]] : | ||||
"""Match Latex-like syntax for unicode characters base | ||||
Matthias Bussonnier
|
r21101 | on the name of the character. | ||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | This does ``\\GREEK SMALL LETTER ETA`` -> ``η`` | ||
Matthias Bussonnier
|
r21101 | |||
Matthias Bussonnier
|
r23284 | Works only on valid python 3 identifier, or on combining characters that | ||
Matthias Bussonnier
|
r21101 | 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] | ||||
Min RK
|
r22340 | except KeyError: | ||
Matthias Bussonnier
|
r21101 | pass | ||
Matthias Bussonnier
|
r25705 | return '', [] | ||
Matthias Bussonnier
|
r21101 | |||
Matthias Bussonnier
|
r25710 | def latex_matches(self, text:str) -> Tuple[str, Sequence[str]]: | ||
"""Match Latex syntax for unicode characters. | ||||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | This does both ``\\alp`` -> ``\\alpha`` and ``\\alpha`` -> ``α`` | ||
Thomas Kluyver
|
r17810 | """ | ||
Brian E. Granger
|
r17738 | slashpos = text.rfind('\\') | ||
if slashpos > -1: | ||||
s = text[slashpos:] | ||||
if s in latex_symbols: | ||||
Brian E. Granger
|
r17740 | # Try to complete a full latex symbol to unicode | ||
# \\alpha -> α | ||||
Brian E. Granger
|
r17738 | return s, [latex_symbols[s]] | ||
Brian E. Granger
|
r17740 | 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)] | ||||
Matthias Bussonnier
|
r25686 | if matches: | ||
return s, matches | ||||
Matthias Bussonnier
|
r25710 | return '', () | ||
Brian E. Granger
|
r17700 | |||
Fernando Perez
|
r2959 | def dispatch_custom_completer(self, text): | ||
Thomas Kluyver
|
r22388 | if not self.custom_completers: | ||
return | ||||
Bernardo B. Marques
|
r4872 | line = self.line_buffer | ||
ville
|
r988 | if not line.strip(): | ||
return None | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2959 | # Create a little structure to pass all the relevant information about | ||
# the current completion to any custom completer. | ||||
Matthias Bussonnier
|
r23284 | event = SimpleNamespace() | ||
ville
|
r988 | event.line = line | ||
event.symbol = text | ||||
cmd = line.split(None,1)[0] | ||||
event.command = cmd | ||||
Fernando Perez
|
r2959 | event.text_until_cursor = self.text_until_cursor | ||
Bernardo B. Marques
|
r4872 | |||
ville
|
r988 | # for foo etc, try also to find completer for %foo | ||
if not cmd.startswith(self.magic_escape): | ||||
try_magic = self.custom_completers.s_matches( | ||||
Bernardo B. Marques
|
r4872 | self.magic_escape + cmd) | ||
ville
|
r988 | else: | ||
Bernardo B. Marques
|
r4872 | try_magic = [] | ||
Fernando Perez
|
r2365 | for c in itertools.chain(self.custom_completers.s_matches(cmd), | ||
Fernando Perez
|
r2956 | try_magic, | ||
self.custom_completers.flat_matches(self.text_until_cursor)): | ||||
ville
|
r988 | try: | ||
res = c(event) | ||||
Fernando Perez
|
r3064 | if res: | ||
# first, try case sensitive match | ||||
Srinivas Reddy Thatiparthy
|
r23669 | withcase = [r for r in res if r.startswith(text)] | ||
Fernando Perez
|
r3064 | if withcase: | ||
return withcase | ||||
# if none, then case insensitive ones are ok too | ||||
text_low = text.lower() | ||||
Srinivas Reddy Thatiparthy
|
r23669 | return [r for r in res if r.lower().startswith(text_low)] | ||
Brian Granger
|
r2205 | except TryNext: | ||
ville
|
r988 | pass | ||
Matthias Bussonnier
|
r23921 | except KeyboardInterrupt: | ||
""" | ||||
If custom completer take too long, | ||||
let keyboard interrupt abort and return nothing. | ||||
""" | ||||
break | ||||
Bernardo B. Marques
|
r4872 | |||
ville
|
r988 | return None | ||
Bernardo B. Marques
|
r4872 | |||
Matthias Bussonnier
|
r23284 | def completions(self, text: str, offset: int)->Iterator[Completion]: | ||
""" | ||||
Returns an iterator over the possible completions | ||||
Matthias Bussonnier
|
r26333 | .. warning:: | ||
Unstable | ||||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23284 | This function is unstable, API may change without warning. | ||
It will also raise unless use in proper context manager. | ||||
Parameters | ||||
---------- | ||||
Matthias Bussonnier
|
r26491 | text : str | ||
Matthias Bussonnier
|
r23284 | Full text of the current input, multi line string. | ||
Matthias Bussonnier
|
r26491 | offset : int | ||
Matthias Bussonnier
|
r23284 | Integer representing the position of the cursor in ``text``. Offset | ||
is 0-based indexed. | ||||
Yields | ||||
------ | ||||
Matthias Bussonnier
|
r25889 | Completion | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25889 | Notes | ||
----- | ||||
Matthias Bussonnier
|
r23284 | 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. | ||||
Matthias Bussonnier
|
r23358 | .. note:: | ||
Steven Silvester
|
r24587 | |||
Matthias Bussonnier
|
r23358 | 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. | ||||
Matthias Bussonnier
|
r23284 | """ | ||
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() | ||||
Matthias Bussonnier
|
r25705 | profiler:Optional[cProfile.Profile] | ||
Matthias Bussonnier
|
r23907 | try: | ||
Scott Sanderson
|
r25693 | if self.profile_completions: | ||
import cProfile | ||||
profiler = cProfile.Profile() | ||||
profiler.enable() | ||||
else: | ||||
profiler = None | ||||
Matthias Bussonnier
|
r23907 | 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 | ||||
Scott Sanderson
|
r25693 | 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) | ||||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25705 | def _completions(self, full_text: str, offset: int, *, _timeout) -> Iterator[Completion]: | ||
Matthias Bussonnier
|
r23308 | """ | ||
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 : | ||||
Sang Min Park
|
r23641 | - Long warm-up the first time a module is encountered after | ||
Matthias Bussonnier
|
r23308 | 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 | ||||
Matthias Bussonnier
|
r23284 | before = full_text[:offset] | ||
cursor_line, cursor_column = position_to_cursor(full_text, offset) | ||||
matched_text, matches, matches_origin, jedi_matches = self._complete( | ||||
full_text=full_text, cursor_line=cursor_line, cursor_pos=cursor_column) | ||||
Matthias Bussonnier
|
r23308 | iter_jm = iter(jedi_matches) | ||
Matthias Bussonnier
|
r23314 | if _timeout: | ||
for jm in iter_jm: | ||||
Matthias Bussonnier
|
r23321 | try: | ||
type_ = jm.type | ||||
except Exception: | ||||
if self.debug: | ||||
print("Error in Jedi getting type of ", jm) | ||||
type_ = None | ||||
Matthias Bussonnier
|
r23314 | delta = len(jm.name_with_symbols) - len(jm.complete) | ||
Matthias Bussonnier
|
r23753 | if type_ == 'function': | ||
signature = _make_signature(jm) | ||||
else: | ||||
signature = '' | ||||
Matthias Bussonnier
|
r23314 | yield Completion(start=offset - delta, | ||
end=offset, | ||||
text=jm.name_with_symbols, | ||||
Matthias Bussonnier
|
r23321 | type=type_, | ||
Matthias Bussonnier
|
r23753 | signature=signature, | ||
Matthias Bussonnier
|
r23314 | _origin='jedi') | ||
if time.monotonic() > deadline: | ||||
break | ||||
Matthias Bussonnier
|
r23308 | |||
for jm in iter_jm: | ||||
delta = len(jm.name_with_symbols) - len(jm.complete) | ||||
Matthias Bussonnier
|
r23314 | yield Completion(start=offset - delta, | ||
end=offset, | ||||
text=jm.name_with_symbols, | ||||
type='<unknown>', # don't compute type for speed | ||||
Matthias Bussonnier
|
r23753 | _origin='jedi', | ||
signature='') | ||||
Matthias Bussonnier
|
r23284 | |||
start_offset = before.rfind(matched_text) | ||||
# TODO: | ||||
luzpaz
|
r24084 | # Suppress this, right now just for debug. | ||
Matthias Bussonnier
|
r23284 | if jedi_matches and matches and self.debug: | ||
Matthias Bussonnier
|
r23753 | yield Completion(start=start_offset, end=offset, text='--jedi/ipython--', | ||
_origin='debug', type='none', signature='') | ||||
Matthias Bussonnier
|
r23284 | |||
# I'm unsure if this is always true, so let's assert and see if it | ||||
# crash | ||||
assert before.endswith(matched_text) | ||||
for m, t in zip(matches, matches_origin): | ||||
Matthias Bussonnier
|
r23753 | yield Completion(start=start_offset, end=offset, text=m, _origin=t, signature='', type='<unknown>') | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25705 | def complete(self, text=None, line_buffer=None, cursor_pos=None) -> Tuple[str, Sequence[str]]: | ||
Fernando Perez
|
r3184 | """Find completions for the given text and line context. | ||
ville
|
r988 | |||
Fernando Perez
|
r2857 | Note that both the text and the line_buffer are optional, but at least | ||
one of them must be given. | ||||
Fernando Perez
|
r2839 | Parameters | ||
---------- | ||||
Matthias Bussonnier
|
r25889 | text : string, optional | ||
Matthias Bussonnier
|
r25890 | Text to perform the completion on. If not given, the line buffer | ||
is split using the instance's CompletionSplitter object. | ||||
Matthias Bussonnier
|
r25889 | line_buffer : string, optional | ||
Matthias Bussonnier
|
r25890 | 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. | ||||
Matthias Bussonnier
|
r25889 | cursor_pos : int, optional | ||
Matthias Bussonnier
|
r25890 | Index of the cursor in the full line buffer. Should be provided by | ||
remote frontends where kernel has no access to frontend state. | ||||
Fernando Perez
|
r3184 | |||
Returns | ||||
------- | ||||
Matthias Bussonnier
|
r25705 | Tuple of two items: | ||
Fernando Perez
|
r3184 | text : str | ||
Matthias Bussonnier
|
r25890 | Text that was actually used in the completion. | ||
Fernando Perez
|
r3184 | matches : list | ||
Matthias Bussonnier
|
r25890 | A list of completion matches. | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25889 | Notes | ||
----- | ||||
Matthias Bussonnier
|
r23284 | 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. | ||||
return self._complete(line_buffer=line_buffer, cursor_pos=cursor_pos, text=text, cursor_line=0)[:2] | ||||
def _complete(self, *, cursor_line, cursor_pos, line_buffer=None, text=None, | ||||
Matthias Bussonnier
|
r25705 | full_text=None) -> _CompleteResult: | ||
Matthias Bussonnier
|
r23284 | """ | ||
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. | ||||
Matthias Bussonnier
|
r25705 | |||
Matthias Bussonnier
|
r25890 | Returns | ||
------- | ||||
Matthias Bussonnier
|
r25705 | A tuple of N elements which are (likely): | ||
matched_text: ? the text that the complete matched | ||||
matches: list of completions ? | ||||
Dimitri Papadopoulos
|
r26875 | matches_origin: ? list same length as matches, and where each completion came from | ||
Matthias Bussonnier
|
r25705 | jedi_matches: list of Jedi matches, have it's own structure. | ||
ville
|
r988 | """ | ||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r25705 | |||
Fernando Perez
|
r2857 | # 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) | ||||
Chilaka Ramakrishna <Chilaka Ramakrishna>
|
r22855 | if self.use_main_ns: | ||
self.namespace = __main__.__dict__ | ||||
Matthias Bussonnier
|
r23284 | # 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] | ||||
Thomas
|
r25715 | 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 '' | ||||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r23446 | if self.backslash_combining_completions: | ||
# allow deactivation of these on windows. | ||||
base_text = text if not line_buffer else line_buffer[:cursor_pos] | ||||
Matthias Bussonnier
|
r25705 | |||
for meth in (self.latex_matches, | ||||
self.unicode_name_matches, | ||||
back_latex_name_matches, | ||||
back_unicode_name_matches, | ||||
self.fwd_unicode_match): | ||||
Matthias Bussonnier
|
r23446 | name_text, name_matches = meth(base_text) | ||
if name_text: | ||||
Matthias Bussonnier
|
r25705 | return _CompleteResult(name_text, name_matches[:MATCHES_LIMIT], \ | ||
[meth.__qualname__]*min(len(name_matches), MATCHES_LIMIT), ()) | ||||
Steven Silvester
|
r24587 | |||
Fernando Perez
|
r2857 | |||
# If no line buffer is given, assume the input text is all there was | ||||
if line_buffer is None: | ||||
line_buffer = text | ||||
Bernardo B. Marques
|
r4872 | |||
Fernando Perez
|
r2956 | self.line_buffer = line_buffer | ||
self.text_until_cursor = self.line_buffer[:cursor_pos] | ||||
Fernando Perez
|
r2857 | |||
Sang Min Park
|
r23685 | # Do magic arg matches | ||
for matcher in self.magic_arg_matchers: | ||||
Thomas Kluyver
|
r23844 | matches = list(matcher(line_buffer))[:MATCHES_LIMIT] | ||
Sang Min Park
|
r23685 | if matches: | ||
Thomas Kluyver
|
r23844 | origins = [matcher.__qualname__] * len(matches) | ||
Matthias Bussonnier
|
r25705 | return _CompleteResult(text, matches, origins, ()) | ||
Sang Min Park
|
r23685 | |||
Fernando Perez
|
r2839 | # Start with a clean slate of completions | ||
Matthias Bussonnier
|
r23284 | matches = [] | ||
Corentin Cadiou
|
r25848 | |||
Matthias Bussonnier
|
r23284 | # FIXME: we should extend our api to return a dict with completions for | ||
# different types of objects. The rlcomplete() method could then | ||||
# simply collapse the dict into a list for readline, but we'd have | ||||
luz.paz
|
r24132 | # richer completion semantics in other environments. | ||
Matthias Bussonnier
|
r25705 | completions:Iterable[Any] = [] | ||
Thomas Kluyver
|
r24126 | if self.use_jedi: | ||
Matthias Bussonnier
|
r23284 | if not full_text: | ||
full_text = line_buffer | ||||
completions = self._jedi_matches( | ||||
cursor_pos, cursor_line, full_text) | ||||
Matthias Bussonnier
|
r25711 | |||
Tony Fast
|
r25168 | if self.merge_completions: | ||
matches = [] | ||||
for matcher in self.matchers: | ||||
try: | ||||
matches.extend([(m, matcher.__qualname__) | ||||
for m in matcher(text)]) | ||||
except: | ||||
# Show the ugly traceback if the matcher causes an | ||||
# exception, but do NOT crash the kernel! | ||||
sys.excepthook(*sys.exc_info()) | ||||
ville
|
r988 | else: | ||
Tony Fast
|
r25168 | for matcher in self.matchers: | ||
matches = [(m, matcher.__qualname__) | ||||
for m in matcher(text)] | ||||
if matches: | ||||
break | ||||
Matthias Bussonnier
|
r23284 | seen = set() | ||
filtered_matches = set() | ||||
for m in matches: | ||||
t, c = m | ||||
if t not in seen: | ||||
filtered_matches.add(m) | ||||
seen.add(t) | ||||
Tony Fast
|
r25168 | _filtered_matches = sorted(filtered_matches, key=lambda x: completions_sorting_key(x[0])) | ||
Matthias Bussonnier
|
r23284 | |||
Tony Fast
|
r25168 | custom_res = [(m, 'custom') for m in self.dispatch_custom_completer(text) or []] | ||
_filtered_matches = custom_res or _filtered_matches | ||||
_filtered_matches = _filtered_matches[:MATCHES_LIMIT] | ||||
Matthias Bussonnier
|
r23677 | _matches = [m[0] for m in _filtered_matches] | ||
origins = [m[1] for m in _filtered_matches] | ||||
Matthias Bussonnier
|
r23284 | |||
Matthias Bussonnier
|
r23677 | self.matches = _matches | ||
David P. Sanders
|
r13343 | |||
Matthias Bussonnier
|
r25705 | return _CompleteResult(text, _matches, origins, completions) | ||
Tony Fast
|
r25168 | |||
Matthias Bussonnier
|
r25710 | def fwd_unicode_match(self, text:str) -> Tuple[str, Sequence[str]]: | ||
Matthias Bussonnier
|
r25705 | """ | ||
Forward match a string starting with a backslash with a list of | ||||
potential Unicode completions. | ||||
Will compute list list of Unicode character names on first call and cache it. | ||||
Matthias Bussonnier
|
r25890 | Returns | ||
------- | ||||
Matthias Bussonnier
|
r25705 | 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. | ||||
Dimitri Papadopoulos
|
r26875 | # Using pygtrie the following seem to work: | ||
Matthias Bussonnier
|
r25705 | |||
# 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. | ||||
zzzz-qq
|
r25018 | |||
Luciana da Costa Marques
|
r24917 | slashpos = text.rfind('\\') | ||
# if text starts with slash | ||||
if slashpos > -1: | ||||
Scott Sanderson
|
r25696 | # 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. | ||||
Luciana da Costa Marques
|
r24919 | s = text[slashpos+1:] | ||
Scott Sanderson
|
r25697 | candidates = [x for x in self.unicode_names if x.startswith(s)] | ||
Matthias Bussonnier
|
r24944 | if candidates: | ||
zzzz-qq
|
r25018 | return s, candidates | ||
Matthias Bussonnier
|
r24944 | else: | ||
return '', () | ||||
Brandon T. Willard
|
r25121 | |||
Luciana da Costa Marques
|
r24917 | # if text does not start with slash | ||
luciana
|
r24916 | else: | ||
Matthias Bussonnier
|
r25705 | return '', () | ||
Scott Sanderson
|
r25697 | |||
@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 | ||||
Matthias Bussonnier
|
r25711 | self._unicode_names = _unicode_name_compute(_UNICODE_RANGES) | ||
Scott Sanderson
|
r25697 | |||
return self._unicode_names | ||||
Matthias Bussonnier
|
r25711 | |||
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 | ||||